#include "IMTQHandler.h"
#include "OBSWConfig.h"

#include <fsfw/globalfunctions/CRC.h>
#include <fsfw/datapool/PoolReadGuard.h>


IMTQHandler::IMTQHandler(object_id_t objectId, object_id_t comIF, CookieIF * comCookie) :
        DeviceHandlerBase(objectId, comIF, comCookie), engHkDataset(this), calMtmMeasurementSet(
                this), rawMtmMeasurementSet(this) {
    if (comCookie == NULL) {
        sif::error << "IMTQHandler: Invalid com cookie" << std::endl;
    }
}

IMTQHandler::~IMTQHandler() {
}


void IMTQHandler::doStartUp(){
	if(mode == _MODE_START_UP){
	    //TODO: Set to MODE_ON again
		setMode(MODE_NORMAL);
		communicationStep = CommunicationStep::SELF_TEST;
	}
}

void IMTQHandler::doShutDown(){

}

ReturnValue_t IMTQHandler::buildNormalDeviceCommand(
		DeviceCommandId_t * id) {
    switch (communicationStep) {
    case CommunicationStep::SELF_TEST:
//        *id = IMTQ::SELF_TEST;
//TODO: Implementing self test command. On-hold because of issue with humidity in clean
//      room
        communicationStep = CommunicationStep::GET_ENG_HK_DATA;
        break;
    case CommunicationStep::GET_ENG_HK_DATA:
        *id = IMTQ::GET_ENG_HK_DATA;
        communicationStep = CommunicationStep::START_MTM_MEASUREMENT;
        break;
    case CommunicationStep::START_MTM_MEASUREMENT:
        *id = IMTQ::START_MTM_MEASUREMENT;
        communicationStep = CommunicationStep::GET_CAL_MTM_MEASUREMENT;
        break;
    case CommunicationStep::GET_CAL_MTM_MEASUREMENT:
        *id = IMTQ::GET_CAL_MTM_MEASUREMENT;
        communicationStep = CommunicationStep::GET_RAW_MTM_MEASUREMENT;
        break;
    case CommunicationStep::GET_RAW_MTM_MEASUREMENT:
        *id = IMTQ::GET_RAW_MTM_MEASUREMENT;
        communicationStep = CommunicationStep::GET_ENG_HK_DATA;
        break;
    default:
        sif::debug << "IMTQHandler::buildNormalDeviceCommand: Invalid communication step"
            << std::endl;
        break;
    }
    return buildCommandFromCommand(*id, NULL, 0);
}

ReturnValue_t IMTQHandler::buildTransitionDeviceCommand(
		DeviceCommandId_t * id){
	return HasReturnvaluesIF::RETURN_OK;
}

ReturnValue_t IMTQHandler::buildCommandFromCommand(
		DeviceCommandId_t deviceCommand, const uint8_t * commandData,
		size_t commandDataLen) {
	switch(deviceCommand) {
	case(IMTQ::START_ACTUATION_DIPOLE): {
        /* IMTQ expects low byte first */
        commandBuffer[0] = IMTQ::CC::START_ACTUATION_DIPOLE;
        commandBuffer[1] = *(commandData + 1);
        commandBuffer[2] = *(commandData);
        commandBuffer[3] = *(commandData + 3);
        commandBuffer[4] = *(commandData + 2);
        commandBuffer[5] = *(commandData + 5);
        commandBuffer[6] = *(commandData + 4);
        commandBuffer[7] = *(commandData + 7);
        commandBuffer[8] = *(commandData + 6);
        rawPacket = commandBuffer;
        rawPacketLen = 9;
        return RETURN_OK;
    }
	case(IMTQ::GET_ENG_HK_DATA): {
	    commandBuffer[0] = IMTQ::CC::GET_ENG_HK_DATA;
		rawPacket = commandBuffer;
		rawPacketLen = 1;
		return RETURN_OK;
	}
	case(IMTQ::GET_COMMANDED_DIPOLE): {
	    commandBuffer[0] = IMTQ::CC::GET_COMMANDED_DIPOLE;
		rawPacket = commandBuffer;
		rawPacketLen = 1;
		return RETURN_OK;
	}
	case(IMTQ::START_MTM_MEASUREMENT): {
	    commandBuffer[0] = IMTQ::CC::START_MTM_MEASUREMENT;
		rawPacket = commandBuffer;
		rawPacketLen = 1;
		return RETURN_OK;
	}
	case(IMTQ::GET_CAL_MTM_MEASUREMENT): {
	    commandBuffer[0] = IMTQ::CC::GET_CAL_MTM_MEASUREMENT;
		rawPacket = commandBuffer;
		rawPacketLen = 1;
		return RETURN_OK;
	}
	case(IMTQ::GET_RAW_MTM_MEASUREMENT): {
	    commandBuffer[0] = IMTQ::CC::GET_RAW_MTM_MEASUREMENT;
		rawPacket = commandBuffer;
		rawPacketLen = 1;
		return RETURN_OK;
	}
	default:
		return DeviceHandlerIF::COMMAND_NOT_IMPLEMENTED;
	}
	return HasReturnvaluesIF::RETURN_FAILED;
}

