#pragma once

#include <vector>

#include "Array.h"
#include "Text.h"
#include "Writer.h"

namespace Framework
{
    namespace Assembly
    {
        enum Operation
        {
            // no real operation, used for labels
            NOP,

            // Arithmetic Operations

            ADD,   // Addition
            ADDPD, // Add Packed Double-Precision Floating-Point Values
            ADDPS, // Add Packed Single-Precision Floating-Point Values
            ADDSD, // Add Scalar Double-Precision Floating-Point Values
            ADDSS, // Add Scalar Single-Precision Floating-Point Values
            SUB,   // Subtraction
            SUBPD, // Subtract Packed Double-Precision Floating-Point Values
            SUBPS, // Subtract Packed Single-Precision Floating-Point Values
            SUBSD, // Subtract Scalar Double-Precision Floating-Point Values
            SUBSS, // Subtract Scalar Single-Precision Floating-Point Values
            MUL,   // Multiply unsigned
            IMUL,  // Multiply signed
            MULPD, // Multiply Packed Double Precision Floating-Point Values
            MULPS, // Multiply Packed Single Precision Floating-Point Values
            MULSD, // Multiply Scalar Double Precision Floating-Point Value
            MULSS, // Multiply Scalar Single Precision Floating-Point Values
            DIV,   // Division unsigned
            IDIV,  // Division signed
            DIVPD, // Divide Packed Double-Precision Floating-Point Values
            DIVPS, // Divide Packed Single-Precision Floating-Point Values
            DIVSD, // Divide Scalar Double-Precision Floating-Point Values
            DIVSS, // Divide Scalar Single-Precision Floating-Point Values
            NEG,   // Negation
            INC,   // Increment

            // Logical Operations

            AND, // Bitwise AND
            OR,  // Bitwise OR
            XOR, // Bitwise XOR
            NOT, // Bitwise NOT

            // Comparison Operations

            // Bitwise AND without storing the result. only
            // SpecialRegister.FLAGS (SF, ZF, PF) is affected
            TEST,
            // Compare
            // temporary subtracts op2 from op without storing the result. only
            // SpecialRegister.FLAGS is affected
            CMP,
            // Compare Packed Double Precision Floating-Point Values
            CMPPD,
            // Compare Packed Single Precision Floating-Point Values
            CMPPS,
            // Compare Scalar Double Precision Floating-Point Value
            CMPSD,
            // Compare Scalar Single Precision Floating-Point Value
            CMPSS,
            // Compare Scalar Ordered Double Precision Floating-Point Values
            // writes to SpecialRegister.FLAGS (ZF, PF, CF)
            // UNORDERED (one of the values was NAN) = 0b111
            // GREATER_THAN = 0b000
            // LESS_THAN = 0b001
            // EQUAL = 0b100
            COMISD,
            // Compare Scalar Ordered Single Precision Floating-Point Values
            // writes to SpecialRegister.FLAGS (ZF, PF, CF)
            // UNORDERED (one of the values was NAN) = 0b111
            // GREATER_THAN = 0b000
            // LESS_THAN = 0b001
            // EQUAL = 0b100
            COMISS,

            // Data Movement

            MOV, // Move data from source to destination
            // Move Unaligned Packed Double-Precision Floating-Point Value
            MOVUPD,
            // Move Unaligned Packed Single-Precision Floating-Point Value
            MOVUPS,
            MOVSD, // Move Scalar Double-Precision Floating-Point Value
            MOVSS, // Move Scalar Single-Precision Floating-Point Value
            LEA,   // Load Effective Address

            // DATA Conversion

            // Convert Integer to Scalar Double Precision Floating-Point Value
            CVTSI2SD,
            // Convert Integer to Scalar Single Precision Floating-Point Value
            CVTSI2SS,
            // Convert With Truncation Scalar Double Precision Floating-Point
            // Value to Signed Integer
            CVTTSD2SI,
            // Convert With Truncation Scalar Single Precision Floating-Point
            // Value to Signed Integer
            CVTTSS2SI,
            // Convert Scalar Double Precision Floating-Point Value to Integer
            // with rounding according to the bits 13 an 14 of the MXCSR
            // register value:
            //
            // 0b00: (default) round to nearest integer value
            // 0b01: round to the next lower integer value
            // 0b10: round to the next heigher integer value
            // 0b11: truncate (like CVTTSS2SI)
            CVTSD2SI,
            // Convert Scalar Single Precision Floating-Point Value to Integer
            // with rounding according to the bits 13 an 14 of the MXCSR
            // register value:
            //
            // 0b00: (default) round to nearest integer value
            // 0b01: round to the next lower integer value
            // 0b10: round to the next heigher integer value
            // 0b11: truncate (like CVTTSS2SI)
            CVTSS2SI,

            // Control Flow

