/*  Copyright 2025 Leuze electronic GmbH + Co. KG
 *
 *  Licensed under the Apache License, Version 2.0
 */

#ifndef LEUZE_ETH_COMM_TCP_H
#define LEUZE_ETH_COMM_TCP_H

#include <array>
#include <condition_variable>
#include <deque>
#include <iostream>
#include <mutex>
#include <string>
#include <vector>

#include <boost/bind/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/circular_buffer.hpp>

#include "leuze_msgs/msg/status_profile_msg_rod.hpp"
#include "leuze_rod_driver/common.h"
#include "leuze_rod_driver/scan_data_parser.h"

constexpr std::uint32_t CMDITF_MSG_LENGTH_MAX       = 128;
constexpr std::uint32_t CMDITF_MSG_TIMEOUT_MS       = 1000;

/* command interface function return values */
constexpr std::uint32_t CMDITF_RETVAL_OK            = 0x00;
constexpr std::uint32_t CMDITF_RETVAL_ERROR         = 0x01;
constexpr std::uint32_t CMDITF_RETVAL_RX_TIMEOUT    = 0x02;
constexpr std::uint32_t CMDITF_RETVAL_TX_TIMEOUT    = 0x04;

/* command interface requests IDs */
constexpr std::uint32_t CMDITF_REQ_NULL             = 0x0000;    
constexpr std::uint32_t CMDITF_REQ_SEND_MDI         = 0x1000;
constexpr std::uint32_t CMDITF_REQ_STOP_MDI         = 0x1001;
constexpr std::uint32_t CMDITF_REQ_GET_PROTO        = 0x1100;
constexpr std::uint32_t CMDITF_REQ_GET_PTYPE        = 0x1101;
constexpr std::uint32_t CMDITF_REQ_GET_RESOL        = 0x1102;
constexpr std::uint32_t CMDITF_REQ_GET_DIR          = 0x1103;
constexpr std::uint32_t CMDITF_REQ_GET_RANGE        = 0x1104;
constexpr std::uint32_t CMDITF_REQ_GET_SKIP         = 0x1105;
constexpr std::uint32_t CMDITF_REQ_GET_CONT         = 0x1106;
constexpr std::uint32_t CMDITF_REQ_GET_WINSTAT      = 0x1107;
constexpr std::uint32_t CMDITF_REQ_GET_VER          = 0x1108;
constexpr std::uint32_t CMDITF_REQ_GET_TEM          = 0x1109;
constexpr std::uint32_t CMDITF_REQ_GET_ELOG         = 0x110A;
constexpr std::uint32_t CMDITF_REQ_GET_HOURS        = 0x110B;
constexpr std::uint32_t CMDITF_REQ_GET_WCALIB       = 0x110C;
constexpr std::uint32_t CMDITF_REQ_GET_FILTER       = 0x110D;
constexpr std::uint32_t CMDITF_REQ_GET_ECODE        = 0x110E;
constexpr std::uint32_t CMDITF_REQ_GET_TXMDI        = 0x110F;

/* command interface requests */
constexpr std::uint8_t CMDITF_CWN_SEND_MDI[]        = { 0x02, 'c', 'W', 'N', ' ', 'S', 'e', 'n', 'd', 'M', 'D', 'I', 0x03 }; 
constexpr std::uint8_t CMDITF_CWN_STOP_MDI[]        = { 0x02, 'c', 'W', 'N', ' ', 'S', 't', 'o', 'p', 'M', 'D', 'I', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_PROTO[]       = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'P', 'r', 'o', 't', 'o', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_PTYPE[]       = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'P', 'T', 'y', 'p', 'e', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_RESOL[]       = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'R', 'e', 's', 'o', 'l', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_DIR[]         = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'D', 'i', 'r', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_RANGE[]       = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'R', 'a', 'n', 'g', 'e', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_SKIP[]        = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'S', 'k', 'i', 'p', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_CONT[]        = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'C', 'o', 'n', 't', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_WINSTAT[]     = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'W', 'i', 'n', 'S', 't', 'a', 't', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_VER[]         = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'V', 'e', 'r', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_TEM[]         = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'T', 'e', 'm', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_ELOG[]        = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'E', 'L', 'o', 'g', 0x03 };;
constexpr std::uint8_t CMDITF_CRN_GET_HOURS[]       = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'H', 'o', 'u', 'r', 's', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_WCALIB[]      = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'W', 'C',  'a',  'l',  'i',  'b', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_FILTER[]      = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'F', 'i',  'l',  't',  'e',  'r', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_ECODE[]       = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'E', 'C', 'o', 'd', 'e', 0x03 };
constexpr std::uint8_t CMDITF_CRN_GET_TXMDI[]       = { 0x02, 'c', 'R', 'N', ' ', 'G', 'e', 't', 'T', 'x', 'M', 'D', 'I', 0x03 };

