#pragma once

#include <list>
#include <functional>
#include "Jitter_SymbolRef.h"

namespace Jitter
{
	enum OPERATION
	{
		OP_NOP = 0,
		OP_MOV,

		OP_ADD,
		OP_SUB,
		OP_CMP,
		OP_SELECT,

		OP_CMPSELECT_P1,
		OP_CMPSELECT_P2,

		OP_AND,
		OP_OR,
		OP_XOR,
		OP_NOT,

		OP_SRA,
		OP_SRL,
		OP_SLL,

		OP_MUL,
		OP_MULS,
		OP_DIV,
		OP_DIVS,

		OP_LZC,

		OP_RELTOREF,
		OP_ADDREF,
		OP_ISREFNULL,
		OP_LOADFROMREF,
		OP_LOAD8FROMREF,
		OP_LOAD16FROMREF,
		OP_STOREATREF,
		OP_STORE8ATREF,
		OP_STORE16ATREF,

		OP_ADD64,
		OP_SUB64,
		OP_AND64,
		OP_CMP64,
		OP_MERGETO64,
		OP_EXTLOW64,
		OP_EXTHIGH64,
		OP_SRA64,
		OP_SRL64,
		OP_SLL64,

		OP_MERGETO256,

		OP_MD_MOV_MASKED,
		OP_MD_LOADFROMREF_MASKED,
		OP_MD_STOREATREF_MASKED,

		OP_MD_ADD_B,
		OP_MD_ADD_H,
		OP_MD_ADD_W,

		OP_MD_ADDSS_B,
		OP_MD_ADDSS_H,
		OP_MD_ADDSS_W,

		OP_MD_ADDUS_B,
		OP_MD_ADDUS_H,
		OP_MD_ADDUS_W,

		OP_MD_SUB_B,
		OP_MD_SUB_H,
		OP_MD_SUB_W,

		OP_MD_SUBSS_B,
		OP_MD_SUBSS_H,
		OP_MD_SUBSS_W,

		OP_MD_SUBUS_B,
		OP_MD_SUBUS_H,
		OP_MD_SUBUS_W,

		OP_MD_CLAMP_S,

		OP_MD_CMPEQ_B,
		OP_MD_CMPEQ_H,
		OP_MD_CMPEQ_W,
		OP_MD_CMPGT_B,
		OP_MD_CMPGT_H,
		OP_MD_CMPGT_W,

		OP_MD_MIN_H,
		OP_MD_MIN_W,
		OP_MD_MAX_H,
		OP_MD_MAX_W,

		OP_MD_AND,
		OP_MD_BITSELECT,
		OP_MD_OR,
		OP_MD_XOR,
		OP_MD_NOT,

		OP_MD_SRLH,
		OP_MD_SRAH,
		OP_MD_SLLH,

		OP_MD_SRLW,
		OP_MD_SRAW,
		OP_MD_SLLW,

		OP_MD_SRL256,

		OP_MD_MAKECLIP,
		OP_MD_MAKESZ,

		OP_MD_TOINT32_TRUNC_S,
		OP_MD_TOSINGLE_I32,

		OP_MD_EXPAND_W,

		OP_MD_UNPACK_LOWER_BH,
		OP_MD_UNPACK_LOWER_HW,
		OP_MD_UNPACK_LOWER_WD,

		OP_MD_UNPACK_UPPER_BH,
		OP_MD_UNPACK_UPPER_HW,
		OP_MD_UNPACK_UPPER_WD,

		OP_MD_PACK_HB,
		OP_MD_PACK_WH,

		OP_MD_ADD_S,
		OP_MD_SUB_S,
		OP_MD_MUL_S,
		OP_MD_DIV_S,
		OP_MD_ABS_S,
		OP_MD_NEG_S,
		OP_MD_MIN_S,
		OP_MD_MAX_S,

		OP_MD_CMPLT_S,
		OP_MD_CMPGT_S,

		OP_FP_ADD_S,
		OP_FP_SUB_S,
		OP_FP_MUL_S,
		OP_FP_DIV_S,
		OP_FP_SQRT_S,
		OP_FP_RSQRT_S,
		OP_FP_RCPL_S,
		OP_FP_ABS_S,
		OP_FP_NEG_S,
		OP_FP_MAX_S,
		OP_FP_MIN_S,
		OP_FP_CMP_S,
		OP_FP_CLAMP_S,