            // Unconditional Jump
            JMP,
            // Jump if Zero: SpecialRegister.FLAGS(ZF) = 1
            JZ,
            // Jump if equal: SpecialRegister.FLAGS(ZF) = 1
            JE = JZ,
            // Jump if Not Zero: SpecialRegister.FLAGS(ZF) = 0
            JNZ,
            // Jump if Not Equal: SpecialRegister.FLAGS(ZF) = 0
            JNE = JNZ,
            // Jump if Greater: SpecialRegister.FLAGS(SF) =
            // SpecialRegister.FLAGS(OF) and SpecialRegister.FLAGS(ZF) = 0
            JG,
            // Jump if Greater or Equal: SpecialRegister.FLAGS(SF) =
            // SpecialRegister.FLAGS(OF)
            JGE,
            // Jump if Less: SpecialRegister.FLAGS(SF) !=
            // SpecialRegister.FLAGS(OF)
            JL,
            // Jump if Less or Equal: SpecialRegister.FLAGS(SF) !=
            // SpecialRegister.FLAGS(OF) or SpecialRegister.FLAGS(ZF) = 1
            JLE,
            // Jump if above: SpecialRegister.FLAGS(CF) = 0 and
            // SpecialRegister.FLAGS(ZF) = 0
            JA,
            // Jump if carry: SpecialRegister.FLAGS(CF) = 1
            JC,
            // Jump if below:  SpecialRegister.FLAGS(CF) = 1
            JB = JC,
            // Jump if not carry: SpecialRegister.FLAGS(CF) = 0
            JNC,
            // Jump if not below: SpecialRegister.FLAGS(CF) = 0
            JNB = JNC,
            // Jump if below or equal: SpecialRegister.FLAGS(CF) = 1 or
            // SpecialRegister.FLAGS(ZF) = 1
            JBE,
            JO,   // Jump if overflow: SpecialRegister.FLAGS(OF) = 1
            JNO,  // Jump if not overflow: SpecialRegister.FLAGS(OF) = 0
            JP,   // Jump if parity even: SpecialRegister.FLAGS(PF) = 1
            JNP,  // Jump if not parity odd: SpecialRegister.FLAGS(PF) = 0
            JS,   // Jump if sign: SpecialRegister.FLAGS(SF) = 1
            JNS,  // Jump if not sign: SpecialRegister.FLAGS(SF) = 0
            CALL, // Call subroutine
            RET,  // Return from subroutine

            // Stack Operations

            PUSH, // Push onto stack
            POP   // Pop from stack
        };

        enum CMP_IMM8
        {
            EQ_OQ = 0,     // Equal (ordered, non-signaling)
            LT_OS = 1,     // Less than (ordered, signaling)
            LE_OS = 2,     // Less than or equal (ordered, signaling
            UNORD_Q = 3,   // Unordered (non-signaling)
            NEQ_UQ = 4,    // Not-equal (unordered, non-signaling)
            NLT_US = 5,    // Not less than (unordered, signaling)
            NLE_US = 6,    // Not-less-than-or-equal (unordered, signaling)
            ORD_Q = 7,     // Ordered (non-signaling)
            EQ_US = 8,     // Equal (unordered, signaling)
            NGE_US = 9,    // Not-greater-than-or-equal (unordered, signaling)
            NGT_US = 10,   // Not-greater-than (unordered, signaling)
            FALSE_OQ = 11, // False (ordered, non-signaling)
            NEQ_OQ = 12,   // Not-equal (ordered, non-signaling)
            GE_OS = 13,    // Greater-than-or-equal (ordered, signaling)
            GT_OS = 14,    // Greater-than (ordered, signaling)
            TRUE_UQ = 15,  // True (unordered, non-signaling)
            EQ_OS = 16,    // Equal (ordered, signaling)
            LT_OQ = 17,    // Less-than (ordered, non-signaling)
            LE_OQ = 18,    // Less-than-or-equal (ordered, non
            UNORD_S = 19,  // Unordered (signaling)
            NEQ_US = 20,   // Not-equal (unordered, signaling)
            NLT_UQ = 21,   // Not-less-than (unordered, non-sign
            NLE_UQ = 22,   // Not-less-than-or-equal (unordered, non-signaling)
            ORD_S = 23,    // Ordered (signaling)
            EQ_UQ = 24,    // Equal (unordered, non-signaling)
            NGE_UQ = 25,   // Not-greater-than-or-equal (
            NGT_UQ = 26,   // Not-greater-than (unordered, non-signaling)
            FALSE_OS = 27, // False (ordered, signaling)
            NEQ_OS = 28,   // Not-equal (ordered, signaling)
            GE_OQ = 29,    // Greater-than-or-equal (ordered, non
            GT_OQ = 30,    // Greater-than (ordered, non-signaling)
            TRUE_S = 31    // True (signaling)
        };

        enum FPDataType
        {
            SINGLE_FLOAT,
            SINGLE_DOUBLE,
            PACKED_FLOAT,
            PACKED_DOUBLE
        };

        enum FuncReturnType
        {
            NO_VALUE,
            INT_VALUE,
            FLOAT_VALUE
        };

        // General Purpose Registers
        enum GPRegister
        {
            // volatile register (can be altered by a function call)
            RAX = 0b0000,
            // non-volatile register (mus be restored on return)
            RBX = 0b0011,
            // volatile register (can be altered by a function call)
            RCX = 0b0001,
            // volatile register (can be altered by a function call)
            RDX = 0b0010,
            // Stack pointer points to the position of the last item that was
            // pushed to the stack. The stack grows downwards so lower means
            // more elements in the stack. needs to be aligned to 16 bytes
            // non-volatile register (mus be restored on return)
            RSP = 0b0100,
            // base pointer points to the base of the current stack frame
            // non-volatile register (mus be restored on return)
            RBP = 0b0101,
            // non-volatile register (mus be restored on return)
            RSI = 0b0110,
            // non-volatile register (mus be restored on return)
            RDI = 0b0111,
            // volatile register (can be altered by a function call)
            R8 = 0b1000,
            // volatile register (can be altered by a function call)
            R9 = 0b1001,
            // volatile register (can be altered by a function call)
            R10 = 0b1010,
            // volatile register (can be altered by a function call)
            R11 = 0b1011,
            // non-volatile register (mus be restored on return)
            R12 = 0b1100,
            // non-volatile register (mus be restored on return)
            R13 = 0b1101,
            // non-volatile register (mus be restored on return)
            R14 = 0b1110,
            // non-volatile register (mus be restored on return)
            R15 = 0b1111
        };

