/*** CEthCamera v1.07 ***/

#include <pthread.h>
#include <SocketHandler.h>
#include "NudpSocket.h"
#include "CEthCameraDefines.h"

class RefreshThread;

class CEthCamera
{
    friend class RefreshThread;

public:
    // Constructor parameters:
    //   local_interface - obligatory - IP address of LOCAL eth device as string: "192.168.123.126"
    //   local_port - LOCAL UDP port number, if default is not suitable or there are many CEthCamera objects
    //   Remember: refresh thread is always at the same IP and the UDP PORT NUMBER IS: local_port+1
    CEthCamera( const std::string &local_interface, port_t local_port = DEF_LOCAL_PORT );

    // Destructor will wait until RAW data transfer is finished,
    // but it will not wait for camera responses for commands.
    ~CEthCamera( void );

    // Opening connection to remote camera:
    //   remote_host - obligatory - IP address of the REMOTE device as string: "192.168.123.154"
    //   remote_port - REMOTE UDP port number, if standard (#23567) is not for you
    // Returns "true" and sets CAM_CONNECTION_OPEN flag if succeeded. That means 2 threads are running
    // in the background from now: refresh and listener. Refresh is quite independent, it just sends 0xFC
    // command every 5s (by default), but it will block SendCmd() method if connection is lost - check
    // CAM_REFRESH_RUNNING flag. Listener thread collects all packets from the camera; unexpected
    // packets will be reported in logs.
    bool Open( const std::string &remote_host, port_t remote_port = DEF_REMOTE_PORT );

    // Closing the connection:
    // Will wait for RAW data, try to close NUDP communication socket and if succeeded - shuts down
    // listener and refresh threads and returns "true".
    bool Close( void );

    // Send command (request, ...) to the camera, get the response. SendCmd() will block current thread
    // execution until camera responded or error occured. If camera sends RAW data packets while we
    // are waiting for command response - it's OK, they will be collected (and reported in logs).
    // Returns "true" if command is sent correctly and proper answer is obtained.
    // Returns "false" if:
    //   - connection is closed (CAM_CONNECTION_OPEN flag not set)
    //   - refresh thread is not running (CAM_REFRESH_RUNNING flag not set)
    //   - command to send is 0x08 while CAM_DATA_TAKING flag is still set
    //   - problem with mutex occured (that would be strange...)
    //   - NUDP socket reported an error (NUDP_CLOSED, NUDP_WAITING states)
    //  or:
    //   - timeouts and damaged packets limit reached
    //   (connection will be closed in this case)
    bool SendCmd(
	    // packet-to-send parameters:
	    const char src_number[4],   // number field, obligatory (Lo byte first, Hi byte last)
	    const char* src_data = NULL,   // pointer to data field, NULL if there is no data to send
	    const size_t src_data_length = 0,   // length of the data field
	    const char src_top = 0,   // type of packet, 0 (command) by default
	    // pointers to store camera response:
	    char* dst_number = NULL,  // address of 4-byte long buffer to store the number field
	    char* dst_data = NULL,   // there must be enough place allocated (NUDP_DATA_LENGTH)
	    size_t* dst_data_length = NULL,   // data field length of the received packet
	    char* dst_top = NULL );
    bool SendCmd(
	    const unsigned int src_number,
	    const char* src_data = NULL,
	    const size_t src_data_length = 0,
	    const char src_top = 0,
	    unsigned int* dst_number = NULL,
	    char* dst_data = NULL,
	    size_t* dst_data_length = NULL,
	    char* dst_top = NULL );

    // Get the last camera response. Do the parameter names look familiar? Yes, it is the "dst_*" part of SendCmd()
    // and the meaning is the same. Following methods access the last packet sent by camera as a command response
    // (any RAW data transmisions do not interfere). It is your responsibility to know what was the last command sent
    // to the camera.
    // Returns 'true" if NUDP socket state is NUDP_READY and NUDP_CMD_ERR flag is cleared.
    bool GetLastResponse(
	    char* dst_number,
	    char* dst_data = NULL,
	    size_t* dst_data_length = NULL,
	    char* dst_top = NULL );
    bool GetLastResponse(
	    unsigned int* dst_number,
	    char* dst_data = NULL,
	    size_t* dst_data_length = NULL,
	    char* dst_top = NULL );