		OP_FP_TOINT32_TRUNC_S,
		OP_FP_TOSINGLE_I32,

		OP_FP_SETROUNDINGMODE,

		OP_FP_LDCST, //This is needed to avoid propagation of constants to fp operations.

		OP_PARAM,
		OP_PARAM_RET,
		OP_CALL,
		OP_RETVAL,
		OP_JMP,
		OP_CONDJMP,
		OP_EXTERNJMP,     //Pass control to another function with same signature (void (*)(void*)) and same input parameter
		OP_EXTERNJMP_DYN, //Same as above, but destination can be changed at run time, cannot be used in AOT mode
		OP_GOTO,
		OP_BREAK,

		OP_LABEL,
	};

	enum CONDITION
	{
		CONDITION_NEVER = 0,
		CONDITION_EQ,
		CONDITION_NE,
		CONDITION_BL,
		CONDITION_BE,
		CONDITION_AB,
		CONDITION_AE,
		CONDITION_LT,
		CONDITION_LE,
		CONDITION_GT,
		CONDITION_GE,
	};

	struct STATEMENT
	{
	public:
		STATEMENT()
		    : op(OP_NOP)
		    , jmpBlock(-1)
		    , jmpCondition(CONDITION_NEVER)
		{
		}

		OPERATION op;
		SymbolRefPtr src1;
		SymbolRefPtr src2;
		SymbolRefPtr src3;
		SymbolRefPtr dst;
		uint32 jmpBlock;
		CONDITION jmpCondition;

		template <typename F>
		void VisitOperands(const F& visitor)
		{
			if(dst) visitor(dst, true);
			if(src1) visitor(src1, false);
			if(src2) visitor(src2, false);
			if(src3) visitor(src3, false);
		}

		template <typename F>
		void VisitOperands(const F& visitor) const
		{
			if(dst) visitor(dst, true);
			if(src1) visitor(src1, false);
			if(src2) visitor(src2, false);
			if(src3) visitor(src3, false);
		}

		template <typename F>
		void VisitDestination(const F& visitor) const
		{
			if(dst) visitor(dst, true);
		}

		template <typename F>
		void VisitSources(const F& visitor)
		{
			if(src1) visitor(src1, false);
			if(src2) visitor(src2, false);
			if(src3) visitor(src3, false);
		}

		template <typename F>
		void VisitSources(const F& visitor) const
		{
			if(src1) visitor(src1, false);
			if(src2) visitor(src2, false);
			if(src3) visitor(src3, false);
		}
	};

	typedef std::list<STATEMENT> StatementList;

	std::string ConditionToString(CONDITION);
	CONDITION NegateCondition(CONDITION);

	void DumpStatementList(const StatementList&);
	void DumpStatementList(std::ostream&, const StatementList&);

	template <typename ListType, typename IteratorType, typename ValueType>
	class IndexedStatementListBase
	{
	public:
		struct ITERATOR
		{
		public:
			struct VALUE
			{
				VALUE(const IteratorType& iterator, ValueType& statement, unsigned int index)
				    : iterator(iterator)
				    , statement(statement)
				    , index(index)
				{
				}

				IteratorType iterator;
				ValueType& statement;
				unsigned int index = 0;
			};

			ITERATOR(const IteratorType& iterator, unsigned int index)
			    : m_iterator(iterator)
			    , m_index(index)
			{
			}

			const ITERATOR& operator++()
			{
				assert(m_index != -1);
				m_iterator++;
				m_index++;
				return (*this);
			}

			bool operator!=(const ITERATOR& rhs) const
			{
				return m_iterator != rhs.m_iterator;
			}

			VALUE operator*() const
			{
				return VALUE(m_iterator, *m_iterator, m_index);
			}

		private:
			IteratorType m_iterator;
			unsigned int m_index = 0;
		};

		IndexedStatementListBase(ListType& statements)
		    : m_statements(statements)
		{
		}

		ITERATOR begin() const
		{
			return ITERATOR(m_statements.begin(), 0);
		}

		ITERATOR end() const
		{
			return ITERATOR(m_statements.end(), -1);
		}

	private:
		ListType& m_statements;
	};

	typedef IndexedStatementListBase<StatementList, StatementList::iterator, STATEMENT> IndexedStatementList;
	typedef IndexedStatementListBase<const StatementList, StatementList::const_iterator, const STATEMENT> ConstIndexedStatementList;
}