        enum SpecialRegister
        {
            // Instruction pointer points to the instruction that is executed
            // next
            RIP,
            /**
              only lower 16 bits are used:
              - CF: Carry Flag
              - PF: Parity Flag (1 if an even number of bits was set to 1 in
              the result of the last operation)
              - AF: Adjust Flag
              - ZF: Zero Flag (1 if the result of the last operation was zero)
              - SF: Sign Flag (1 if the result of the last operation was a
              positive number)
              - TF: Trap Flag
              - IF: Interrupt Flag
              - DF: Direction Flag
              - OF: Overflow Flag
             */
            FLAGS,
            /**
             * controls the behaviour of some operations.
             */
            MXCSR,
        };

        /**
          floating point registers
         */
        enum FPRegister
        {
            MM0,  // volatile register (can be altered by a function call)
            MM1,  // volatile register (can be altered by a function call)
            MM2,  // volatile register (can be altered by a function call)
            MM3,  // volatile register (can be altered by a function call)
            MM4,  // volatile register (can be altered by a function call)
            MM5,  // volatile register (can be altered by a function call)
            MM6,  // non volatile register (mus be restored on return)
            MM7,  // non volatile register (mus be restored on return)
            MM8,  // non volatile register (mus be restored on return)
            MM9,  // non volatile register (mus be restored on return)
            MM10, // non volatile register (mus be restored on return)
            MM11, // non volatile register (mus be restored on return)
            MM12, // non volatile register (mus be restored on return)
            MM13, // non volatile register (mus be restored on return)
            MM14, // non volatile register (mus be restored on return)
            MM15, // non volatile register (mus be restored on return)
            __FP_REGISTER_COUNT
        };

        /**
          describes the bits of the specified register that should be used.
         */
        enum GPRegisterPart
        {
            LOWER8,
            HIGHER8,
            LOWER16,
            LOWER32,
            FULL64
        };

        /**
          describes the bits of the specified register that should be used.
         */
        enum FPRegisterPart
        {
            X,
            Y,
            // Z
        };

        class AssemblyBlock;
        class GPRegisterArgument;
        class FPRegisterArgument;
        class MemoryAccessArgument;
        class ConstantArgument;
        class JumpTargetArgument;

        /**
          An argument for an Instruction.
         */
        class OperationArgument
        {
        private:

        public:
            /**
             * checks if a register is used in this argument.
             *
             * \param reg the register to check
             * \return true if the register is used
             */
            DLLEXPORT virtual bool usesRegister(GPRegister reg) const;
            /**
             * checks if a register is used in this argument.
             *
             * \param reg the register to check
             * \return true if the register is used
             */
            DLLEXPORT virtual bool usesRegister(FPRegister reg) const;
            /**
             * replaces a register with another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             */
            DLLEXPORT virtual void replaceRegister(
                GPRegister oldReg, GPRegister newReg);
            /**
             * replaces a register with another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             */
            DLLEXPORT virtual void replaceRegister(
                FPRegister oldReg, FPRegister newReg);
            /**
             * adds a prefix to all jump labels in this argument to avoid
             * conflicts if the assembly block that contains this argument is
             * inlined into another block.
             *
             * \param labelPrefix the label prefix to add
             */
            DLLEXPORT virtual void addJumpLabelPrefix(Text labelPrefix);
            /**
             * \return the GPRegisterArgument or 0 if it is not a
             * GPRegisterArgument
             */
            DLLEXPORT const GPRegisterArgument* asGPRegisterArgument() const;
            /**
             * \return the GPRegisterArgument or 0 if it is not a
             * GPRegisterArgument
             */
            DLLEXPORT const MemoryAccessArgument*
            asMemoryAccessArgument() const;
            /**
             * \return the ConstantArgument or 0 if it is not a
             * ConstantArgument
             */
            DLLEXPORT const ConstantArgument* asConstantArgument() const;
            /**
             * \return the FPRegisterArgument or 0 if it is not a
             * FPRegisterArgument
             */
            DLLEXPORT const FPRegisterArgument* asFPRegisterArgument() const;
            /**
             * \return the JumpTargetArgument or 0 if it is not a
             * JumpTargetArgument
             */
            DLLEXPORT const JumpTargetArgument* asJumpTargetArgument() const;
        };

        /**
          Represents the usage of a GPRegister as an Instruction Argument.
         */
        class GPRegisterArgument : public OperationArgument
        {
        private:
            GPRegister reg;
            GPRegisterPart part;

        public:
            DLLEXPORT GPRegisterArgument(
                GPRegister reg, GPRegisterPart part = GPRegisterPart::FULL64);
            DLLEXPORT bool usesRegister(GPRegister reg) const override;
            DLLEXPORT void replaceRegister(
                GPRegister oldReg, GPRegister newReg) override;
            DLLEXPORT GPRegister getRegister() const;
            DLLEXPORT GPRegisterPart getPart() const;
        };

        /**
          Represents the usage of a FPRegister as an Instruction Argument.
         */
        class FPRegisterArgument : public OperationArgument
        {
        private:
            FPRegister reg;
            FPRegisterPart part;