void IMTQHandler::fillCommandAndReplyMap() {
    this->insertInCommandAndReplyMap(IMTQ::START_ACTUATION_DIPOLE, 1, nullptr,
            IMTQ::SIZE_STATUS_REPLY);
    this->insertInCommandAndReplyMap(IMTQ::GET_ENG_HK_DATA, 1, &engHkDataset,
            IMTQ::SIZE_ENG_HK_DATA_REPLY);
    this->insertInCommandAndReplyMap(IMTQ::GET_COMMANDED_DIPOLE, 1, nullptr,
            IMTQ::SIZE_GET_COMMANDED_DIPOLE_REPLY);
    this->insertInCommandAndReplyMap(IMTQ::START_MTM_MEASUREMENT, 1, nullptr,
            IMTQ::SIZE_STATUS_REPLY);
    this->insertInCommandAndReplyMap(IMTQ::GET_CAL_MTM_MEASUREMENT, 1, &calMtmMeasurementSet,
            IMTQ::SIZE_GET_CAL_MTM_MEASUREMENT);
    this->insertInCommandAndReplyMap(IMTQ::GET_RAW_MTM_MEASUREMENT, 1, &rawMtmMeasurementSet,
            IMTQ::SIZE_GET_RAW_MTM_MEASUREMENT);
}

ReturnValue_t IMTQHandler::scanForReply(const uint8_t *start,
        size_t remainingSize, DeviceCommandId_t *foundId, size_t *foundLen) {

    ReturnValue_t result = RETURN_OK;

	switch(*start) {
	case(IMTQ::CC::START_ACTUATION_DIPOLE):
        *foundLen = IMTQ::SIZE_STATUS_REPLY;
        *foundId = IMTQ::START_ACTUATION_DIPOLE;
		break;
	case(IMTQ::CC::START_MTM_MEASUREMENT):
        *foundLen = IMTQ::SIZE_STATUS_REPLY;
        *foundId = IMTQ::START_MTM_MEASUREMENT;
		break;
	case(IMTQ::CC::GET_ENG_HK_DATA):
        *foundLen = IMTQ::SIZE_ENG_HK_DATA_REPLY;
        *foundId = IMTQ::GET_ENG_HK_DATA;
		break;
	case(IMTQ::CC::GET_COMMANDED_DIPOLE):
        *foundLen = IMTQ::SIZE_GET_COMMANDED_DIPOLE_REPLY;
        *foundId = IMTQ::GET_COMMANDED_DIPOLE;
		break;
	case(IMTQ::CC::GET_CAL_MTM_MEASUREMENT):
        *foundLen = IMTQ::SIZE_GET_CAL_MTM_MEASUREMENT;
        *foundId = IMTQ::GET_CAL_MTM_MEASUREMENT;
		break;
	case(IMTQ::CC::GET_RAW_MTM_MEASUREMENT):
        *foundLen = IMTQ::SIZE_GET_RAW_MTM_MEASUREMENT;
        *foundId = IMTQ::GET_RAW_MTM_MEASUREMENT;
		break;
	default:
        sif::debug << "IMTQHandler::scanForReply: Reply contains invalid command code" << std::endl;
        result = IGNORE_REPLY_DATA;
		break;
	}

    return result;
}

