//
// Created by magino on 3. 3. 2020.
//

#ifndef RECEPTUREJSON_H
#define RECEPTUREJSON_H

#include <nlohmann/json.hpp>
#include <fstream>
#include <iostream>
#include "HeaderJson.h"
#include "PieceItemJson.h"
#include "ProcedureStepJson.h"
#include "WriteFormat.h"
#include "MqttPublisher.h"
#include "ProductionStateJson.h"

class ReceptureJson {
private:
    const int _rfidMemoryByteSize = 144;
    const int _sectorSize = 4;
    const int _userMemoryOffset = 4; // Indexing from 0

    MqttPublisher _publisher;

    void Split16bitsToBytes(std::vector<uint8_t>& data, uint16_t number) {
        data.push_back(number >> 8);
        data.push_back(number);
    }

    void Split32BitsToBytes(std::vector<uint8_t>& data, uint32_t number) {
        data.push_back(number >> 24);
        data.push_back(number >> 16);
        data.push_back(number >> 8);
        data.push_back(number);
    }

    void Split64BitsToBytes(std::vector<uint8_t>& data, uint64_t number) {
        uint8_t *p = (uint8_t *)&number;
        for(int i = 0; i < 8; i++) {
            data.push_back(p[i]);
        }
    }

    void SplitStringToBytes(std::vector<uint8_t>& data, std::string str, int size) {
        std::vector<uint8_t> convData(str.begin(), str.end());
        data.insert(data.end(), convData.begin(), convData.end());
        for(int i=0; i < (size - str.size()); ++i) {
            data.push_back('\0');
        }
    }

    void SplitFloatToBytes(std::vector<uint8_t>& data, float number) {
        uint8_t* amount = reinterpret_cast<uint8_t*>(&number);
        data.emplace_back(amount[0]);
        data.emplace_back(amount[1]);
        data.emplace_back(amount[2]);
        data.emplace_back(amount[3]);
    }

public:
    nlohmann::json inputJson;
    nlohmann::json outputJson;
    nlohmann::json clearRfidJson;

    HeaderJson header;
    std::vector<PieceItemJson> pieceItemList;
    std::vector<ProcedureStepJson> procedureStepList;
    ProductionStateJson productionState;

    ReceptureJson() = default;
    ~ReceptureJson() = default;

    ReceptureJson& LoadJson(std::string& jsonFileName) {
        try {
            std::ifstream ifs(jsonFileName);
            ifs >> inputJson;

        } catch (std::exception& e) {
            std::cout << "Error LoadJson - " << e.what() << std::endl;
        }
        return *this;
    }

    ReceptureJson& ParseData() {
        try {
            header = inputJson["Header"];

            auto jsonPieceItemList = inputJson["PieceItemList"];
            pieceItemList.reserve(jsonPieceItemList.size());
            //Parsing array of arrays into vector of bytes (unsigned char)
            for (auto const& item: jsonPieceItemList)
                pieceItemList.emplace_back(item);

            auto jsonProcedureStepList = inputJson["ProcedureStepList"];
            pieceItemList.reserve(jsonProcedureStepList.size());
            //Parsing array of arrays into vector of bytes (unsigned char)
            for (auto const& item: jsonProcedureStepList)
                procedureStepList.emplace_back(item);

            productionState = inputJson["ProductionState"];

        } catch(std::exception& e) {
            std::cout << "Error ParseData - " << e.what() << std::endl;
        }
        return *this;
    }

