#ifndef SoftWire_Optimizer_hpp
#define SoftWire_Optimizer_hpp

#include "RegisterAllocator.hpp"

namespace SoftWire
{
	class Optimizer : public RegisterAllocator
	{
	public:
		using RegisterAllocator::spillAll;

		// Optimization flags
		static void enableCopyPropagation();   // Default on
		static void disableCopyPropagation();

		static void enableDropUnmodified();   // Default on
		static void disableDropUnmodified();

		static void enableSpillUnrelocated();   // Default off
		static void disableSpillUnrelocated();

		static void enableLoadLoadElimination();   // Default on
		static void disableLoadLoadElimination();

	protected:
		struct AllocationTable
		{
			Allocation eax;
			Allocation ecx;
			Allocation edx;
			Allocation ebx;
			Allocation esi;
			Allocation edi;

			Allocation mm0;
			Allocation mm1;
			Allocation mm2;
			Allocation mm3;
			Allocation mm4;
			Allocation mm5;
			Allocation mm6;
			Allocation mm7;

			Allocation xmm0;
			Allocation xmm1;
			Allocation xmm2;
			Allocation xmm3;
			Allocation xmm4;
			Allocation xmm5;
			Allocation xmm6;
			Allocation xmm7;
		};

		Optimizer();

		~Optimizer();

		const AllocationTable getAllocationState();
		void spillAll(const AllocationTable &allocationState);   // Restore state to minimize spills

		void free(const OperandREF &ref);
		void free(const OperandREG32 &reg);
		void free(const OperandMMREG &reg);
		void free(const OperandXMMREG &reg);

		using RegisterAllocator::spill;
		void spill(const OperandREF &ref);

		// Register state restore for forward and backward jumps
		void restoreForward(const AllocationTable &allocationState);
		void restoreBackward(const AllocationTable &allocationState);

		// Overloaded to optimize
		Encoding *cmpxchg(OperandR_M8 r_m8, OperandREG8 r8);
		Encoding *cmpxchg(OperandR_M16 r_m16, OperandREG16 r16);
		Encoding *cmpxchg(OperandR_M32 r_m32, OperandREG32 r32);

		Encoding *lock_cmpxchg(OperandMEM8 m8, OperandREG8 r8);
		Encoding *lock_cmpxchg(OperandMEM16 m16, OperandREG16 r16);
		Encoding *lock_cmpxchg(OperandMEM32 m32, OperandREG32 r32);

		using RegisterAllocator::mov;
		Encoding *mov(OperandREG32 r32i, OperandREG32 r32j);
		Encoding *mov(OperandREG32 r32, OperandMEM32 m32);
		Encoding *mov(OperandREG32 r32, OperandR_M32 r_m32);
		Encoding *mov(OperandMEM32 m32, OperandREG32 r32);
		Encoding *mov(OperandR_M32 r_m32, OperandREG32 r32);
		Encoding *mov(OperandREG32 r32, int imm);

		Encoding *movaps(OperandXMMREG xmmi, OperandXMMREG xmmj);
		Encoding *movaps(OperandXMMREG xmm, OperandMEM128 m128);
		Encoding *movaps(OperandXMMREG xmm, OperandR_M128 r_m128);
		Encoding *movaps(OperandMEM128 m128, OperandXMMREG xmm);
		Encoding *movaps(OperandR_M128 r_m128, OperandXMMREG xmm);

		Encoding *movq(OperandMMREG mmi, OperandMMREG mmj);
		Encoding *movq(OperandMMREG mm, OperandMEM64 mem64);
		Encoding *movq(OperandMMREG mm, OperandR_M64 r_m64);
		Encoding *movq(OperandMEM64 mem64, OperandMMREG mm);
		Encoding *movq(OperandR_M64 r_m64, OperandMMREG mm);

		using RegisterAllocator::movups;
		Encoding *movups(OperandXMMREG xmmi, OperandXMMREG xmmj);
		Encoding *movups(OperandXMMREG xmm, OperandMEM128 m128);
		Encoding *movups(OperandXMMREG xmm, OperandR_M128 r_m128);

		using RegisterAllocator::pshufw;
		Encoding *pshufw(OperandMMREG mmi, OperandMMREG mmj, unsigned char c);

		using RegisterAllocator::shufps;
		Encoding *shufps(OperandXMMREG xmmi, OperandXMMREG xmmj, unsigned char c);

		Encoding *xadd(OperandREG8 r8i, OperandREG8 r8j);
		Encoding *xadd(OperandREG16 r16i, OperandREG16 r16j);
		Encoding *xadd(OperandREG32 r32i, OperandREG32 r32j);
		Encoding *xadd(OperandR_M8 r_m8, OperandREG8 r8);
		Encoding *xadd(OperandR_M16 r_m16, OperandREG16 r16);
		Encoding *xadd(OperandR_M32 r_m32, OperandREG32 r32);
		Encoding *xchg(OperandREG8 r8i, OperandREG8 r8j);
		Encoding *xchg(OperandREG16 r16i, OperandREG16 r16j);
		Encoding *xchg(OperandREG32 r32i, OperandREG32 r32j);
		Encoding *xchg(OperandR_M8 r_m8, OperandREG8 r8);
		Encoding *xchg(OperandR_M16 r_m16, OperandREG16 r16);
		Encoding *xchg(OperandR_M32 r_m32, OperandREG32 r32);
		Encoding *xchg(OperandREG8 r8, OperandR_M8 r_m8);
		Encoding *xchg(OperandREG16 r16, OperandR_M16 r_m16);
		Encoding *xchg(OperandREG32 r32, OperandR_M32 r_m32);

		Encoding *lock_xadd(OperandMEM8 m8, OperandREG8 r8);
		Encoding *lock_xadd(OperandMEM16 m16, OperandREG16 r16);
		Encoding *lock_xadd(OperandMEM32 m32, OperandREG32 r32);
		Encoding *lock_xchg(OperandMEM8 m8, OperandREG8 r8);
		Encoding *lock_xchg(OperandMEM16 m16, OperandREG16 r16);
		Encoding *lock_xchg(OperandMEM32 m32, OperandREG32 r32);

	protected:
		// Overloaded to detect modified/unmodified registers
		virtual Encoding *x86(int instructionID,
		                      const Operand &firstOperand = Operand::OPERAND_VOID,
		                      const Operand &secondOperand = Operand::OPERAND_VOID,
		                      const Operand &thirdOperand = Operand::OPERAND_VOID);

	private:
		void markModified(const Operand &op);   // Used to prevent spilling of unmodified registers
		void markReferenced(const Operand &op);   // Retain the instruction that loads this register
		void swapAllocation(Allocation *destination, Allocation *source);   // Used for copy propagation

		static bool copyPropagation;
		static bool dropUnmodified;
		static bool spillUnrelocated;
		static bool loadLoadElimination;
	};
}

#endif   // SoftWire_Optimizer_hpp