        public:
            DLLEXPORT FPRegisterArgument(FPRegister reg, FPRegisterPart = X);
            DLLEXPORT bool usesRegister(FPRegister reg) const override;
            DLLEXPORT void replaceRegister(
                FPRegister oldReg, FPRegister newReg) override;
            DLLEXPORT FPRegister getRegister() const;
            DLLEXPORT FPRegisterPart getPart() const;
        };

        enum class MemoryBlockSize
        {
            // 8 bist
            BYTE = 1,
            // 16 bits
            WORD = 2,
            // 32 bits
            DWORD = 4,
            // 64 bits
            QWORD = 8,
            // 128 bits
            M128 = 16,
            // 256 bits
            M256 = 32,
            // 512 bits
            // M512 = 64
        };

        /**
          Represents the usage of a Memory Read Request as an Instruction
          Argument.
         */
        class MemoryAccessArgument : public OperationArgument
        {
        private:
            MemoryBlockSize blockSize; // size of the block to access (1,2,4,8)
            bool useAddressReg;
            GPRegister address;
            int offset; // offset from the address in the register
            GPRegister offsetReg;
            bool useOffsetReg;

        public:
            DLLEXPORT MemoryAccessArgument(MemoryBlockSize blockSize,
                GPRegister address,
                bool useAddressReg = true,
                int offset = 0,
                bool useOffsetReg = false,
                GPRegister offsetReg = RAX);
            DLLEXPORT bool usesRegister(GPRegister reg) const override;
            DLLEXPORT void replaceRegister(
                GPRegister oldReg, GPRegister newReg) override;
            DLLEXPORT bool isUsingAddressRegister() const;
            DLLEXPORT GPRegister getAddressRegister() const;
            DLLEXPORT int getOffset() const;
            DLLEXPORT bool isUsingOffsetRegister() const;
            DLLEXPORT GPRegister getOffsetRegister() const;
            DLLEXPORT MemoryBlockSize getBlockSize() const;
        };

        /**
          Represents the usage of a const value as an Instruction Argument.
         */
        class ConstantArgument : public OperationArgument
        {
        private:
            __int64 value;
            MemoryBlockSize size; // size in byte

        public:
            DLLEXPORT ConstantArgument(
                __int64 value, MemoryBlockSize size = MemoryBlockSize::QWORD);
            DLLEXPORT ConstantArgument(
                int value, MemoryBlockSize size = MemoryBlockSize::DWORD);
            DLLEXPORT ConstantArgument(
                short value, MemoryBlockSize size = MemoryBlockSize::WORD);
            DLLEXPORT ConstantArgument(
                char value, MemoryBlockSize size = MemoryBlockSize::BYTE);
            DLLEXPORT __int64 getValue() const;
            DLLEXPORT MemoryBlockSize getSize() const;
        };

        /**
          Represents the usage of a jump label as an Instruction Argument.
         */
        class JumpTargetArgument : public OperationArgument
        {
        private:
            Text name;

        public:
            DLLEXPORT JumpTargetArgument(Text name);
            DLLEXPORT void addJumpLabelPrefix(Text labelPrefix) override;
            const Text& getLabel() const;
        };

        /**
          Represents a single assembly instruction with its arguments.
         */
        class Instruction : public Framework::ReferenceCounter
        {
        private:
            Operation op;
            std::vector<OperationArgument*> args;
            std::vector<GPRegister> implicitReadGPs;
            std::vector<FPRegister> implicitReadFPs;
            std::vector<GPRegister> implicitWriteGPs;
            std::vector<FPRegister> implicitWriteFPs;

        public:
            DLLEXPORT Instruction(
                Operation op, std::initializer_list<OperationArgument*> params);
            DLLEXPORT Instruction(Operation op,
                std::initializer_list<OperationArgument*> params,
                std::initializer_list<GPRegister> implicitReadGPs,
                std::initializer_list<FPRegister> implicitReadFPs,
                std::initializer_list<GPRegister> implicitWriteGPs,
                std::initializer_list<FPRegister> implicitWriteFPs);
            DLLEXPORT ~Instruction();
            /**
             * checks if this instruction reads from a specified register.
             *
             * \param reg the register to check
             * \return true if the instruction reads from the register
             */
            DLLEXPORT bool writesToRegister(
                GPRegister reg, const AssemblyBlock* block) const;
            /**
             * checks if this instruction reads from a specified register.
             *
             * \param reg the register to check
             * \return true if the instruction reads from the register
             */
            DLLEXPORT bool writesToRegister(
                FPRegister reg, const AssemblyBlock* block) const;
            /**
             * checks if this instruction reads from a specified register.
             *
             * \param reg the register to check
             * \return true if the instruction reads from the register
             */
            DLLEXPORT bool readsFromRegister(
                GPRegister reg, const AssemblyBlock* block) const;
            /**
             * checks if this instruction reads from a specified register.
             *
             * \param reg the register to check
             * \return true if the instruction reads from the register
             */
            DLLEXPORT bool readsFromRegister(
                FPRegister reg, const AssemblyBlock* block) const;
            /**
             * checks if a register can be replaced by another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             * \return true if the replacement is possible
             */
            DLLEXPORT bool isReplacementPossible(GPRegister oldReg,
                GPRegister newReg,
                const AssemblyBlock* block) const;
            /**
             * checks if a register can be replaced by another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             * \return true if the replacement is possible
             */
            DLLEXPORT bool isReplacementPossible(FPRegister oldReg,
                FPRegister newReg,
                const AssemblyBlock* block) const;
            /**
             * replaces a register with another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             */
            DLLEXPORT void replaceRegister(
                GPRegister oldReg, GPRegister newReg);
            /**
             * replaces a register with another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             */
            DLLEXPORT void replaceRegister(
                FPRegister oldReg, FPRegister newReg);
            /**
             * adds a prefix to all jump labels in this instruction to avoid
             * conflicts if the assembly block that contains this instruction is
             * inlined into another block.
             *
             * \param labelPrefix the label prefix to add
             */
            DLLEXPORT void addJumpLabelPrefix(Text labelPrefix);
            /**
             * compiles this Instruction to macine code.
             *
             * \param machineCodeWriter the machine code will be written to this
             * writer
             * \param block the block that contains this instruction. needed to
             * resolve jump labels
             */
            DLLEXPORT void compile(StreamWriter* machineCodeWriter,
                const AssemblyBlock* block) const;
            /**
             * test if this instruction is valid.
             *
             * \param block the code block that contains this instruction
             * \return true if the instruction can be compiled
             */
            DLLEXPORT bool isValid(const AssemblyBlock* block) const;
            /**
             * calculates the bytes needed for this instruction in machine code.
             *
             * \return the bytes needed
             */
            DLLEXPORT int compiledSize(const AssemblyBlock* block) const;
            /**
             * \return the op code of this instruction
             */
            DLLEXPORT Operation getOperation() const;
            /**
             * \return true if the given label is defined by this operation
             */
            DLLEXPORT bool definesLabel(Text label) const;
            /**
             * \return the arguments of this instruction
             */
            DLLEXPORT const std::vector<OperationArgument*>&
            getArguments() const;
        };