/* command interface responses IDs */
constexpr std::uint32_t CMDITF_RESP_NULL            = 0x0000;
constexpr std::uint32_t CMDITF_RESP_SEND_MDI        = 0x4000;
constexpr std::uint32_t CMDITF_RESP_STOP_MDI        = 0x4001;
constexpr std::uint32_t CMDITF_RESP_GET_PROTO       = 0x4100;
constexpr std::uint32_t CMDITF_RESP_GET_PTYPE       = 0x4101;
constexpr std::uint32_t CMDITF_RESP_GET_RESOL       = 0x4102;
constexpr std::uint32_t CMDITF_RESP_GET_DIR         = 0x4103;
constexpr std::uint32_t CMDITF_RESP_GET_RANGE       = 0x4104;
constexpr std::uint32_t CMDITF_RESP_GET_SKIP        = 0x4105;
constexpr std::uint32_t CMDITF_RESP_GET_CONT        = 0x4106;
constexpr std::uint32_t CMDITF_RESP_GET_WINSTAT     = 0x4107;
constexpr std::uint32_t CMDITF_RESP_GET_VER         = 0x4108;
constexpr std::uint32_t CMDITF_RESP_GET_TEM         = 0x4109;
constexpr std::uint32_t CMDITF_RESP_GET_ELOG        = 0x410A;
constexpr std::uint32_t CMDITF_RESP_GET_HOURS       = 0x410B;
constexpr std::uint32_t CMDITF_RESP_GET_WCALIB      = 0x410C;
constexpr std::uint32_t CMDITF_RESP_GET_FILTER      = 0x410D;
constexpr std::uint32_t CMDITF_RESP_GET_ECODE       = 0x410E;
constexpr std::uint32_t CMDITF_RESP_GET_TXMDI       = 0x410F;

/* commend interface responses */
const std::string CMDITF_CWA_SEND_MDI               = "cWA SendMDI"; 
const std::string CMDITF_CWA_STOP_MDI               = "cWA StopMDI";
const std::string CMDITF_CRA_GET_PROTO              = "cRA GetProto";
const std::string CMDITF_CRA_GET_PTYPE              = "cRA GetPType";
const std::string CMDITF_CRA_GET_RESOL              = "cRA GetResol";
const std::string CMDITF_CRA_GET_DIR                = "cRA GetDir";
const std::string CMDITF_CRA_GET_RANGE              = "cRA GetRange";
const std::string CMDITF_CRA_GET_SKIP               = "cRA GetSkip";
const std::string CMDITF_CRA_GET_CONT               = "cRA GetCont";
const std::string CMDITF_CRA_GET_WINSTAT            = "cRA GetWinStat";
const std::string CMDITF_CRA_GET_VER                = "cRA GetVer";
const std::string CMDITF_CRA_GET_TEM                = "cRA GetTem";
const std::string CMDITF_CRA_GET_ELOG               = "cRA GetELog";
const std::string CMDITF_CRA_GET_HOURS              = "cRA GetHours";
const std::string CMDITF_CRA_GET_WCALIB             = "cRA GetWCalib";
const std::string CMDITF_CRA_GET_FILTER             = "cRA GetFilter";
const std::string CMDITF_CRA_GET_ECODE              = "cRA GetECode";
const std::string CMDITF_CRA_GET_TXMDI              = "cRA GetTxMDI"; 

using namespace boost::placeholders;

namespace leuze_rod_ros2_drivers 
{
    /****************************************************************************************************
     * Structures
     ****************************************************************************************************/

    /**
     * 
     */
    typedef struct tag_ROD_SETTINGS
    {
        int protocol_type;                  // ethernet protocol type used for (0 - UDP / 1 - TCP)
        int packet_type;                    // data packet type (0 - only distances / 1 - distances & amplitudes)
        int scan_direction;                 // scan direction (0 - clockwise / 1 - counterclockwise)
        int skip_spots;
        double angle_start;
        double angle_stop;
        double resolution;
        double scan_freq;
        double scan_time;
        double delta_angle;
    } ROD_SETTINGS;

    /************************************************************************************************************
     * @class   EthCommTCP
     * 
     * @brief   This class ensures TCP commmunication with a ROD scanner. It supports capturing of a TCP process
     *          data (MDI packets) and communication of settings and status (command interface)
     ************************************************************************************************************/
    class EthCommTCP : public ScanDataParser
    {
        public:

            int pending_req_;
            int pending_resp_;

            /****************************************************************************************************/

            /**
             *@brief    constructor
             *@param    hostname
             *@param    tcp_port
             */
            EthCommTCP(const std::string hostname, int tcp_port = 3050);

            /**
             *@brief    destructor 
             */
            ~EthCommTCP();

            /****************************************************************************************************/

            /**
             *@brief    handle opened TCP socket receive and data parsing
             *@param    error
             *@param    bytes_transferred 
             */
            void tcpSockRecvHandler(const boost::system::error_code& error, std::size_t bytes_transferred);

            /**
             *@brief    handle opened TCP socket read
             *@param    error
             *@param    bytes_transferred 
             */
            boost::system::error_code tcpSockSendCommand(const unsigned int cmd);

            /**
             *@brief    
             *@return   true if success
             */
            bool parseCmdItfResp(std::string response_string);

