#pragma once

#include "../../libs/include/pnet_api.h"
#include "profinet_settings.hpp"
#include <string>
#include <map>
#include <memory>
#include "profinet_slot.hpp"
#include "profinet_serv_data.hpp"

#include <atomic>
#include <thread>
#include <vector>

#include "../log/log.hpp"
#include "../flags/flags.hpp"

#include "../flags/flags_iface.hpp"

#include "profinet_data_map.hpp"
#include "profinet_iface.hpp"

class Profinet : public  ProfinetIface {
public:
  
    Profinet();

    ~Profinet();

    bool Config(ProfinetSettings& Settings, 
                ProfinetDeviceSettings& DevSettings,
                ProfinetData_Map * const p_data_map);

    /**
     * @brief Добавляет ранее созданный слот slot_ptr
     * 
     * @param slot_ptr слот
     * @return true слот добавлен
     * @return false слот не был добавлен
     */
    bool addSlot(std::shared_ptr<ProfinetSlot>& slot_ptr);

    /**
     * @brief Если слот с номером slot_nbr еще не добавлен, то создает слот и добавляет его
     * 
     * @param slot_nbr номер слота
     * @return указатель на слот
     */
    std::shared_ptr<ProfinetSlot> addSlot(uint16_t slot_nbr);

    /**
     * @brief Добавляет слот с номером slot_nbr и подключает к нему модуль module_ptr.
     * Если слота с номером slot_nbr еще не добавлено, то он будет создан и добавлен.
     * Если слот с номером slot_nbr был добавлен ранее, то это штатная работа данной функции.
     * 
     * @param slot_nbr номер слота
     * @param module_ptr указатель на модуль
     * @return true модуль успешно подключен
     * @return false модуль не подключен
     */
    bool addSlotAndPlugModule(uint16_t slot_nbr, std::shared_ptr<ProfinetModule>& module_ptr);

    /**
     * @brief Добавляет слот с номером slot_nbr и подключает к нему модуль из словаря \a m_modules чей 
     * идентификатор равен module_id.
     * Если слота с номером slot_nbr еще не добавлено, то он будет создан и добавлен.
     * Если слот с номером slot_nbr был добавлен ранее, то это штатная работа данной функции.
     * 
     * @param slot_nbr номер слота
     * @param module_id идентификатор модуля
     * @return true модуль успешно подключен
     * @return false модуль не подключен
     */
    bool addSlotAndPlugModule(uint16_t slot_nbr, uint32_t module_id);

    /**
     * @brief Добавляет к слоту с номером \a slot_nbr подслот с номером \a subslot_nbr и подключает к нему подмодуль \a submodule_ptr.
     * Если слота с номером \a slot_nbr еще не добавлено, то выполнене функции завершиться со значением \a false.
     * Если подслота с номером \a subslot_nbr еще не добавлено в слот \a slot_nbr то он будет создан и добавлен.
     * 
     * @param slot_nbr номер слота
     * @param subslot_nbr номер подслота
     * @param submodule_ptr указатель на подмодуль
     * @return true подмодуль успешно подключен
     * @return false подмодуль не подключен
     */
    bool addSubslotAndPlugSubmodule(uint16_t slot_nbr, uint16_t subslot_nbr, std::shared_ptr<ProfinetSubmodule>& submodule_ptr);
    
    /**
     * @brief Добавляет к слоту с номером \a slot_nbr, к которому подключен модуль с идентификатором \a module_id, подслот 
     * с номером \a subslot_nbr и подключает к нему подмодуль с идентификатором \a submodule_id.
     * Выполнение функции прекратиться с возвратом false, если:
     *  - слот \a slot_nbr не был добавлен ранее,
     *  - слот \a slot_nbr был добавлен ранее, но к нему подключен модуль с идентфикатором не равным \a module_id,
     *  - в модуль с идентификатором \a module_id не входит подмодуль с идентификатором \a submodule_id
     *  - описание данных подмодуля \a submodule_id не совпарадет с описанием данным в \a p_exp_data
     * 
     * @param slot_nbr номер слота
     * @param subslot_nbr номер подслота
     * @param module_id идентификатор модуля
     * @param submodule_id идентификатор подмодуля
     * @param p_exp_data описание данныз подмодуля
     * @return true подмодуль подключен
     * @return false подмодуль не подключен
     */
    bool addSubslotAndPlugSubmodule(uint16_t slot_nbr, 
                                    uint16_t subslot_nbr, 
                                    uint32_t module_id,
                                    uint32_t submodule_id,
                                    const pnet_data_cfg_t * p_exp_data);
    
    
    /**
     * @brief Добавляет модуль в конфигурацию
     * 
     * @param module_ptr 
     * @return true 
     * @return false 
     */
    bool addModule(std::shared_ptr<ProfinetModule>& module_ptr);

