#pragma once

class TemplateNode
{
public:

	TemplateNode(TemplateNode* parent)
		: mParent(parent)
	{

	}

	TemplateNode* getParent() const
	{
		return mParent;
	}

	virtual void addChild(std::unique_ptr<TemplateNode> child)
	{
		mChildren.push_back(std::move(child));
	}

	std::size_t getNumChildren() const
	{
		return mChildren.size();
	}

	virtual std::string getIdentifier() const
	{
		return {};
	}

	TemplateNode* getChild(std::size_t index) const
	{
		return mChildren[index].get();
	}

	virtual std::string getRawContent() const
	{
		std::string content;
		for (const auto& child : mChildren)
		{
			content += child->getRawContent() + "\n";
		}
		return content;
	}

	template<typename T>
	T* getFirstChildShallow(const std::string& identifier = {}) const
	{
		for (const auto& child : mChildren)
		{
			if (auto ret = dynamic_cast<T*>(child.get()))
			{
				if (!identifier.empty())
				{
					if (child->getIdentifier() == identifier)
					{
						return ret;
					}
				}
				else
				{
					return ret;
				}
			}
		}
		return nullptr;
	}

	void setExtensionParent(TemplateNode* parent)
	{
		mExtensionParent = parent;
	}

	virtual std::string render(TemplateNode* parentContext = nullptr)
	{
		if (!parentContext && mExtensionParent)
		{
			parentContext = mExtensionParent;
		}

		std::string content;
		for (size_t idx = 0; idx < mChildren.size(); idx++)
		{
			content += mChildren[idx]->render(parentContext);
		}
		return content;
	}

protected:
	std::vector<std::unique_ptr<TemplateNode> > mChildren;
	TemplateNode* mParent{ nullptr };
	TemplateNode* mExtensionParent{ nullptr };
};

using TemplateNodePtr = std::unique_ptr<TemplateNode>;

class TemplateExtends : public TemplateNode
{
public:
	TemplateExtends(TemplateNode* parent, const std::string& path)
		: TemplateNode(parent)
	{
		mPath = StringUtils::stripQuotes(path);
	};

	std::string getRawContent() const override
	{
		return "TemplateExtends: " + mPath;
	}

	std::string getPath() const
	{
		return mPath;
	}

private:
	std::string mPath;
};

class TemplateBlock : public TemplateNode
{
public:
	TemplateBlock(TemplateNode* parent, const std::string& name)
		: TemplateNode(parent),
		mName(name)
	{

	}

	void addLine(const std::string& line)
	{
		mBody.push_back(line);
	}

	std::string getRawContent() const override
	{
		std::string content = "TemplateBlock: " + mName + "\n";
		content += TemplateNode::getRawContent();
		return content;
	}

	std::string renderAsParent(TemplateNode* base)
	{
		return {};
	}

	std::string render(TemplateNode* parentContext) override
	{
		std::string content;
		if (parentContext)
		{
			if (auto parent_node = parentContext->getFirstChildShallow<TemplateBlock>(getIdentifier()))
			{
				content = dynamic_cast<TemplateBlock*>(parent_node)->renderAsParent(this);
			}
		}
		else
		{

		}
		return content;
	}

private:
	std::string mName;
	std::vector<std::string> mBody;
};

class TemplateExpression : public TemplateNode
{
public:
	TemplateExpression(TemplateNode* parent, const std::string& content)
		: TemplateNode(parent),
		mContent(content)
	{

	}

	std::string getRawContent() const override
	{
		return "TemplateExpression: " + mContent;
	}

private:
	std::string mContent;
};

class TemplateTextBody : public TemplateNode
{
public:
	TemplateTextBody(TemplateNode* parent)
		: TemplateNode(parent)
	{

	}

	void addLine(const std::string& content)
	{
		mContent.push_back(content);
	}

	bool hasContent() const
	{
		return !mContent.empty();
	}

	std::string render(TemplateNode* parentContext) override
	{
		std::string content;
		for (const auto& line : mContent)
		{
			content += line + '\n';
		}
		return content;
	}

	std::string getRawContent() const override
	{
		std::string content;
		for (const auto& line : mContent)
		{
			content += "TemplateBody: " + line + "\n";
		}
		return content;
	}

private:
	std::vector<std::string> mContent;
};