        /**
          Represents a block of assembly instructions that can be compiled to
          machine code or inlined into another AssemblyBlock with addBlock(...).
         */
        class AssemblyBlock
        {
        private:
            RCArray<Instruction> instructions;
            int inlineIndex;
            void* compiledCode;

        public:
            DLLEXPORT AssemblyBlock();
            DLLEXPORT ~AssemblyBlock();
            /**
             * adds an instruction.
             *
             * \param instr the instruction to add.
             */
            DLLEXPORT void addInstruction(Instruction* instr);
            /**
             * defines a new jump target label.
             *
             * \param name the name of the label.
             */
            DLLEXPORT void defineJumpTarget(Text name);
            /**
             * adds a jump instruction that jumps to the next Instaruction after
             * the definition of the given label.
             *
             * \param jumpOp the op code of the jump iperation.
             * \param targetName the label to jump to.
             */
            DLLEXPORT void addJump(Operation jumpOp, Text targetName);
            /**
             * adds the value of the source register to the value of the target
             * register and stores the result in the target register. single
             * operation.
             *
             * \param target the target register
             * \param source the source register
             * \param part the register part
             */
            DLLEXPORT void addAddition(GPRegister target,
                GPRegister source,
                GPRegisterPart part = FULL64);
            /**
             * adds the given value to the given part of the target
             * register and stores the result in the given part of the target
             * register. single operation.
             *
             * \param target the target register
             * \param value the value to add
             * \param part the target register part
             */
            DLLEXPORT void addAddition(
                GPRegister target, char value, GPRegisterPart part = LOWER8);
            /**
             * adds the given value to the given part of the target
             * register and stores the result in the given part of the target
             * register. single operation.
             *
             * \param target the target register
             * \param value the value to add
             * \param part the target register part (only LOWER32 or FULL64 are
             * valid)
             */
            DLLEXPORT void addAddition(
                GPRegister target, int value, GPRegisterPart part = LOWER32);
            /**
             * adds the value of the source register to the value of the target
             * register and stores the result in the target register. single
             * operation.
             *
             * \param target the target register
             * \param source the source register
             * \param type the data type stored in the registers
             * \param part the register part
             */
            DLLEXPORT void addAddition(FPRegister target,
                FPRegister source,
                FPDataType type,
                FPRegisterPart part = Y);
            /**
             * subtracts the value of the target register from the value of the
             * source register and stores the result in the target register.
             * single operation.
             *
             * \param target the target register
             * \param source the source register
             * \param part the register part
             */
            DLLEXPORT void addSubtraction(GPRegister target,
                GPRegister source,
                GPRegisterPart part = FULL64);
            /**
             * subtracts the value of the target register from the value of the
             * source register and stores the result in the target register.
             * single operation.
             *
             * \param target the target register
             * \param source the source register
             * \param type the data type stored in the registers
             * \param part the register part
             */
            DLLEXPORT void addSubtraction(FPRegister target,
                FPRegister source,
                FPDataType type,
                FPRegisterPart part = Y);
            /**
             * multiplies the value of the source register to the value of the
             * target register and stores the result in the target register.
             * single operation.
             *
             * \param target the target register
             * \param source the source register
             * \param part the register part
             */
            DLLEXPORT void addMultiplication(GPRegister target,
                GPRegister source,
                GPRegisterPart part = FULL64);
            /**
             * multiplies the value of the source register to the value of the
             * target register and stores the result in the target register.
             * single operation.
             *
             * \param target the target register
             * \param source the source register
             * \param type the data type stored in the registers
             * \param part the register part
             */
            DLLEXPORT void addMultiplication(FPRegister target,
                FPRegister source,
                FPDataType type,
                FPRegisterPart part = Y);
            /**
             * devides the value of the target register by the value of the
             * source register and stores the result in the target register.
             * single operation.
             *
             * \param target the target register
             * \param source the source register
             * \param part the register part
             */
            DLLEXPORT void addDivision(GPRegister target,
                GPRegister source,
                GPRegisterPart part = FULL64);
            /**
             * devides the value of the target register by the value of the
             * source register and stores the result in the target register.
             * single operation.
             *
             * \param target the target register
             * \param source the source register
             * \param type the data type stored in the registers
             * \param part the register part
             */
            DLLEXPORT void addDivision(FPRegister target,
                FPRegister source,
                FPDataType type,
                FPRegisterPart part = Y);
            /**
             * performes bitwise AND without storing the result. only
             * SpecialRegister.FLAGS (SF, ZF, PF) is affected. Single operation.
             *
             * \param r1 the first value
             * \param r2 the second value
             * \param part the part of the values to test
             */
            DLLEXPORT void addTest(
                GPRegister r1, GPRegister r2, GPRegisterPart part = FULL64);
            /**
             * subtracts the value of r2 from r1 and sets FLAGS (CF, OF, SF, ZF,
             * AF, PF) according to the result. The result is discarded.
             *
             * \param r1 the first value
             * \param r2 the second value
             * \param part the part of the values to compare
             */
            DLLEXPORT void addCompare(
                GPRegister r1, GPRegister r2, GPRegisterPart part = FULL64);
            /**
             * subtracts the value from r1 and sets FLAGS (CF, OF, SF, ZF,
             * AF, PF) according to the result. The result is discarded.
             *
             * \param r1 the first value
             * \param value the value to subtract
             */
            DLLEXPORT void addCompare(GPRegister r1, char value);
            /**
             * subtracts the value from r1 and sets FLAGS (CF, OF, SF, ZF,
             * AF, PF) according to the result. The result is discarded.
             *
             * \param r1 the first value
             * \param value the value to subtract
             */
            DLLEXPORT void addCompare(GPRegister r1, short value);
            /**
             * subtracts the value from r1 and sets FLAGS (CF, OF, SF, ZF,
             * AF, PF) according to the result. The result is discarded.
             *
             * \param r1 the first value
             * \param value the value to subtract
             */
            DLLEXPORT void addCompare(GPRegister r1, int value);
            /**
             * subtracts the value of r2 from r1 and sets FLAGS (ZF, PF, CF).
             * the result is discarded
             *
             * UNORDERED: ZF,PF,CF := 111;
             * GREATER_THAN: ZF,PF,CF := 000;
             * LESS_THAN: ZF,PF,CF := 001;
             * EUAL: ZF,PF,CF := 100;
             *
             * \param r1 the first value
             * \param r2 the second value
             * \param type the datatype to compare. only SINGLE types are valid
             */
            DLLEXPORT void addCompare(
                FPRegister r1, FPRegister r2, FPDataType type);
            /**
             * computes bitwise AND for the values in source and target
             * register and stores the result in the target register. single
             * operation
             *
             * \param target the target register
             * \param source the source register
             * \param part the register part
             */
            DLLEXPORT void addAnd(GPRegister target,
                GPRegister source,
                GPRegisterPart part = FULL64);
            /**
             * computes bitwise AND for the values in source and target register
             * and stores the result in the target register. single operation
             *
             * \param target the target register
             * \param source the source register
             * \param part the register part
             */
            DLLEXPORT void addOr(GPRegister target,
                GPRegister source,
                GPRegisterPart part = FULL64);
            /**
             * writes the specified valueAddress pointer into a register.
             *
             * \param valueAddress the pointer to the value witch address should
             * be stored
             * \param target the register where the address should be stored
             */
            DLLEXPORT void addLoadValue(char* valueAddress, GPRegister target);
            /**
             * writes the specified valueAddress pointer into a register.
             *
             * \param valueAddress the pointer to the value witch address should
             * be stored
             * \param target the register where the address should be stored
             */
            DLLEXPORT void addLoadValue(short* valueAddress, GPRegister target);
            /**
             * writes the specified valueAddress pointer into a register.
             *
             * \param valueAddress the pointer to the value witch address should
             * be stored
             * \param target the register where the address should be stored
             */
            DLLEXPORT void addLoadValue(int* valueAddress, GPRegister target);
            /**
             * writes the specified valueAddress pointer into a register.
             *
             * \param valueAddress the pointer to the value witch address should
             * be stored
             * \param target the register where the address should be stored
             */
            DLLEXPORT void addLoadValue(
                __int64* valueAddress, GPRegister target);
            /**
             * loads the value at the address stored in addressRegister into the
             * target register. Single operation.
             *
             * \param addressRegister a register that contains a memory address
             * \param target the target register to store the value into
             * \param targetPart the part of the target register that will be
             * written to
             */
            DLLEXPORT void addLoadValue(GPRegister addressRegister,
                GPRegister target,
                GPRegisterPart targetPart,
                int offset = 0);
            /**
             * writes the specified valueAddress pointer into a register.
             *
             * \param valueAddress the pointer to the value witch address should
             * be stored
             * \param target the register where the address should be stored
             */
            DLLEXPORT void addLoadValue(
                float* valueAddress, FPRegister target, GPRegister temp = RAX);
            /**
             * writes the specified valueAddress pointer into a register.
             *
             * \param valueAddress the pointer to the value witch address should
             * be stored
             * \param target the register where the address should be stored
             */
            DLLEXPORT void addLoadValue(
                double* valueAddress, FPRegister target, GPRegister temp = RAX);

