#include "json_rpc_handler.hpp"
#include <vector>
#include <cstdint>
#include <iostream>

using json = nlohmann::json;
using string = std::string;

void json_rpc_set_status(ProfinetIface::ErrorCode result, string& out)
{
    switch(result)
    {
        case ProfinetIface::ERR_SUCCESS:
        {
            out = "OK";
        }
        break;
        case ProfinetIface::ERR_MODULE_DOES_NOT_EXIST:
        {
            out = "WRONG_MODULE_ID";
        }
        break;
        case ProfinetIface::ERR_SUBMODULE_DOES_NOT_EXIST:
        {
            out = "WRONG_SUBMODULE_ID";
        }
        break;
        case ProfinetIface::ERR_PARAM_DOES_NOT_EXIST:
        {
            out = "WRONG_PARAM_INDEX";
        }
        break;
        case ProfinetIface::ERR_WRONG_DATA_LENGTH:
        {
            out = "WRONG_DATA_LENGTH";
        }
        break;
    }
}

static void json_readFlags(json& j_in, json& j_out, ProfinetIface * p_profinet)
{
    uint32_t events = p_profinet->getEvents();
    
    j_out["result" ]["EVENT_CONNECTION_ESTABLISHED"] = static_cast<bool>(events & ProfinetIface::EVENT_CONNECTION_ESTABLISHED);
    j_out["result" ]["EVENT_NEW_CYCLIC_DATA"]        = static_cast<bool>(events & ProfinetIface::EVENT_NEW_CYCLIC_DATA);
    j_out["result" ]["EVENT_NEW_PARAM_DATA"]         = static_cast<bool>(events & ProfinetIface::EVENT_NEW_PARAM_DATA);
    j_out["result" ]["EVENT_CONNECTION_ABORT"]       = static_cast<bool>(events & ProfinetIface::EVENT_CONNECTION_ABORT);

    p_profinet->clearEvents(events);
}   

static bool json_writeCyclicIoData(json& j_in, json& j_out, ProfinetIface * p_profinet)
{
    if (!j_in.contains("params"))
        return false;
        
    /// Запись 
    j_out["result"] = json::array();

    for (auto& param : j_in["params"])
    {
        json j_arr;
        uint32_t module_id        = param["module_id"].get<int>();
        uint32_t submodule_id     = param["submodule_id"].get<int>();
        std::vector<uint8_t> data = param["data"]["bytes"].get<std::vector<uint8_t>>();

        auto result = p_profinet->putInputCyclicData(module_id, submodule_id, data);

        /// 
        j_arr["module_id"]    = module_id;
        j_arr["submodule_id"] = submodule_id;
        string status;

        json_rpc_set_status(static_cast<ProfinetIface::ErrorCode>(result), status);

        j_arr["status"] = status;

        j_out["result"].push_back(j_arr);
    }

    return true;
}

static bool json_readCyclicIoData(json& j_in, json& j_out, ProfinetIface * p_profinet)
{
    if (!j_in.contains("params"))
        return false;
        
    /// Запись 
    j_out["result"] = json::array();

    for (auto& param : j_in["params"])
    {
        json j_arr;
        uint32_t module_id        = param["module_id"].get<int>();
        uint32_t submodule_id     = param["submodule_id"].get<int>();
        std::vector<uint8_t> data;

        auto result = p_profinet->getOutputCyclicData(module_id, submodule_id, data);

        /// 
        j_arr["module_id"]    = module_id;
        j_arr["submodule_id"] = submodule_id;

        string status;
        json_rpc_set_status(static_cast<ProfinetIface::ErrorCode>(result), status);
        j_arr["status"] = status;
        if (result == ProfinetIface::ERR_SUCCESS)
        {
            j_arr["data"] = json::binary(data);
        }
        else
        {
            j_arr["data"];
        }

        j_out["result"].push_back(j_arr);
    }

    return true;
}

static bool json_readSubmoduleParameter(json& j_in, json& j_out, ProfinetIface * p_profinet)
{
    if (!j_in.contains("params"))
        return false;
        
    /// Запись 
    j_out["result"] = json::array();

    for (auto& param : j_in["params"])
    {
        json j_arr;
        uint32_t module_id        = param["module_id"].get<int>();
        uint32_t submodule_id     = param["submodule_id"].get<int>();
        uint32_t param_idx        = param["param_index"].get<int>();
        std::vector<uint8_t> data;

        auto result = p_profinet->getSubmoduleParam(module_id, submodule_id, param_idx, data);

        /// 
        j_arr["module_id"]    = module_id;
        j_arr["submodule_id"] = submodule_id;
        j_arr["param_index"]  = param_idx;

        string status;
        json_rpc_set_status(static_cast<ProfinetIface::ErrorCode>(result), status);
        j_arr["status"] = status;
        
        if (result == ProfinetIface::ERR_SUCCESS)
        {
            j_arr["data"] = json::binary(data);
        }
        else
        {
            j_arr["data"];
        }

        j_out["result"].push_back(j_arr);
    }

    return true;
}

bool json_rpc_handler(std::string& input, std::string& output, ProfinetIface * p_profinet)
{
    json j_in;

    // Может прийти неправильная строка
    try
    {
        /// Парсим строку
        j_in = json::parse(input);
    }
    catch(const std::exception& e)
    {
        std::cerr << e.what() << '\n';
        return false;
    }
    
    json j_out;

    if (j_in.is_null())
        return false;

    if (!j_in.contains("jsonrpc"))
        return false;
    
    if (!j_in.contains("id"))
        return false;

    int rpc_id = j_in["id"].get<int>();
    
    if (!j_in.contains("method"))
        return false;
    
    string method =  j_in["method"].get<string>();
    
    j_out["jsonrpc"] = "2.0";

    if (method == "readFlags")
    {   
        json_readFlags(j_in, j_out, p_profinet);
    }
    else if (method == "writeCyclicIoData")
    {
        json_writeCyclicIoData(j_in, j_out, p_profinet);
    }
    else if (method == "readCyclicIoData")
    {
        json_readCyclicIoData(j_in, j_out, p_profinet);
    }
    else if (method == "readSubmoduleParameter")
    {
        json_readSubmoduleParameter(j_in, j_out, p_profinet);
    }

    j_out["method"] = method;
    j_out["id"] = rpc_id;

    output = j_out.dump();

    return true;
}   