ReturnValue_t IMTQHandler::interpretDeviceReply(DeviceCommandId_t id,
        const uint8_t *packet) {

    ReturnValue_t result = RETURN_OK;

    result = parseStatusByte(packet);

    if (result != RETURN_OK) {
        return result;
    }

    switch (id) {
    case (IMTQ::START_ACTUATION_DIPOLE):
    case (IMTQ::START_MTM_MEASUREMENT):
        /* Replies only the status byte which is already handled with parseStatusByte */
        break;
    case (IMTQ::GET_ENG_HK_DATA):
        fillEngHkDataset(packet);
        break;
    case (IMTQ::GET_COMMANDED_DIPOLE):
        handleGetCommandedDipoleReply(packet);
        break;
    case (IMTQ::GET_CAL_MTM_MEASUREMENT):
        fillCalibratedMtmDataset(packet);
        break;
    case (IMTQ::GET_RAW_MTM_MEASUREMENT):
        fillRawMtmDataset(packet);
        break;
    default: {
        sif::debug << "IMTQHandler::interpretDeviceReply: Unknown device reply id" << std::endl;
		return DeviceHandlerIF::UNKNOWN_DEVICE_REPLY;
	}
	}

	return RETURN_OK;
}

void IMTQHandler::setNormalDatapoolEntriesInvalid(){

}

uint32_t IMTQHandler::getTransitionDelayMs(Mode_t modeFrom, Mode_t modeTo){
    return 500;
}

ReturnValue_t IMTQHandler::initializeLocalDataPool(localpool::DataPool& localDataPoolMap,
        LocalDataPoolManager& poolManager) {

    /** Entries of engineering housekeeping dataset */
    localDataPoolMap.emplace(IMTQ::DIGITAL_VOLTAGE_MV, new PoolEntry<uint16_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::ANALOG_VOLTAGE_MV, new PoolEntry<uint16_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::DIGITAL_CURRENT, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::ANALOG_CURRENT, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::COIL_X_CURRENT, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::COIL_Y_CURRENT, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::COIL_Z_CURRENT, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::COIL_X_TEMPERATURE, new PoolEntry<uint16_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::COIL_Y_TEMPERATURE, new PoolEntry<uint16_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::COIL_Z_TEMPERATURE, new PoolEntry<uint16_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::MCU_TEMPERATURE, new PoolEntry<uint16_t>( { 0 }));

    /** Entries of calibrated MTM measurement dataset */
    localDataPoolMap.emplace(IMTQ::MTM_CAL_X, new PoolEntry<uint32_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::MTM_CAL_Y, new PoolEntry<uint32_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::MTM_CAL_Z, new PoolEntry<uint32_t>( { 0 }));
    localDataPoolMap.emplace(IMTQ::ACTUATION_CAL_STATUS, new PoolEntry<uint8_t>( { 0 }));

    /** Entries of raw MTM measurement dataset */
    localDataPoolMap.emplace(IMTQ::MTM_RAW_X, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::MTM_RAW_Y, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::MTM_RAW_Z, new PoolEntry<float>( { 0 }));
    localDataPoolMap.emplace(IMTQ::ACTUATION_RAW_STATUS, new PoolEntry<uint8_t>( { 0 }));

    return HasReturnvaluesIF::RETURN_OK;
}

