namespace cisb
{
	const char* getInstructionTypeAsString(InstructionType instructionType)
	{
		switch(instructionType)
		{
			case InstructionType::NOP: 			return "nop";
			case InstructionType::MOV_I32: 		return "mov_i32";
			case InstructionType::MOV_F32: 		return "mov_f32";
			case InstructionType::MOV_F64: 		return "mov_f64";
			case InstructionType::ADD_I32: 		return "add_i32";
			case InstructionType::ADD_F32: 		return "add_f32";
			case InstructionType::ADD_F64: 		return "add_f64";
			case InstructionType::ADD_RI32: 	return "add_ri32";
			case InstructionType::EQ_I32: 		return "eq_i32";
            case InstructionType::LT_I32: 		return "lt_i32";
            case InstructionType::GT_I32: 		return "gt_i32";
			case InstructionType::JZ: 			return "jz";
			case InstructionType::JMP: 			return "jmp";
            case InstructionType::PUSH_REG_I32: return "push_reg_i32";
            case InstructionType::PUSH_I32:     return "push_i32";
            case InstructionType::CALL_EXTERN:  return "call_extern";
            case InstructionType::CALL_INTERN:  return "call_intern";
			default:
				assert(false);
				return "[Unknown]";
		}
	}

	InstructionOperandsType getInstructionOperandsType(InstructionType instructionType)
	{
		switch(instructionType)
		{
			case InstructionType::NOP:
				return InstructionOperandsType::INS;
			case InstructionType::MOV_I32:
			case InstructionType::MOV_F32:
			case InstructionType::MOV_F64:
			case InstructionType::ADD_I32:
			case InstructionType::ADD_F32:
			case InstructionType::ADD_F64:
			case InstructionType::JZ:
			case InstructionType::EQ_I32:
            case InstructionType::LT_I32:
            case InstructionType::GT_I32:
				return InstructionOperandsType::REG_IMM;
			case InstructionType::ADD_RI32:
				return InstructionOperandsType::REG_REG;
			case InstructionType::JMP:
            case InstructionType::PUSH_I32:
				return InstructionOperandsType::IMM;
            case InstructionType::PUSH_REG_I32:
                return InstructionOperandsType::REG;
            case InstructionType::CALL_EXTERN:
            case InstructionType::CALL_INTERN:
                return InstructionOperandsType::NUM;
			default:
				assert(false);
				return InstructionOperandsType::UNKNOWN;
		}
	}
	
	Instruction constructInstruction(InstructionType instructionType)
	{
		u32 result = 0;
		result |= ((u8)instructionType << 24);
		return Instruction(result);
	}
	
	Instruction constructInstruction(InstructionType instructionType, u8 reg)
	{
		u32 result = 0;
		result |= ((u8)instructionType << 24);
        result |= (reg << 16);
		return Instruction(result);
	}
	
	Instruction constructInstruction(InstructionType instructionType, u8 reg, u16 numberIndex)
	{
		u32 result = 0;
		result |= ((u8)instructionType << 24);
		result |= (reg << 16);
		result |= numberIndex;
		return Instruction(result);
	}

	Instruction constructInstructionNoReg(InstructionType instructionType, u16 numberIndex)
	{
		u32 result = 0;
		result |= ((u8)instructionType << 24);
		result |= ((u8)0 << 16);
		result |= numberIndex;
		return Instruction(result);
	}
	
	Instruction constructBranchingInstruction(InstructionType instructionType, u16 numberIndex)
	{
		return constructInstructionNoReg(instructionType, numberIndex);
	}
	
	Instruction constructInstructionNoReg(InstructionType instructionType, u32 number)
	{
		u32 result = 0;
		result |= ((u8)instructionType << 24);
		result |= (number & 0x00FFFFFF);
		return Instruction(result);
	}

	InstructionType Instruction::getType() const
	{
		return (InstructionType)(instruction >> 24);
	}

	u8 Instruction::getRegister() const
	{
		return (u8)((instruction & 0x00FF0000) >> 16);
	}

	u16 Instruction::getNumberIndex() const
	{
		return (u16)(instruction & 0x0000FFFF);
	}
	
	u32 Instruction::getNumber() const
	{
        return (u32)(instruction & 0x00FFFFFF);
    }

	u8 Instruction::getDstReg() const
	{
		return getRegister();
	}

	u8 Instruction::getSrcReg() const
	{
		return (u8)getNumberIndex();
	}

	u32 Instruction::data() const
	{
		return instruction;
	}

	void Instruction::setNumberIndex(u16 index)
	{
		instruction &= 0xFFFF0000;
		instruction |= index;
	}

	std::string Instruction::toString(BytecodeWriter *bytecodeWriter) const
	{
		std::string result;
		result.reserve(16);
		result = getInstructionTypeAsString(getType());

		switch(getInstructionOperandsType(getType()))
		{
			case InstructionOperandsType::UNKNOWN:
			{
				result += " UNKNOWN";
				break;
			}
			case InstructionOperandsType::REG_IMM:
			{
				result += " r";
				i32 instructionNumber = bytecodeWriter->getNumberByIndex(getNumberIndex()).unwrap();
				result += std::to_string(getRegister()) + ", " + std::to_string(instructionNumber);
				break;
			}
			case InstructionOperandsType::REG_REG:
			{
				result += " r";
				result += std::to_string(getDstReg()) + ", r" + std::to_string(getSrcReg());
				break;
			}
			case InstructionOperandsType::IMM:
			{
				result += " ";
                i64 instructionNumber = bytecodeWriter->getNumberByIndex(getNumberIndex()).unwrap();
				result += std::to_string(instructionNumber);
				break;
			}
			case InstructionOperandsType::INS:
			{
				break;
			}
            case InstructionOperandsType::REG:
            {
                result += " r";
                result += std::to_string(getRegister());
                break;
            }
            case InstructionOperandsType::NUM:
            {
                result += " ";
                result += std::to_string(getNumber());
                break;
            }
		}
		return result;
	}
}
