#include "CreatePipes.h"
#include <memory>
#include <iostream>
#include "json.hpp"
#include <chrono>
#include <thread>
#include "file_api.hpp"

#define ECHO_MODULE_ID 0x00000040 /// Идентификатор тестового модуля
#define ECHO_SUBMOD_ID 0x00000140 /// Идентификатор тестового подмодуля
#define ECHO_INPUT_DATA_SIZE 8
#define ECHO_OUTPUT_DATA_SIZE 8
#define ECHO_PARAMETER_GAIN_IDX 125 /// Индекс параметра Gain для подмодуля ECHO

using json = nlohmann::json;

uint32_t Echo_Gain = 0;

struct EchoData {
    float  data_f;
    uint32_t data_i;
};

union EchoDataMem
{
    EchoData data;
    uint8_t mem[sizeof(EchoData)];
};

EchoDataMem Echo_inpCycData;
EchoDataMem Echo_outCycData;

uint8_t be_data_inp[sizeof(EchoData)];

void endian_convert_32(uint8_t * p_data)
{
    uint8_t tmp = p_data[1];
    p_data[1] = p_data[2];
    p_data[2] = tmp;
    tmp = p_data[0];
    p_data[0] = p_data[3];
    p_data[3] = tmp;
}

/** Поток обработки входящих данных от именнованого канала. */
std::shared_ptr< std::istream > p_input_stream_;
        
/** Поток для отправки данных в именнованй канал. */
std::shared_ptr< std::ostream > p_output_stream_;

using json = nlohmann::json;