            /**
             * calls a function at a specified memory address.
             *
             * \param functionAddress pointet to the address of the function to
             * call
             */
            template<typename T>
            DLLEXPORT void addLoadAddress(T* addr, GPRegister temp = RAX)
            {
                instructions.add(new Instruction(MOV,
                    {new GPRegisterArgument(temp),
                        new ConstantArgument(
                            reinterpret_cast<__int64>(addr))}));
            }

            /**
             * moves the given value to LOWER8 bits of target register.
             *
             * \param target the register to move the value to
             * \param value the value to move
             */
            DLLEXPORT void addMoveValue(GPRegister target, char value);
            /**
             * moves the given value to LOWER16 bits of target register.
             *
             * \param target the register to move the value to
             * \param value the value to move
             */
            DLLEXPORT void addMoveValue(GPRegister target, short value);
            /**
             * moves the given value to LOWER32 bits of target register.
             *
             * \param target the register to move the value to
             * \param value the value to move
             */
            DLLEXPORT void addMoveValue(GPRegister target, int value);
            /**
             * moves the given value to LOWER64 bits of target register.
             *
             * \param target the register to move the value to
             * \param value the value to move
             */
            DLLEXPORT void addMoveValue(GPRegister target, __int64 value);
            /**
             * moves the given value to LOWER32 bits of target register.
             * The value needs to be temporarily pushed to the stack, so
             * addLoadValue should be used if performance is critical.
             *
             * \param target the register to move the value to
             * \param value the value to move
             * \param temp temporary register that can be used us store the
             * value
             */
            DLLEXPORT void addMoveValue(
                FPRegister target, float value, GPRegister temp = RAX);
            /**
             * moves the given value to LOWER64 bits of target register.
             * The value needs to be temporarily pushed to the stack, so
             * addLoadValue should be used if performance is critical.
             *
             * \param target the register to move the value to
             * \param value the value to move
             * \param temp temporary register that can be used us store the
             * value
             */
            DLLEXPORT void addMoveValue(
                FPRegister target, double value, GPRegister temp = RAX);
            /**
             * moves the value from source register to target register.
             *
             * \param target the register to move the value to
             * \param source the register to move the value from
             * \param part the part of the register to move
             */
            DLLEXPORT void addMoveValue(GPRegister target,
                GPRegister source,
                GPRegisterPart part = FULL64);
            /**
             * moves the value from source register to target register.
             *
             * \param target the register to move the value to
             * \param source the register to move the value from
             * \param part the part of the register to move
             */
            DLLEXPORT void addMoveValue(FPRegister target,
                FPRegister source,
                FPDataType type,
                FPRegisterPart part = Y);
            /**
             * converts a floating point value from source register into a
             * signed integer value and writes it into the target register.
             * Single operation.
             *
             * \param target the target register
             * \param source the source register
             * \param type the datatype of the source register. Only SINGLE
             * types are valid.
             * \param targetPart the register part to write the result into.
             * Only LOWER32 or FULL64 are valid.
             * \param round if true the value is rounded acourding to bits 13
             * and 14 of the MXCSR register value
             */
            DLLEXPORT void addConversion(GPRegister target,
                FPRegister source,
                FPDataType type,
                GPRegisterPart targetPart,
                bool round = 0);
            /**
             * converts a signed integer from source register into a floating
             * point value and writes it into the target register. Single
             * operation
             *
             * \param target the target register
             * \param source the source register
             * \param targetType the data type after the conversion. Only SINGLE
             * types are valid.
             * \param sourcePart the register part that contains the signed
             * integer. Only LOWER32 and FULL64 are valid.
             */
            DLLEXPORT void addConversion(FPRegister target,
                GPRegister source,
                FPDataType targetType,
                GPRegisterPart sourcePart);

