#include "profinet_submodule.hpp"
#include <cstring>

ProfinetSubmodule::ProfinetSubmodule(uint32_t submodule_id, 
                      std::string submodule_name, 
                      pnet_data_cfg_t& submodule_data_cfg,
                      SharedDataIface * inp_data_ptr,
                      SharedDataIface * out_data_ptr) : 
                      m_id(submodule_id), 
                      m_name(submodule_name),
                      m_data_cfg(submodule_data_cfg),
                      inp_data_ptr_(inp_data_ptr),
                      out_data_ptr_(out_data_ptr),
                      m_indata_iocs(0),
                      m_outdata_iops(0),
                      data_updated_(false),
                      out_data_buf_(nullptr),
                      inp_data_buf_(nullptr)
{
    if (m_data_cfg.outsize && (out_data_ptr_!=nullptr))
    {
        if (m_data_cfg.outsize == out_data_ptr_->Size())
        {
            out_data_buf_ = std::shared_ptr<uint8_t[]>(new uint8_t[m_data_cfg.outsize]);
        }
    }

    if (m_data_cfg.insize && (inp_data_ptr_ != nullptr))
    {
        if (m_data_cfg.insize == inp_data_ptr_->Size())
        {
            inp_data_buf_ = std::shared_ptr<uint8_t[]>(new uint8_t[m_data_cfg.insize]);
        }
    }
}

bool ProfinetSubmodule::addParameter(std::shared_ptr<ProfinetParameter>& param)
{
    auto ret = m_params.emplace(param->index, param);

    return ret.second;
}

std::shared_ptr<ProfinetParameter> ProfinetSubmodule::getParameterPtr(uint32_t index)
{
    if (!m_params.count(index))
    {
        return nullptr;
    }

    return m_params[index];
}

bool ProfinetSubmodule::inputSetData( pnet_t * pnet_ptr, 
                                      uint32_t api, 
                                      uint16_t slot_nbr, 
                                      uint16_t subslot_nbr, 
                                      uint8_t iops)
{
    if ((pnet_ptr == nullptr) || 
       ((inp_data_buf_ == nullptr) && (m_data_cfg.insize > 0)))
    {
        return false;
    }

    /**
     * @brief Вызов когда m_data_cfg.insize==0 и inp_data_ptr_== nullptr 
     * нужен для корректного установления соединения
     * 
     */

    ///Копируем данные в буфер
    if (inp_data_ptr_ != nullptr)
    {
        inp_data_ptr_->Read(0, inp_data_buf_.get(), m_data_cfg.insize);
    }

    /// Копируем данные для передачи в ПЛК
    pnet_input_set_data_and_iops( pnet_ptr,
                                  api,
                                  slot_nbr,
                                  subslot_nbr,
                                  inp_data_buf_.get(),
                                  m_data_cfg.insize,
                                  iops);

    return true;
}

bool ProfinetSubmodule::outputGetData( pnet_t * pnet_ptr, 
                                       uint32_t api, 
                                       uint16_t slot_nbr, 
                                       uint16_t subslot_nbr,  bool& data_update )
{
    bool out = true;

    /// Существуют подмодули без данных, например DAP
    if ((pnet_ptr == nullptr) || ((out_data_ptr_ == nullptr) && (m_data_cfg.outsize > 0)))
    {
        return false;
    }

    uint16_t outdata_length = m_data_cfg.outsize;
    uint8_t outdata_iops;
    /**
     * @brief Флаг data_updated_ устанавливается внутри pnet_output_get_data_and_iops почему-то с задержкой 1 сек.
     * Поэтому условием новых данных является не равность нулю outdata_length.
     */
    /// Копируем данные полученные от контроллера
    pnet_output_get_data_and_iops (pnet_ptr,
                                   api,
                                   slot_nbr,
                                   subslot_nbr,
                                   &data_updated_,
                                   out_data_buf_.get(),
                                   &outdata_length,
                                   &outdata_iops);

    ///out_data_ptr_ данные из буфера
    ///Когда новых данных нет, то outdata_length == 0
    if ((out_data_ptr_ != nullptr) && (outdata_length == m_data_cfg.outsize))
    {
        out_data_ptr_->Write(0, out_data_buf_.get(), m_data_cfg.outsize);
        data_update = true;
    }
    else
    {
        data_update = false;
    }

    m_outdata_iops = outdata_iops;

    if (m_data_cfg.outsize != outdata_length)
    {
        /// Неправильная длина данных
        out = false;
    }
    else if (outdata_iops == PNET_IOXS_BAD)
    {
        /// Что-то не так с данными от контроллера
        out = false;
    }

    return out;
}

void ProfinetSubmodule::readOutputData(std::vector<uint8_t>& out)
{
    uint32_t size = out_data_ptr_->Size();
    uint8_t buff[size];
    
    out_data_ptr_->Read(0, buff, size);

    out.insert(out.end(), &buff[0], &buff[size]);
}

bool ProfinetSubmodule::writeInputData(std::vector<uint8_t>& in)
{
    uint32_t size = out_data_ptr_->Size();
    uint8_t buff[size];

    if (in.size() != size)
        return false;

    std::copy(in.begin(), in.end(), buff);

    inp_data_ptr_->Write(0, buff, size);

    return true;
}