ReturnValue_t IMTQHandler::parseStatusByte(const uint8_t* packet) {
    uint8_t cmdErrorField = *(packet + 1) & 0xF;
    switch (cmdErrorField)  {
    case 0:
        return RETURN_OK;
    case 1:
        sif::error << "IMTQHandler::parseStatusByte: Command rejected without reason" << std::endl;
        return REJECTED_WITHOUT_REASON;
    case 2:
        sif::error << "IMTQHandler::parseStatusByte: Command has invalid command code" << std::endl;
        return INVALID_COMMAND_CODE;
    case 3:
        sif::error << "IMTQHandler::parseStatusByte: Command has missing parameter" << std::endl;
        return PARAMETER_MISSING;
    case 4:
        sif::error << "IMTQHandler::parseStatusByte: Command has invalid parameter" << std::endl;
        return PARAMETER_INVALID;
    case 5:
        sif::error << "IMTQHandler::parseStatusByte: CC unavailable" << std::endl;
        return CC_UNAVAILABLE;
    case 7:
        sif::error << "IMTQHandler::parseStatusByte: IMQT replied internal processing error"
                << std::endl;
        return INTERNAL_PROCESSING_ERROR;
    default:
        sif::error << "IMTQHandler::parseStatusByte: CMD Error field contains unknown error code "
            << cmdErrorField << std::endl;
        return CMD_ERR_UNKNOWN;
    }
}