            /**
             * calls a function at a specified memory address.
             *
             * \param functionAddress pointet to the address of the function to
             * call
             * \param gpParams must be the the every n-th entry of the register
             * list (RCX, RDX, R8, R9) for each n-th function parameter that is
             * of type integer
             * \param fpParams must be the the every n-th entry of the register
             * list (MM0, MM1, MM2, MM3) for each n-th function parameter that
             * is of type float/double
             * \param temp temporary register to store the address of the
             * function
             * \param bpTemp temporary register to store the stack pointer
             * before ligning the stack pointer to a 16 byte boundry. The stack
             * pointer will be automatically restored after the function call
             * using the value of this register. A non-volatile register must be
             * used.
             */
            DLLEXPORT void addCall(void* functionAddress,
                FuncReturnType returnType = NO_VALUE,
                std::initializer_list<GPRegister> gpParams = {},
                std::initializer_list<FPRegister> fpParams = {},
                GPRegister temp = RAX,
                GPRegister bpTemp = RBP);

            /**
             * calls a function at a specified memory address.
             *
             * \param functionAddress pointet to the address of the function to
             * call
             * \param gpParams must be the the every n-th entry of the register
             * list (RCX, RDX, R8, R9) for each n-th function parameter that is
             * of type integer
             * \param fpParams must be the the every n-th entry of the register
             * list (MM0, MM1, MM2, MM3) for each n-th function parameter that
             * is of type float/double
             * \param temp temporary register to store the address of the
             * function
             * \param bpTemp temporary register to store the stack pointer
             * before ligning the stack pointer to a 16 byte boundry. The stack
             * pointer will be automatically restored after the function call
             * using the value of this register. A non-volatile register must be
             * used.
             */
            template<typename T>
            DLLEXPORT void addMemberCall(T&& functionAddress,
                FuncReturnType returnType = NO_VALUE,
                std::initializer_list<GPRegister> gpParams = {},
                std::initializer_list<FPRegister> fpParams = {},
                GPRegister temp = RAX,
                GPRegister bpTemp = RBP)
            {
                addCall((void*&)functionAddress,
                    returnType,
                    gpParams,
                    fpParams,
                    temp,
                    bpTemp);
            }