int main(int argc, char * argv[])
{
    json j_cfg = json::parse(fileapi_read_file("sample_app_echo_config.json"));

    std::filesystem::path _input_path{j_cfg["app_settings"]["app_inp_pipe_name"].get<std::string>()}; /// Выходной канал профинет, входной канал апп
    std::filesystem::path _output_path{j_cfg["app_settings"]["app_out_pipe_name"].get<std::string>()}; /// Входной канал профинет, входной канал апп

    auto streams = itr::create_pipes( _input_path, _output_path, std::cout );
    
    p_input_stream_ = std::move( std::get<0>( streams ) ) ;
    p_output_stream_ = std::move( std::get<1>( streams ) );

    json j_get_flags;
    j_get_flags["jsonrpc"] = "2.0";
    j_get_flags["method"] = "readFlags";
    j_get_flags["params"];
    j_get_flags["id"] = 0;

    json j_get_echo_param_gain;
    j_get_echo_param_gain["jsonrpc"] = "2.0";
    j_get_echo_param_gain["method"] = "readSubmoduleParameter";
    j_get_echo_param_gain["params"] = json::array();
    json j_data;
    j_data["module_id"] = ECHO_MODULE_ID;
    j_data["submodule_id"] = ECHO_SUBMOD_ID;
    j_data["param_index"] = ECHO_PARAMETER_GAIN_IDX;
    j_get_echo_param_gain["params"].push_back(j_data);
    j_get_echo_param_gain["id"] = 0;

    json j_get_echo_data;
    j_get_echo_data["jsonrpc"] = "2.0";
    j_get_echo_data["method"] = "readCyclicIoData";
    j_get_echo_data["params"] = json::array();
    j_data.clear();
    j_data["module_id"] = ECHO_MODULE_ID;
    j_data["submodule_id"] = ECHO_SUBMOD_ID;
    j_get_echo_data["params"].push_back(j_data);
    j_get_echo_data["id"] = 0;
    
    json j_set_echo_data;
    
    j_set_echo_data["jsonrpc"] = "2.0";
    j_set_echo_data["method"] = "writeCyclicIoData";
    j_set_echo_data["params"] = json::array();
    j_set_echo_data["id"] = 0;

    std::cout << "Starting...." << std::endl;
    std::string answer_str;
    for(;;)
    {
        answer_str.clear();

        std::this_thread::sleep_for(std::chrono::milliseconds(500));

        std::string request{j_get_flags.dump()};
        ///Запрос:
        *p_output_stream_ << request << std::endl;

        ///Ждем ответ:
        std::getline( *p_input_stream_.get( ), answer_str);

        json j_answ = json::parse(answer_str);

        if (!j_answ.contains("result"))
        {
            std::cout << "Error: result" << std::endl;
            continue;
        }

        json& j_res = j_answ["result"];

        if (!j_res.contains("EVENT_CONNECTION_ESTABLISHED"))
        {
            std::cout << "Error: EVENT_CONNECTION_ESTABLISHED" << std::endl;
            continue;
        }
        
        if (j_res["EVENT_CONNECTION_ESTABLISHED"].get<bool>() == true)
        {
            std::cout << "EVENT_CONNECTION_ESTABLISHED: true" << std::endl;
        }

        if (j_res["EVENT_CONNECTION_ABORT"].get<bool>() == true)
        {
            std::cout << "EVENT_CONNECTION_ABORT: true" << std::endl;
        }


        if (j_res["EVENT_NEW_PARAM_DATA"].get<bool>() == true)
        {
            answer_str.clear();
            std::cout << "EVENT_NEW_PARAM_DATA: true" << std::endl;
            *p_output_stream_ << j_get_echo_param_gain.dump() << std::endl;
            std::getline( *p_input_stream_.get( ), answer_str);
            json j_answ_par = json::parse(answer_str);
            std::vector<uint8_t> in;

            for (auto& jx : j_answ_par["result"])
            {
                uint32_t module_id; 
                uint32_t submodule_id;
                uint32_t param_idx;

                module_id = jx["module_id"].get<int>();
                submodule_id = jx["submodule_id"].get<int>();
                param_idx = jx["param_index"].get<int>();

                if ((module_id != ECHO_MODULE_ID) || (submodule_id != ECHO_SUBMOD_ID))
                {
                    std::cout << "Error id" << std::endl;
                    continue;
                }

                if (param_idx != ECHO_PARAMETER_GAIN_IDX)
                {
                    std::cout << "Error param idx" << std::endl;
                    continue;
                }
                in.clear();
                in = jx["data"]["bytes"].get<std::vector<uint8_t>>();

                if (in.size() == 4)
                {
                    std::copy(in.begin(), in.end(), (uint8_t*)&Echo_Gain);
                }
                endian_convert_32((uint8_t*)&Echo_Gain);

                std::cout << "Echo_Gain = " << Echo_Gain << std::endl;
            }
        }

        if (j_res["EVENT_NEW_CYCLIC_DATA"].get<bool>() == true)
        {
            static uint32_t value_i = 0;
            static float value_f = 0.0;

            answer_str.clear();
            *p_output_stream_ << j_get_echo_data.dump() << std::endl;
            std::getline( *p_input_stream_.get( ), answer_str);
            json j_answ_data = json::parse(answer_str);
            std::vector<uint8_t> in;

            for (auto& jx : j_answ_data["result"])
            {
                uint32_t module_id; 
                uint32_t submodule_id;

                module_id = jx["module_id"].get<int>();
                submodule_id = jx["submodule_id"].get<int>();

                if ((module_id != ECHO_MODULE_ID) || (submodule_id != ECHO_SUBMOD_ID))
                {
                    std::cout << "Error id" << std::endl;
                    continue;
                }

                in = jx["data"]["bytes"].get<std::vector<uint8_t>>();

                if (in.size() == 8)
                {
                    std::copy(in.begin(), in.end(), Echo_outCycData.mem);
                }
                /// Конвертируем в литл эндиан
                endian_convert_32((uint8_t*)&Echo_outCycData.data.data_i);
                endian_convert_32((uint8_t*)&Echo_outCycData.data.data_f);

                if (value_i != Echo_outCycData.data.data_i)
                {
                    value_i = Echo_outCycData.data.data_i;

                    std::cout << "New data_i from PLC: " << value_i << std::endl;
                }

                if (value_f != Echo_outCycData.data.data_f)
                {
                    value_f = Echo_outCycData.data.data_f;

                    std::cout << "New data_f from PLC: " << value_f << std::endl;
                }

                ///Подготавливаем данные для ПЛК
                Echo_inpCycData.data.data_i = Echo_Gain * Echo_outCycData.data.data_i;
                Echo_inpCycData.data.data_f = Echo_Gain * Echo_outCycData.data.data_f;
                ///Конвертируем в биг эндиан
                endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_i);
                endian_convert_32((uint8_t*)&Echo_inpCycData.data.data_f);
                json j_set_data;

                std::vector<uint8_t> in_data;
                in_data.insert(in_data.end(), &Echo_inpCycData.mem[0], &Echo_inpCycData.mem[8]);

                j_set_data["module_id"] = module_id;
                j_set_data["submodule_id"] = submodule_id;
                j_set_data["data"] = json::binary(in_data);

                j_set_echo_data["params"].push_back(j_set_data);
                ///Записываем данные
                *p_output_stream_ << j_set_echo_data.dump() << std::endl;
                
                answer_str.clear();
                ///Ждем ответ
                std::getline( *p_input_stream_.get( ), answer_str);
            }
            
            j_set_echo_data["params"].clear();
        }
    }
    return 0;
}