    ReceptureJson& CreateMqttMsg() {
        std::vector<uint8_t> data;

        // Header
        Split16bitsToBytes(data, header.recipeId);
        Split16bitsToBytes(data, header.recipeVersion);


        Split64BitsToBytes(data, header.releaseDateTime);
        Split64BitsToBytes(data, header.serialNumber);


        Split64BitsToBytes(data, header.orderDateTime);
        Split64BitsToBytes(data, header.dispenseDateTime);


        Split16bitsToBytes(data, header.status);
        data.emplace_back(header.pieceListLength);
        data.emplace_back(header.procedureLength);

        // PieceItemList
        for(auto &item: pieceItemList) {
            data.emplace_back(item.classa);
            data.emplace_back(item.definition);
            SplitFloatToBytes(data, item.amount);
            //Split64BitsToBytes(data, item.Reserve);
        }

        // ProcedureStepList
        for(auto &item: procedureStepList) {
            data.emplace_back(item.order);
            data.emplace_back(item.operation);
            SplitFloatToBytes(data, item.parA);
            SplitFloatToBytes(data, item.parB);
        }

        // ProductionState
        //data.emplace_back(ProductionState.State);
        //data.emplace_back(ProductionState.Step);
        Split16bitsToBytes(data, productionState.state);
        Split16bitsToBytes(data, productionState.step);
        Split64BitsToBytes(data, productionState.reservedActionCellId);
        Split64BitsToBytes(data, productionState.actionReservationId);
        Split64BitsToBytes(data, productionState.reservedTransportCellId);
        Split64BitsToBytes(data, productionState.transportReservationId);

        //SplitStringToBytes(data, ProductionState.ReservedCellAddress,10);

        // C Output Bytes
        for(auto &item: data)
            std::cout << std::to_string(item) << ',';

        std::cout << std::endl;

        // Creating Output Json Format
        auto jsonObjects = nlohmann::json::array();

        int sectorCount = data.size()/4;
        int rem = data.size()%4;

        for(auto i = 0; i < sectorCount; ++i) {
            WriteFormat element;
            std::cout << std::to_string(*data.begin()+(i*4)) << " -> " << std::to_string(*data.begin()+(i+1)*4) << std::endl;

            element.data.insert(element.data.begin(), data.begin()+(i*4), data.begin()+(i+1)*4);
            element.sector = (i+4);

            jsonObjects.emplace_back(element);
        }

        // Remainder of data.size() is not divided by 4 without the rest
        if(rem > 0) {
            WriteFormat element;
            element.data.insert(element.data.begin(), data.begin()+(sectorCount*4), data.end());
            element.sector = sectorCount+4;

            for(int i=0; i < (4 - rem); ++i)
                element.data.emplace_back('\0');

            jsonObjects.emplace_back(element);
            sectorCount = sectorCount+1;
        }
        sectorCount = sectorCount+4;
        for(auto i=sectorCount; i<40 ; ++i) {
            WriteFormat element;

            for (auto j = 0; j<4; ++j)
                element.data.emplace_back('\0');

            element.sector = i;

            jsonObjects.emplace_back(element);
        }

        outputJson["write_multi"] = jsonObjects;

        return *this;
    }

    ReceptureJson& ClearRfidMemoryMsg() {
        // Creating Output Json Format
        auto jsonObjects = nlohmann::json::array();

        for(auto i = 0; i < _rfidMemoryByteSize/_sectorSize ; ++i) {
            WriteFormat element;

            for (auto j = 0; j<4; ++j)
                element.data.emplace_back('\0');

            element.sector = (i+_userMemoryOffset);

            jsonObjects.emplace_back(element);
        }

        clearRfidJson["write_multi"] = jsonObjects;

        return *this;
    }

    ReceptureJson& ConnectMqtt() {
        _publisher.Connect();
        return *this;
    }

    ReceptureJson& DisconnectMqtt() {
        _publisher.Disconnect();
        return *this;
    }

    ReceptureJson& PublishClearMsgMultiSector() {
        //if (publisher.Connect() == 1) return *this;
        _publisher.SendMessage("spi/reader/data/write", clearRfidJson.dump());
        //publisher.Disconnect();
        return *this;
    }

    ReceptureJson& PublishReceptureMsgMultiSector() {
        //if (publisher.Connect() == 1) return *this;
        _publisher.SendMessage("spi/reader/data/write", outputJson.dump());
        //publisher.Disconnect();
        return *this;
    }

    ReceptureJson& PublishMqttMessageOneSector() {
        auto json_data = outputJson["write_multi"];
        //if (publisher.Connect() == 1) return *this;

        for (auto const& subMsg: json_data) {
            auto jsonObjects = nlohmann::json::array();
            jsonObjects.emplace_back(subMsg);
            nlohmann::json writeMsg;
            writeMsg["write"] = jsonObjects;
            _publisher.SendMessage("spi/reader/data/write", writeMsg.dump());
        }
        //publisher.Disconnect();
        return *this;
    }
};

#endif //RECEPTUREJSON_H