    // Sets pointer to an external buffer where RAW data shoud be stored.
    // Must be called before each 0x08 command, or data will not be recorded.
    // Buffer length thah you must allocate on your own: NUDP_DATA_LENGTH*NUDP_DATA_PACKETS
    bool SetRawDataBuffer( char* buf_addr );

    // Blocks execution until RAW data transmision is finished.
    // Returns: true if all packed collected; false otherwise (timeout occured)
    bool WaitForRawData( void );

    // Check if particular RAW packet is received. It is safe to call these methods during data transfer.
    bool FragmentReceivedByOffset( int offset );
    bool FragmentReceivedByIndex( int index );
    // Get the number of RAW data packets already received, also safe during the transfer.
    int GetDataCount( void );

    // Read missing packets - uses GetDataCount() and FragmentReceivedByIndex()
    // to get missing packets from last transmission (one by one, top=6).
    // Returns "true" if all packets has been received.
    // Returns "false" if some packets are still missing (command timeout or other error occured).
    bool ReadMissing( char* buf_addr );

    // Set refresh interval in seconds (default 5s is used if not changed).
    void SetRefreshTime( int sec );
    // Set timeout BETWEEN RAW data packets in miliseconds (default is 500ms)
    void SetDataTimeout( int msec );
    // Get/set command response timeout in miliseconds (default is 100ms)
    int GetCommandTimeout(){ return _cmd_timeout/1000; }
    void SetCommandTimeout( int msec );
    // Set number of command retries, only one attempt if set to 0 (default is 2)
    void SetCommandRetries( int n );

    // Check NUDP communication socket and camera states.
    char GetNudpState( void ) { return _comm->GetState(); }
    char GetCamState( void );
    
    // stop data :
    void StopData(){ return _comm->StopData(); }

    // Set file names for all logs and error logs. No logging if not called.
    // Logging to screen if compiled with D_DEBUG option.
    bool SetLogFilename( const std::string &fname );
    bool SetErrLogFilename( const std::string &fname );

    // Get LOCAL interface IP and UDP port number.
    ipaddr_t GetLocalInterface( void ) { return _local_interface; }
    port_t GetLocalPort( void ) { return _local_port; }
    // Get LOCAL interface full address as string "ip:port".
    std::string GetLocalAddrStr( void );
    
    // Get REMOTE camera IP and UDP port number.
    ipaddr_t GetRemoteHost( void ) { return _remote_host; }
    port_t GetRemotePort( void ) { return _remote_port; }
    // Get REMOTE camera full address as string "ip:port".
    std::string GetRemoteAddrStr( void );

private:
    bool TransferDone( void );
    static void* BkgListener( void* );
    pthread_mutex_t _nudp_mutex, _wait_mutex;
    pthread_cond_t _nudp_free, _data_done;
    pthread_t _bkg_thread_id;
    bool _stop_bkg, _nudp_req;

    SocketHandler* _h;
    NudpSocket* _comm;
    RefreshThread* _refresh;

    ipaddr_t _local_interface, _remote_host;
    port_t _local_port, _remote_port;
    int _refresh_time, _data_timeout, _cmd_timeout, _cmd_retries;
    bool _opened, _data_taking;
    
    void Log( const char* msg );
    void LogErr( const char* msg );
    FILE *_log, *_err_log;
};

class RefreshThread
{
public:
    RefreshThread( CEthCamera* owner, int t_sec = DEF_REFRESH_TIME );
    ~RefreshThread( void );
    
    int Start( void );
    bool IsAlive( void ) { return _running; }
    void SetTime( int sec ) { _t_sec = sec; }
    
private:
    void Stop( void );
    static void* EntryPoint( void* );

    unsigned int _rcmd;
    CEthCamera* _owner;
    SocketHandler* _handler;
    NudpSocket* _socket;
    pthread_t _thread_id;
    bool _running, _stop;
    int _t_sec;
};