    /**
     * @brief геттер модуля
     * 
     * @param module_id идентификатор модуля
     * @return std::shared_ptr<ProfinetModule> возвращает указатель на модуль или nullptr если такого модуля нет в \a m_modules
     */
    std::shared_ptr<ProfinetModule> getModule(uint32_t module_id);

    /**
     * @brief геттер указателя на структуру данных параметра подмодуля с идексом \a param_index подключенного к подслоту 
     * \a subslot_nbr входящего в состав \a slot_nbr.
     * 
     * @param slot_nbr номер слота 
     * @param subslot_nbr номер подслота
     * @param param_index индекс параметра
     * @return std::shared_ptr<ProfinetParameter> указатель на параметр или nullptr
     */
    std::shared_ptr<ProfinetParameter> getSubmoduleParameter(uint16_t slot_nbr, uint16_t subslot_nbr, uint32_t param_index);

    /**
     * @brief Таймер для циклического обмена данными
     * 
     */
    void PeriodicOperations();

    /**
     * @brief Запуск потока
     * 
     */
    void Start();

    uint32_t EventWait(uint32_t mask) { return p_events_->wait_flags(mask); }

    virtual void clearEvents(uint32_t mask) override { p_events_->clear_flags(mask); }

    virtual uint32_t getEvents() const override { return p_events_->get_flags(); }

    virtual int getOutputCyclicData(uint32_t module_id, 
                                    uint32_t submodule_id, 
                                    std::vector<uint8_t>& data) override;
    
    virtual int putInputCyclicData(uint32_t module_id, 
                                   uint32_t submodule_id, 
                                   std::vector<uint8_t>& data) override;

    virtual int getSubmoduleParam(uint32_t module_id, 
                                  uint32_t submodule_id, 
                                  uint32_t param_id, 
                                  std::vector<uint8_t>& data) override;
   
private:
    ProfinetServiceData m_pnet_data;

    uint32_t m_run_period_us;          /// Период одного тика (цикла) выполнения

    uint32_t m_cyclic_io_period_ms;    /// Период циклического обмена
    uint32_t m_cyclic_io_cnt;          /// Счетчик циклического обмена

    std::thread m_periodic_thread;     /// Поток в котором исполняется PeriodicOperations
    std::atomic<bool> m_periodic_stop; /// Флаг завершения потока 

    /**
     * @brief Ключ - номер слота, Значение - указатель на слот
     * 
     */
    std::map<uint16_t, std::shared_ptr<ProfinetSlot>> m_slots;
    /**
     * @brief Ключ - идентиикатор модуля, Значение - указатель на модуль
     * 
     */
    std::map<uint32_t, std::shared_ptr<ProfinetModule>> m_modules;

    Log log_;
    
    Flags flags_; /// Внутренние флаги: взаимодействиe между потоком Profinet и потоком стека Pnet

    FlagsIface * p_events_; /// Флаги для внешних событий. Это более общие флаги, например флаг получения данных от ПЛК, а в каком подмодуле, уже не уточняется.
private:
    /**
     * @brief Проверяет наличие слота slot_nbr в конфигурации и возвращает указатель на него
     * 
     * @param slot_nbr  номер слота
     * @return std::shared_ptr<ProfinetSlot> указатель на слот, при успехе или nullptr если слот в конфигурации отсутсвует
     */
    std::shared_ptr<ProfinetSlot> getSlotPtr(uint16_t slot_nbr);
    /**
     * @brief Проверяет наличие подслота в слоте и возвращает указатель на него 
     * 
     * @param slot_nbr номер слота
     * @param subslot_nbr номер подслота
     * @return std::shared_ptr<ProfinetSubslot> указатель на подслот, при успехе или nullptr если подслот в конфигурации отсутсвует
     */
    std::shared_ptr<ProfinetSubslot> getSubslotPtr(uint16_t slot_nbr, uint16_t subslot_nbr);

