#pragma once

// BaseList.h --
//	Interface for class BaseList, current implementation is as an
//	array of ent's.  This implementation was chosen to optimize
//	getting to the ent's rather than inserting and deleting.
//	Also pairs of append's and get's act like push's and pop's
//	and are very efficient.  The only really expensive operations
//	are inserting (but not appending), which requires pushing every
//	element up, and resizing the list, which involves getting new space
//	and moving the data.  Resizing occurs automatically when inserting
//	more elements than the list can currently hold.  Automatic
//	resizing is done by growing by GROWTH_FACTOR at a time and
//	always increases the size of the list.  Resizing to zero
//	(or to less than the current value of num_entries)
//	will decrease the size of the list to the current number of
//	elements.  Resize returns the new max_entries.
//
//	Entries must be either a pointer to the data or nonzero data with
//	sizeof(data) <= sizeof(void*).

#include <stdarg.h>
#include <cassert>
#include <initializer_list>
#include <iterator>
#include <utility>

#include "zeek/util.h"

namespace zeek
	{

enum class ListOrder : int
	{
	ORDERED,
	UNORDERED
	};

template <typename T, ListOrder Order = ListOrder::ORDERED> class List
	{
public:
	constexpr static int DEFAULT_LIST_SIZE = 10;
	constexpr static int LIST_GROWTH_FACTOR = 2;

	~List() { free(entries); }
	explicit List(int size = 0)
		{
		num_entries = 0;

		if ( size <= 0 )
			{
			max_entries = 0;
			entries = nullptr;
			return;
			}

		max_entries = size;

		entries = (T*)util::safe_malloc(max_entries * sizeof(T));
		}

	List(const List& b)
		{
		max_entries = b.max_entries;
		num_entries = b.num_entries;

		if ( max_entries )
			entries = (T*)util::safe_malloc(max_entries * sizeof(T));
		else
			entries = nullptr;

		for ( int i = 0; i < num_entries; ++i )
			entries[i] = b.entries[i];
		}

	List(List&& b)
		{
		entries = b.entries;
		num_entries = b.num_entries;
		max_entries = b.max_entries;

		b.entries = nullptr;
		b.num_entries = b.max_entries = 0;
		}

	List(const T* arr, int n)
		{
		num_entries = max_entries = n;
		entries = (T*)util::safe_malloc(max_entries * sizeof(T));
		memcpy(entries, arr, n * sizeof(T));
		}

	List(std::initializer_list<T> il) : List(il.begin(), il.size()) { }

	List& operator=(const List& b)
		{
		if ( this == &b )
			return *this;

		free(entries);

		max_entries = b.max_entries;
		num_entries = b.num_entries;

		if ( max_entries )
			entries = (T*)util::safe_malloc(max_entries * sizeof(T));
		else
			entries = nullptr;

		for ( int i = 0; i < num_entries; ++i )
			entries[i] = b.entries[i];

		return *this;
		}

	List& operator=(List&& b)
		{
		if ( this == &b )
			return *this;

		free(entries);
		entries = b.entries;
		num_entries = b.num_entries;
		max_entries = b.max_entries;

		b.entries = nullptr;
		b.num_entries = b.max_entries = 0;
		return *this;
		}

	// Return nth ent of list (do not remove).
	T& operator[](int i) const { return entries[i]; }

	void clear() // remove all entries
		{
		free(entries);
		entries = nullptr;
		num_entries = max_entries = 0;
		}

	bool empty() const noexcept { return num_entries == 0; }
	size_t size() const noexcept { return num_entries; }

	int length() const { return num_entries; }
	int max() const { return max_entries; }
	int resize(int new_size = 0) // 0 => size to fit current number of entries
		{
		if ( new_size < num_entries )
			new_size = num_entries; // do not lose any entries

		if ( new_size != max_entries )
			{
			entries = (T*)util::safe_realloc((void*)entries, sizeof(T) * new_size);
			if ( entries )
				max_entries = new_size;
			else
				max_entries = 0;
			}

		return max_entries;
		}

	void push_front(const T& a)
		{
		if ( num_entries == max_entries )
			resize(max_entries ? max_entries * LIST_GROWTH_FACTOR : DEFAULT_LIST_SIZE);

		for ( int i = num_entries; i > 0; --i )
			entries[i] = entries[i - 1]; // move all pointers up one

		++num_entries;
		entries[0] = a;
		}

	void push_back(const T& a)
		{
		if ( num_entries == max_entries )
			resize(max_entries ? max_entries * LIST_GROWTH_FACTOR : DEFAULT_LIST_SIZE);

		entries[num_entries++] = a;
		}

	void pop_front() { remove_nth(0); }
	void pop_back() { remove_nth(num_entries - 1); }

	T& front() { return entries[0]; }
	T& back() { return entries[num_entries - 1]; }

	// The append method is maintained for historical/compatibility reasons.
	// (It's commonly used in the event generation API)
	void append(const T& a) // add to end of list
		{
		push_back(a);
		}

	bool remove(const T& a) // delete entry from list
		{
		int pos = member_pos(a);
		if ( pos != -1 )
			{
			remove_nth(pos);
			return true;
			}

		return false;
		}

	T remove_nth(int n) // delete nth entry from list
		{
		assert(n >= 0 && n < num_entries);

		T old_ent = entries[n];

		// For data where we don't care about ordering, we don't care about keeping
		// the list in the same order when removing an element. Just swap the last
		// element with the element being removed.
		if constexpr ( Order == ListOrder::ORDERED )
			{
			--num_entries;

			for ( ; n < num_entries; ++n )
				entries[n] = entries[n + 1];
			}
		else
			{
			entries[n] = entries[num_entries - 1];
			--num_entries;
			}

		return old_ent;
		}

	// Return 0 if ent is not in the list, ent otherwise.
	bool is_member(const T& a) const
		{
		int pos = member_pos(a);
		return pos != -1;
		}

	// Returns -1 if ent is not in the list, otherwise its position.
	int member_pos(const T& e) const
		{
		int i;
		for ( i = 0; i < length() && e != entries[i]; ++i )
			;

		return (i == length()) ? -1 : i;
		}

	T replace(int ent_index, const T& new_ent) // replace entry #i with a new value
		{
		if ( ent_index < 0 )
			return T{};

		T old_ent{};

		if ( ent_index > num_entries - 1 )
			{ // replacement beyond the end of the list
			resize(ent_index + 1);

			for ( int i = num_entries; i < max_entries; ++i )
				entries[i] = T{};
			num_entries = max_entries;
			}
		else
			old_ent = entries[ent_index];

		entries[ent_index] = new_ent;

		return old_ent;
		}

	// Type traits needed for some of the std algorithms to work
	using value_type = T;
	using pointer = T*;
	using const_pointer = const T*;

	// Iterator support
	using iterator = pointer;
	using const_iterator = const_pointer;
	using reverse_iterator = std::reverse_iterator<iterator>;
	using const_reverse_iterator = std::reverse_iterator<const_iterator>;

	iterator begin() { return entries; }
	iterator end() { return entries + num_entries; }
	const_iterator begin() const { return entries; }
	const_iterator end() const { return entries + num_entries; }
	const_iterator cbegin() const { return entries; }
	const_iterator cend() const { return entries + num_entries; }

	reverse_iterator rbegin() { return reverse_iterator{end()}; }
	reverse_iterator rend() { return reverse_iterator{begin()}; }
	const_reverse_iterator rbegin() const { return const_reverse_iterator{end()}; }
	const_reverse_iterator rend() const { return const_reverse_iterator{begin()}; }
	const_reverse_iterator crbegin() const { return rbegin(); }
	const_reverse_iterator crend() const { return rend(); }

protected:
	// This could essentially be an std::vector if we wanted.  Some
	// reasons to maybe not refactor to use std::vector ?
	//
	//  - Harder to use a custom growth factor.  Also, the growth
	//    factor would be implementation-specific, taking some control over
	//    performance out of our hands.
	//
	//  - It won't ever take advantage of realloc's occasional ability to
	//    grow in-place.
	//
	//  - Combine above point this with lack of control of growth
	//    factor means the common choice of 2x growth factor causes
	//    a growth pattern that crawls forward in memory with no possible
	//    re-use of previous chunks (the new capacity is always larger than
	//    all previously allocated chunks combined).  This point and
	//    whether 2x is empirically an issue still seems debated (at least
	//    GCC seems to stand by 2x as empirically better).
	//
	//  - Sketchy shrinking behavior: standard says that requests to
	//    shrink are non-binding (it's expected implementations heed, but
	//    still not great to have no guarantee).  Also, it would not take
	//    advantage of realloc's ability to contract in-place, it would
	//    allocate-and-copy.

	T* entries;
	int max_entries;
	int num_entries;
	};

// Specialization of the List class to store pointers of a type.
template <typename T, ListOrder Order = ListOrder::ORDERED> using PList = List<T*, Order>;

// Popular type of list: list of strings.
using name_list = PList<char>;

	} // namespace zeek

// Macro to visit each list element in turn.
#define loop_over_list(list, iterator)                                                             \
	int iterator;                                                                                  \
	for ( iterator = 0; iterator < (list).length(); ++iterator )
