#pragma once

template <typename T>
class Node
{
public:
	T value;
	Node *nextNode;
	Node()
	{
		this->value = 0;
		this->nextNode = nullptr;
	}
	~Node()
	{
		delete nextNode;
	}
	Node(const Node &n)
	{
		value = n.value;
		nextNode = n.nextNode;
	}
	Node &operator=(const Node &n)
	{
		this->value = n.value;
		this->nextNode = n.nextNode;
		return *this;
	}
	Node &operator=(const T &value)
	{
		this->value = value;
		return *this;
	}
};

template <typename T>
class LinkedList
{
private:
	Node<T> *beginingNode;
	int size;

public:
	LinkedList(Node<T> *f = nullptr);
	LinkedList(const LinkedList &l);
	~LinkedList();

	// assignment operator for a LinkedList
	LinkedList &operator=(const LinkedList &l);

	T &operator[](int pos);

	// Adds an element to the current LinkedList.
	void add(const T &e);

	int getSize() const;
};

template <typename T>
LinkedList<T>::LinkedList(Node<T> *f)
{
	this->beginingNode = f;
	this->size = 0;
	if (f != nullptr)
	{
		this->size++;
		while (f->nextNode != nullptr)
			this->size++;
	}
}

template <typename T>
LinkedList<T>::LinkedList(const LinkedList &l)
{
	Node<T> *currentNode = new Node<T>();
	if (l.beginingNode == nullptr)
	{
		beginingNode = nullptr;
		size = 0;
		return;
	}
	*currentNode = *l.beginingNode;
	beginingNode = currentNode;
	size = 1;
	Node<T> *nextNode = l.beginingNode->nextNode;
	while (nextNode != nullptr)
	{
		*(currentNode->nextNode) = *nextNode;
		currentNode = currentNode->nextNode;
		nextNode = nextNode->nextNode;
		size++;
	}
}

template <typename T>
LinkedList<T>::~LinkedList()
{
	delete this->beginingNode;
}

template <typename T>
LinkedList<T> &LinkedList<T>::operator=(const LinkedList &l)
{
	Node<T> *currentNode = new Node<T>();
	if (l.beginingNode == nullptr)
	{
		this->beginingNode = nullptr;
		this->size = 0;
		return *this;
	}
	*currentNode = *l.beginingNode;
	this->beginingNode = currentNode;
	this->size = 1;
	Node<T> *nextNode = l.beginingNode->nextNode;
	while (nextNode != nullptr)
	{
		*(currentNode->nextNode) = *nextNode;
		currentNode = currentNode->nextNode;
		nextNode = nextNode->nextNode;
		this->size++;
	}
	return *this;
}

template <typename T>
T &LinkedList<T>::operator[](int pos)
{
	if (pos >= this->size || pos < 0)
		throw std::invalid_argument("Invalid position");
	Node<T> *currentNode = this->beginingNode;
	for (int i = 0; i < pos; i++)
	{
		currentNode = currentNode->nextNode;
	}
	return currentNode->value;
}

template <typename T>
void LinkedList<T>::add(const T &e)
{
	if (this->beginingNode == nullptr)
	{
		this->beginingNode = new Node<T>();
		this->beginingNode->value = e;
		this->size = 1;
		return;
	}
	Node<T> *currentNode = this->beginingNode;
	for (int i = 1; i < this->size; i++)
	{
		currentNode = currentNode->nextNode;
	}
	currentNode->nextNode = new Node<T>();
	currentNode->nextNode->value = e;
	this->size++;
}

template <typename T>
int LinkedList<T>::getSize() const
{
	return this->size;
}