    /**
     * @brief Флаг подключение к контроллеру(ПЛК).
     * Реализация взята с примера стека pnet. На самом деле не понятна роль AREP.
     * Открытой информации недостаточно, нужно покупать и смотреть спецификацию.     * 
     * 
     * @return true подключение есть
     * @return false 
     */
    bool isConnectedToController() { return m_pnet_data.arep != UINT32_MAX; };

    void initDataAndIoxs();

    void cyclicIoData();

    void createInternalStucture(ProfinetData_Map * const p_data_map);

public:
    /**
     * 
     * Indication to the application that a module is requested by the controller in
     * a specific slot.
     *
     * This application call-back function is called by the Profinet stack to
     * indicate that the controller has requested the presence of a specific module,
     * ident number \a module_ident, in the slot number \a slot.
     *
     * The application must react to this by configuring itself accordingly (if
     * possible) and call function pnet_plug_module() to configure the stack for
     * this module.
     *
     * If the wrong module ident number is plugged then the stack will accept this,
     * but signal to the controller that a substitute module is fitted.
     *
     * This function should return 0 (zero) if a valid module was plugged. Or return
     * -1 if the application cannot handle this request.
     * 
     * @param arep  In:    The AREP.
     * @param event In:    The state transition event. See pnet_event_values_t
     * @return 0 on success. Other values are ignored.
     */
     int callbackStateInd ( uint32_t arep, pnet_event_values_t event);

    /**
     * @brief This application call-back function is called by the Profinet stack on every
     * Connect request from the Profinet controller.
     * 
     * @param arep             In:    The AREP.
     * @param p_result         Out:   Detailed error information if return != 0.
     * @return  0  on success.
     *          -1 if an error occurred.
     */
     int callbackConnectInd ( uint32_t arep, pnet_result_t * p_result);

    /**
     * @brief This application call-back function is called by the Profinet stack on every
     * Release request from the Profinet controller.
     * 
     * @param arep             In:    The AREP.
     * @param p_result         Out:   Detailed error information if return != 0.
     * @return  0  on success.
     *          -1 if an error occurred.
     */
     int callbackReleaseInd ( uint32_t arep, pnet_result_t * p_result);

    /**
     * @brief  This application call-back function is called by the Profinet stack on every
     * DControl request from the Profinet controller.
     * 
     * @param arep             In:    The AREP.
     * @param control_command  In:    The DControl command code.
     * @param p_result         Out:   Detailed error information if return != 0.
     * @return  0  on success.
     *          -1 if an error occurred.
     */
     int callbackDcontrolInd ( uint32_t arep, 
                               pnet_control_command_t control_command,
                               pnet_result_t * p_result);

    /**
     * @brief * This application call-back function is called by the Profinet stack on every
     * CControl confirmation from the Profinet controller.
     * @param arep             In:    The AREP.
     * @param p_result         Out:   Detailed error information.
     * @return 0 on success. Other values are ignored.
     */
     int callbackCcontrolInd ( uint32_t arep, pnet_result_t * p_result);
     
    /**
     * @brief This application call-back function is called by the Profinet stack on every
     * IODRead request from the Profinet controller which specify an
     * application-specific value of \a idx (0x0000 - 0x7fff). All other values of
     * \a idx are handled internally by the Profinet stack.
     * 
     * @param arep             In:    The AREP.
     * @param slot             In:    The slot number.
     * @param subslot          In:    The sub-slot number.
     * @param idx              In:    The data record index.
     * @param sequence_number  In:    The sequence number.
     * @param pp_read_data     Out:   A pointer to the binary value.
     * @param p_read_length    InOut: The maximum (in) and actual (out) length in
     *                                bytes of the binary value.
     * @param p_result         Out:   Detailed error information if returning != 0
     * @return  0  on success.
     *          -1 if an error occurred.
     */
     int callbackReadInd (
        uint32_t arep,
        uint16_t slot_nbr,
        uint16_t subslot_nbr,
        uint16_t idx,
        uint16_t sequence_number,
        uint8_t ** pp_read_data,
        uint16_t * p_read_length,
        pnet_result_t * p_result);
    /**
     * @brief This application call-back function is called by the Profinet stack on every
     * IODWrite request from the Profinet controller which specify an
     * application-specific value of \a idx (0x0000 - 0x7fff). All other values of
     * \a idx are handled internally by the Profinet stack.
     * 
     * @param arep             In:    The AREP.
     * @param slot             In:    The slot number.
     * @param subslot          In:    The sub-slot number.
     * @param idx              In:    The data record index.
     * @param sequence_number  In:    The sequence number.
     * @param write_length     In:    The length in bytes of the binary value.
     * @param p_write_data     In:    A pointer to the binary value.
     * @param p_result         Out:   Detailed error information if returning != 0
     * @return  0  on success.
     *          -1 if an error occurred.
     */
     int callbackWriteInd (
        uint32_t arep,
        uint16_t slot_nbr,
        uint16_t subslot_nbr,
        uint16_t idx,
        uint16_t sequence_number,
        uint16_t write_length,
        const uint8_t * p_write_data,
        pnet_result_t * p_result);