void IMTQHandler::fillEngHkDataset(const uint8_t* packet) {
    uint8_t offset = 2;
    engHkDataset.digitalVoltageMv = *(packet + offset + 1) << 8 | *(packet + offset);
    offset += 2;
    engHkDataset.analogVoltageMv = *(packet + offset + 1) << 8 | *(packet + offset);
    offset += 2;
    engHkDataset.digitalCurrentmA = (*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1;
    offset += 2;
    engHkDataset.analogCurrentmA = (*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1;
    offset += 2;
    engHkDataset.coilXCurrentmA = (*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1;
    offset += 2;
    engHkDataset.coilYCurrentmA = (*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1;
    offset += 2;
    engHkDataset.coilZCurrentmA = (*(packet + offset + 1) << 8 | *(packet + offset)) * 0.1;
    offset += 2;
    engHkDataset.coilXTemperature = (*(packet + offset + 1) << 8 | *(packet + offset));
    offset += 2;
    engHkDataset.coilYTemperature = (*(packet + offset + 1) << 8 | *(packet + offset));
    offset += 2;
    engHkDataset.coilZTemperature = (*(packet + offset + 1) << 8 | *(packet + offset));
    offset += 2;
    engHkDataset.mcuTemperature = (*(packet + offset + 1) << 8 | *(packet + offset));

#if OBSW_VERBOSE_LEVEL >= 1 && IMQT_DEBUG == 1
    sif::info << "IMTQ digital voltage: " << engHkDataset.digitalVoltageMv << " mV" << std::endl;
    sif::info << "IMTQ analog voltage: " << engHkDataset.analogVoltageMv << " mV" << std::endl;
    sif::info << "IMTQ digital current: " << engHkDataset.digitalCurrentmA << " mA" << std::endl;
    sif::info << "IMTQ analog current: " << engHkDataset.analogCurrentmA << " mA" << std::endl;
    sif::info << "IMTQ coil X current: " << engHkDataset.coilXCurrentmA << " mA" << std::endl;
    sif::info << "IMTQ coil Y current: " << engHkDataset.coilYCurrentmA << " mA" << std::endl;
    sif::info << "IMTQ coil Z current: " << engHkDataset.coilZCurrentmA << " mA" << std::endl;
    sif::info << "IMTQ coil X temperature: " << engHkDataset.coilXTemperature << " °C"
            << std::endl;
    sif::info << "IMTQ coil Y temperature: " << engHkDataset.coilYTemperature << " °C"
            << std::endl;
    sif::info << "IMTQ coil Z temperature: " << engHkDataset.coilZTemperature << " °C"
            << std::endl;
    sif::info << "IMTQ coil MCU temperature: " << engHkDataset.mcuTemperature << " °C"
            << std::endl;
#endif
}

void IMTQHandler::setModeNormal() {
    mode = MODE_NORMAL;
}

void IMTQHandler::handleDeviceTM(const uint8_t* data, size_t dataSize, DeviceCommandId_t replyId) {

    if (wiretappingMode == RAW) {
        /* Data already sent in doGetRead() */
        return;
    }

    DeviceReplyMap::iterator iter = deviceReplyMap.find(replyId);
    if (iter == deviceReplyMap.end()) {
        sif::debug << "IMTQHandler::handleDeviceTM: Unknown reply id" << std::endl;
        return;
    }
    MessageQueueId_t queueId = iter->second.command->second.sendReplyTo;

    if (queueId == NO_COMMANDER) {
        return;
    }

    ReturnValue_t result = actionHelper.reportData(queueId, replyId, data, dataSize);
    if (result != RETURN_OK) {
        sif::debug << "IMTQHandler::handleDeviceTM: Failed to report data" << std::endl;
        return;
    }
}

void IMTQHandler::handleGetCommandedDipoleReply(const uint8_t* packet) {
    uint8_t tmData[6];
    /* Switching endianess of received dipole values */
    tmData[0] = *(packet + 3);
    tmData[1] = *(packet + 2);
    tmData[2] = *(packet + 5);
    tmData[3] = *(packet + 4);
    tmData[4] = *(packet + 7);
    tmData[5] = *(packet + 6);
    handleDeviceTM(tmData, sizeof(tmData), IMTQ::GET_COMMANDED_DIPOLE);
}

void IMTQHandler::fillCalibratedMtmDataset(const uint8_t* packet) {
    int8_t offset = 2;
    calMtmMeasurementSet.mtmXnT = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16
            | *(packet + offset + 1) << 8 | *(packet + offset);
    offset += 4;
    calMtmMeasurementSet.mtmYnT = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16
            | *(packet + offset + 1) << 8 | *(packet + offset);
    offset += 4;
    calMtmMeasurementSet.mtmZnT = *(packet + offset + 3) << 24 | *(packet + offset + 2) << 16
            | *(packet + offset + 1) << 8 | *(packet + offset);
    offset += 4;
    calMtmMeasurementSet.coilActuationStatus = (*(packet + offset + 3) << 24)
            | (*(packet + offset + 2) << 16) | (*(packet + offset + 1) << 8) | (*(packet + offset));
#if OBSW_VERBOSE_LEVEL >= 1 && IMQT_DEBUG == 1
    sif::info << "IMTQ calibrated MTM measurement X: " << calMtmMeasurementSet.mtmXnT << " nT"
            << std::endl;
    sif::info << "IMTQ calibrated MTM measurement Y: " << calMtmMeasurementSet.mtmYnT << " nT"
            << std::endl;
    sif::info << "IMTQ calibrated MTM measurement Z: " << calMtmMeasurementSet.mtmZnT << " nT"
            << std::endl;
    sif::info << "IMTQ coil actuation status during MTM measurement: "
            << (unsigned int) calMtmMeasurementSet.coilActuationStatus.value << std::endl;
#endif
}

void IMTQHandler::fillRawMtmDataset(const uint8_t* packet) {
    int8_t offset = 2;
    rawMtmMeasurementSet.mtmXnT = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16
            | *(packet + offset + 1) << 8 | *(packet + offset)) * 7.5;
    offset += 4;
    rawMtmMeasurementSet.mtmYnT = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16
            | *(packet + offset + 1) << 8 | *(packet + offset)) * 7.5;
    offset += 4;
    rawMtmMeasurementSet.mtmZnT = (*(packet + offset + 3) << 24 | *(packet + offset + 2) << 16
            | *(packet + offset + 1) << 8 | *(packet + offset)) * 7.5;
    offset += 4;
    rawMtmMeasurementSet.coilActuationStatus = (*(packet + offset + 3) << 24)
            | (*(packet + offset + 2) << 16) | (*(packet + offset + 1) << 8) | (*(packet + offset));
#if OBSW_VERBOSE_LEVEL >= 1 && IMQT_DEBUG == 1
    sif::info << "IMTQ raw MTM measurement X: " << rawMtmMeasurementSet.mtmXnT << " nT"
            << std::endl;
    sif::info << "IMTQ raw MTM measurement Y: " << rawMtmMeasurementSet.mtmYnT << " nT"
            << std::endl;
    sif::info << "IMTQ raw MTM measurement Z: " << rawMtmMeasurementSet.mtmZnT << " nT"
            << std::endl;
    sif::info << "IMTQ coil actuation status during MTM measurement: "
            << (unsigned int) rawMtmMeasurementSet.coilActuationStatus.value << std::endl;
#endif
}