            /**
             *@brief    get connection state
             *@return   true if success
             */
            bool startCapturing(); 
            
            /**
             *@brief close TCP socket and disconnect from ROD 
             */
            void disconnect();

            /**
             *@brief    get connection state
             *@return   state of connection
             */
            bool getStateConnected() const { return state_connected_; }
            
            
            /****************************************************************************************************/

            /**
             * @brief    Ask laser scanner to start to send MDI (measurement data)
             *
             * @return   0 if success
             */
            int SendMDI();

            /**
             * @brief    Ask laser scanner to stop to send MDI (measurement data)
             *
             * @return   0 if success
             */            
            int StopMDI();

            /****************************************************************************************************/
            
            /**
             * @brief    Query laser scanner for its MDI transmission protocol (UDP / TCP)
             *
             * @return   0 if success
             */
            int GetProto();

            /**
             * @brief    Query laser scanner for its MDI data packet type (Distance only / Distance & intensity)
             *
             * @return   0 if success
             */
            int GetPType();

            /**
             * @brief    Query laser scanner for its angular resolution (scan frequency)
             *
             * @return   0 if success
             */
            int GetResol();

            /**
             * @brief    Query laser scanner for its MDI data output direction (clockwise / counterclockwise)
             *
             * @return   0 if success
             */
            int GetDir();

            /**
             * @brief    Query laser scanner for its absolute angle range of one complete scan
             *
             * @return   0 if success
             */
            int GetRange();       
            
            /**
             * @brief    Query laser scanner for its skip spots between successive output measurements
             *
             * @return   0 if success
             */
            int GetSkip();
            
            /**
             * @brief    Query laser scanner for its warning and error threshold percentage value of contamination
             *
             * @return   0 if success
             */
            int GetCont();

            /**
             * @brief    Query laser scanner for its contamination state
             *
             * @return   0 if success
             */
            int GetWinStat();

            /**
             * @brief    Query laser scanner for its version information
             *
             * @return   0 if success
             */
            int GetVer();

            /**
             * @brief    Query laser scanner for its internal temperature
             *
             * @return   0 if success
             */
            int GetTem();

            /**
             * @brief    Query laser scanner for its internal stored error log
             *
             * @return   0 if success
             */
            int GetELog();

            /**
             * @brief    Query laser scanner for its runtime hours
             *
             * @return   0 if success
             */
            int GetHours();

            /**
             * @brief    Query laser scanner for its calibration status
             *
             * @return   0 if success
             */
            int GetWCalib();

            /**
             * @brief    Query laser scanner for applied filter type
             *
             * @return   0 if success
             */
            int GetFilter();

            /**
             * @brief    Query laser scanner for error code that are currently occuring
             *
             * @return   0 if success
             */
            int GetECode();
            /**
             * @brief    Query laser scanner for current MDI transmission status
             *
             * @return   0 if success
             */
            int GetTxMDI();

            /**
              * @brief   Get ROD status
              *
              * @return  parameterInfo structure  
              */
            leuze_msgs::msg::StatusProfileMsgRod getStatusData();

        private:

            boost::asio::streambuf  inbuf_;
            std::istream            instream_;
            boost::thread           io_service_thread_;
            boost::asio::io_service io_service_;
        
            /* TCP */
            std::timed_mutex                            mutex_tcp_write_;
            boost::asio::ip::tcp::socket*               tcp_socket_;
            std::array< char, SCANPAR_BUFF_SIZE_RING >  tcp_buffer_;

            /* ROD status */
            std::mutex                              mutex_rod_status_;
            leuze_msgs::msg::StatusProfileMsgRod    rod_status_;

            bool state_connected_;
            bool state_capturing_;
            double last_scan_time_;

            /* function mutexes */
            std::timed_mutex    mutex_send_mdi_;  
            std::timed_mutex    mutex_stop_mdi_;  
            std::timed_mutex    mutex_get_proto_;  
            std::timed_mutex    mutex_get_ptype_;  
            std::timed_mutex    mutex_get_resol_;  
            std::timed_mutex    mutex_get_dir_;  
            std::timed_mutex    mutex_get_range_;  
            std::timed_mutex    mutex_get_skip_;  
            std::timed_mutex    mutex_get_cont_;  
            std::timed_mutex    mutex_get_winstat_;  
            std::timed_mutex    mutex_get_ver_;  
            std::timed_mutex    mutex_get_tem_; 
            std::timed_mutex    mutex_get_elog_; 
            std::timed_mutex    mutex_get_hours_;  
            std::timed_mutex    mutex_get_wcalib_;  
            std::timed_mutex    mutex_get_filter_;  
            std::timed_mutex    mutex_get_ecode_;  
            std::timed_mutex    mutex_get_txmdi_;

            /**
             * @brief   split string to multiple substrings thet are separadet by a delimiter
             * @param   input_str   = input string
             * @param   dalimiter   = delimiter
             * @return  vector splitted substring 
             */
            std::vector<std::string> splitString(const std::string &input_str, const char delimiter);
    };  
}

#endif /* eth_comm_tcp_H */