            /**
             * returns from executing the compiled assembly function.
             */
            DLLEXPORT void addReturn();
            /**
             * pushes a register into the stack.
             *
             * \param reg the register to push
             * \param part the part of the register to push
             */
            DLLEXPORT void addPush(
                GPRegister reg, GPRegisterPart part = FULL64);
            /**
             * pops a value from the sack into a specified register.
             *
             * \param reg the register to store the popped value
             * \param part the part of the register to store the popped value
             */
            DLLEXPORT void addPop(GPRegister reg, GPRegisterPart part = FULL64);
            /**
             * pushes a register into the stack.
             *
             * \param reg the register to push
             * \param type the datatype of the register
             * \param part the part of the register
             */
            DLLEXPORT void addPush(
                FPRegister reg, FPDataType type, FPRegisterPart part = X);
            /**
             * pops a value from the sack into a specified register.
             *
             * \param reg the register to store the popped value
             * \param type the datatype of the register
             * \param part the part of the register
             */
            DLLEXPORT void addPop(
                FPRegister reg, FPDataType type, FPRegisterPart part = X);

        private:
            DLLEXPORT void addPop(RCArray<Instruction>& instructionList,
                FPRegister reg,
                FPDataType type,
                FPRegisterPart part = X);

        public:
            /**
             * copies the assembly code from a given block of assembly
             * instructions.
             *
             * \param block the block to inline
             * \param preservedGPRegisters the GP registers that should be
             * preserved during the inlined block. if a preserved register is
             * used in the block, it will be replaced by another register or
             * pushed to the stack if no free register is available.
             * \param preservedFPRegisters same as preservedGPRegisters but for
             * FP registers
             * \param preservedFPDataTypes data types of the preserved FP
             * registers used to push them to the stack if needed
             * \param blockResultGpReg if the register this pointer points to
             * should be preserved this pointer will be set to the register that
             * was selected to replace the register during execution of the
             * block. Ignored if 0.
             * \param blockResultFpReg same as blockResultGpReg but for FP
             * Registers
             */
            DLLEXPORT void addBlock(AssemblyBlock* block,
                std::initializer_list<GPRegister> preservedGPRegisters,
                std::initializer_list<FPRegister> preservedFPRegisters,
                std::initializer_list<FPDataType> preservedFPDataTypes,
                GPRegister* blockResultGpReg,
                FPRegister* blockResultFpReg);
            /**
             * checks if this block writes to a specified register.
             *
             * \param reg the register to check
             * \return true if the block writes to the register
             */
            DLLEXPORT bool writesToRegister(GPRegister reg) const;
            /**
             * checks if this block writes to a specified register.
             *
             * \param reg the register to check
             * \return true if the block writes to the register
             */
            DLLEXPORT bool writesToRegister(FPRegister reg) const;
            /**
             * checks if this block reads from a specified register.
             *
             * \param reg the register to check
             * \return true if the block reads from the register
             */
            DLLEXPORT bool readsFromRegister(GPRegister reg) const;
            /**
             * checks if this block reads from a specified register.
             *
             * \param reg the register to check
             * \return true if the block reads from the register
             */
            DLLEXPORT bool readsFromRegister(FPRegister reg) const;
            /**
             * checks if a register can be replaced by another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             * \return true if the replacement is possible
             */
            DLLEXPORT bool isReplacementPossible(
                GPRegister oldReg, GPRegister newReg) const;
            /**
             * checks if a register can be replaced by another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             * \return true if the replacement is possible
             */
            DLLEXPORT bool isReplacementPossible(
                FPRegister oldReg, FPRegister newReg) const;
            /**
             * replaces a register with another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             */
            DLLEXPORT void replaceRegister(
                GPRegister oldReg, GPRegister newReg);
            /**
             * replaces a register with another register.
             *
             * \param oldReg the register to replace
             * \param newReg the register to use instead
             */
            DLLEXPORT void replaceRegister(
                FPRegister oldReg, FPRegister newReg);
            /**
             * adds a prefix to all jump labels in this block to avoid conflicts
             * if this block is inlined into another block.
             *
             * \param labelPrefix the label prefix to add
             */
            DLLEXPORT void addJumpLabelPrefix(Text labelPrefix);
            /**
             * \return the instructions of this block
             */
            DLLEXPORT const RCArray<Instruction>& getInstructions() const;
            /**
             * performs optimisations to shorten the needed instructions.
             *
             */
            DLLEXPORT void optimize();
            /**
             * \return a pointer to a function that contains the compiled byte
             * code of this assembly block. and can be called directly with
             * reinterpret_cast<returnType(*)(parameterTypes...)>(compile())(parameters...)
             */
            DLLEXPORT void* compile();

            /**
             * \return a pointer to a function that contains the compiled byte
             * code of this assembly block. and can be called directly with
             */
            template<typename T> DLLEXPORT T compileToFunction()
            {
                return reinterpret_cast<T>(compile());
            }
        };
    } // namespace Assembly
} // namespace Framework