    /**
     * @brief * This application call-back function is called by the Profinet stack to
     * indicate that the controller has requested the presence of a specific module,
     * ident number \a module_ident, in the slot number \a slot.
     * 
     * @param slot 
     * @param module_ident 
     * @return int 
     */
     int callbackExpModuleInd ( uint16_t slot, uint32_t module_ident);

    /**
     * @brief This application call-back function is called by the Profinet stack to
     * indicate that the controller has requested the presence of a specific
     * sub-module with ident number \a submodule_ident, in the sub-slot number
     * \a subslot, with module ident number \a module_ident in slot \a slot.
     * 
     * @param slot             In:    The slot number.
     * @param subslot          In:    The sub-slot number.
     * @param module_ident     In:    The module ident number.
     * @param submodule_ident  In:    The sub-module ident number.
     * @param p_exp_data       In:    The expected data configuration (sizes and
     *                                direction)
     * @return  0  on success.
     *          -1 if an error occurred.
     */
     int callbackExpSubmoduleInd (
        uint16_t slot,
        uint16_t subslot,
        uint32_t module_id,
        uint32_t submodule_id,
        const pnet_data_cfg_t * p_exp_data);

    /**
     * @brief This application call-back function is called by the Profinet stack to
     * indicate that the received data status has changed.
     * 
     * @param arep             In:    The AREP.
     * @param crep             In:    The CREP.
     * @param changes          In:    The changed bits in the received data status.
     *                                See pnet_data_status_bits_t
     * @param data_status      In:    Current received data status (after changes).
     *                                See pnet_data_status_bits_t
     * @return 0 on success. Other values are ignored.
     */
     int callbackNewDataStatusInd (
        uint32_t arep,
        uint32_t crep,
        uint8_t changes,
        uint8_t data_status);
        
    /**
     * @brief This functionality is used for alarms triggered by the IO-controller.
     * 
     * @param arep             In:    The AREP.
     * @param p_alarm_argument In:    The alarm argument (with slot, subslot,
     *                                alarm_type etc)
     * @param data_len         In:    Data length
     * @param data_usi         In:    Alarm USI
     * @param p_data           In:    Alarm data
     * @return  0  on success.
     *          Other values are ignored.
     */
     int callbackAlarmInd (
        uint32_t arep,
        const pnet_alarm_argument_t * p_alarm_arg,
        uint16_t data_len,
        uint16_t data_usi,
        const uint8_t * p_data);

    /**
     * @brief This functionality is used for alarms triggered by the IO-device.
     * 
     * @param arep             In:    The AREP.
     * @param p_pnio_status    In:    Detailed ACK information.
     * @return 0 on success. Other values are ignored.
     */
     int callbackAlarmCnf ( uint32_t arep, const pnet_pnio_status_t * p_pnio_status);
    
    /**
     * @brief This functionality is used for alarms triggered by the IO-controller.
     * 
     * @param arep             In:    The AREP.
     * @param res              In:    0 if ACK was received by the remote side.
     *                                  This is cnf(+).
     *                                -1 if ACK was not received by the remote
     *                                  side. This is cnf(-).
     * @return 0 on success. Other values are ignored.
     */
     int callbackAlarmAckCnf ( uint32_t arep, int res);
    /**
     * @brief This application call-back function is called by the Profinet stack on every
     * reset request (via the DCP "Set" command) from the Profinet controller.
     * 
     * @param should_reset_application In:    True if the user should reset the
     *                                        application data.
     * @param reset_mode               In:    Detailed reset information.
     * @return 0 on success. Other values are ignored. 
     */
     int callbackResetInd ( bool should_reset_application, uint16_t reset_mode);
    
    /**
     * @brief Use this callback to implement control of the LED.
     * 
     * @param led_state        In:    True if the signal LED should be on.
     * @return  0  on success.
     *          -1 if an error occurred. Will trigger a log message. 
     */
     int callbackSignalLedInd (bool led_state);
};