/*************************************************************************
PURPOSE: (Represent the state of a variable server connection.)
**************************************************************************/

#ifndef VSSESSION_HH
#define VSSESSION_HH

#include <vector>
#include <string>

#include "trick/VariableReference.hh"
#include "trick/ClientConnection.hh"
#include "trick/variable_server_sync_types.h"
#include "trick/tc.h"
#include "trick/variable_server_message_types.h"


namespace Trick {
    class VariableServerSession {
    public:
        VariableServerSession();

        virtual ~VariableServerSession();

        friend std::ostream& operator<< (std::ostream& s, const Trick::VariableServerSession& session);

        /**
         * @brief Set the connection object
         * 
         * @param connection - a pointer to a ClientConnection that is initialized and ready to go
         */
        virtual void set_connection(ClientConnection * connection);

        /**
         @brief Read a message from the connection and act on it
        */
        virtual int  handle_message();

        /**
         @brief Get the pause state of this thread.
        */
        virtual bool get_pause() ;

        /**
         @brief Set the pause state of this thread.
        */
        virtual void set_pause(bool on_off) ;

        /**
         @brief Get the exit command of this session.
        */
        virtual bool get_exit_cmd() ;

        /**
         @brief Command session to exit
        */
        virtual void set_exit_cmd() ;

        /**
         @brief gets the send_stdio flag.
        */
        virtual bool get_send_stdio() ;

        /**
         @brief sets the send_stdio flag.
        */
        virtual int set_send_stdio(bool on_off) ;

        // Called from different types of Trick jobs. 
        // Determines whether this session should be copying at that time, and calls internal copy methods if so
        int copy_and_write_freeze(long long curr_frame);
        int copy_and_write_freeze_scheduled(long long curr_tics);
        int copy_and_write_scheduled(long long curr_tics);
        int copy_and_write_top(long long curr_frame);

        // Called from VariableServerSessionThread
        virtual int copy_and_write_async();

        /**
         @brief Copy given variable values from Trick memory to each variable's output buffer.
            cyclical indicated whether it is a normal cyclical copy or a send_once copy
        */
        virtual int copy_sim_data(std::vector<VariableReference *>& given_vars, bool cyclical);
        virtual int copy_sim_data();

        /**
         @brief Write data from the given var only to the appropriate format (var_ascii or var_binary) from variable output buffers to socket.
        */
        virtual int write_data(std::vector<VariableReference *>& var, VS_MESSAGE_TYPE message_type) ;
        virtual int write_data();

        int write_stdio(int stream, std::string text);

        void disconnect_references();

        virtual long long get_next_tics() const;

        virtual long long get_freeze_next_tics() const;

        int freeze_init();

        virtual double get_update_rate() const;

        void pause_copy();
        void unpause_copy();

        virtual VS_WRITE_MODE get_write_mode () const;
        virtual VS_COPY_MODE get_copy_mode () const;


        /************************************************************************************************/
        /*                               Variable Server Interface                                      */
        /************************************************************************************************/

        /**
         @brief @userdesc Command to add a variable to a list of registered variables for value retrieval.
            The variable server will immediately begin returning the variable values to the client at a
            frequency according to var_cycle.
            @par Python Usage:
            @code trick.var_add("<in_name>") @endcode
            @param in_name - the variable name to retrieve
            @return always 0
        */
        virtual int var_add( std::string in_name ) ;

        /**
         @brief @userdesc Command to add a variable to a list of registered variables for value retrieval,
            and also instructs the variable server to return the value in the specified measurement units.
            The unit specification will be returned after the variable's value (var_ascii mode only).
            Specifying units as "{xx}" will use the unit specification from the variable's model code declaration,
            which is essentially a method of querying a variable's unit specification.
            The variable server will immediately begin returning the variable values/units to the client at
            a frequency according to var_cycle.
            @par Python Usage:
            @code trick.var_add("<in_name>", "<units_name>") @endcode
            @param in_name - the variable name to retrieve
            @param units_name - the desired units, specified within curly braces
            @return always 0
        */
        virtual int var_add( std::string in_name, std::string units_name ) ;

        /**
         @brief @userdesc Command to remove a variable (previously registered with var_add)
            from the list of registered variables for value retrieval.
            @par Python Usage:
            @code trick.var_remove("<in_name>") @endcode
            @param in_name - the variable name to remove
            @return always 0
        */
        virtual int var_remove( std::string in_name ) ;

        /**
         @brief @userdesc Command to instruct the variable server to return the value of a variable
            in the specified units (var_ascii mode only).
            The variable must have been previously registered with the var_add command.
            The unit specification will be returned after the variable's value.
            @par Python Usage:
            @code trick.var_units("<var_name>", "<units_name>") @endcode
            @param var_name - the variable name previously registered with var_add
            @param units_name - the desired units, specified within curly braces
            @return always 0
            @note trick.var_add("my_object.my_variable"); trick.var_units("my_object.my_variable","{m}")
            is the same as trick.var_add("my_object.my_variable","{m}")
        */
        virtual int var_units(std::string var_name , std::string units_name) ;

        /**
         @brief @userdesc Command to instruct the variable server to send a Boolean value indicating
            whether the specified variable exists
            (is a Trick processed variable in the current simulation).
            - var_binary mode: the message indicator is 1, and the returned value is a binary 0 or 1.
            - var_ascii mode: the message indicator is "1" followed by a tab, then an ASCII "0" or "1" returned value.
            .
            @par Python Usage:
            @code trick.var_exists("<in_name>") @endcode
            @param in_name - the variable name to query
            @return always 0
        */
        virtual int var_exists( std::string in_name ) ;

        /**
         @brief @userdesc Command to immediately send the value of a comma separated list of variables
            @par Python Usage:
            @code trick.var_send_once("<in_name_list>", <number of variables in list>) @endcode
            @param in_name_list - the variables name to retrieve, comma separated
            @param num_vars - number of vars in in_name_list
            @return always 0
        */
        virtual int var_send_once(std::string in_name_list, int num_vars);

        /**
         @brief @userdesc Command to instruct the variable server to immediately send back the values of
            variables that are registered with the var_add command
            (typically used when var_pause is in effect). Each var_send command sends back all current values
            once instead of cyclically.
            @par Python Usage:
            @code trick.var_send() @endcode
            @return always 0
        */
        virtual int var_send() ;

        /**
         @brief @userdesc Command to remove all variables from the list of variables currently
            registered with the var_add command,
            so the variable server will no longer send cyclic values until new var_add commands are issued.
            @par Python Usage:
            @code trick.var_clear() @endcode
            @return always 0
        */
        virtual int var_clear() ;

        /**
         @brief @userdesc Turns on validating addresses before they are referenced
            @par Python Usage:
            @code trick.var_validate_address() @endcode
            @return always 0
        */
        virtual int var_validate_address(bool on_off) ;

        /**
         @brief @userdesc Command to instruct the variable server to output debug information.
            @par Python Usage:
            @code trick.var_debug(<level>) @endcode
            @return always 0
            @param level - 1,2,or 3, higher number increases amount of info output
        */
        virtual int var_debug(int level) ;

        /**
         @brief @userdesc Command to instruct the variable server to return values in ASCII format (this is the default).
            @par Python Usage:
            @code trick.var_ascii() @endcode
            @return always 0
        */
        virtual int var_ascii() ;

        /**
         @brief @userdesc Command to instruct the variable server to return values in binary format.
            @par Python Usage:
            @code trick.var_binary() @endcode
            @return always 0
        */
        virtual int var_binary() ;

        /**
         @brief @userdesc Command to instruct the variable server to return values in binary format,
            but saves space in the returned messages
            by omitting the variable names.
            @par Python Usage:
            @code trick.var_binary_nonames() @endcode
            @return always 0
        */
        virtual int var_binary_nonames() ;

        /**
         @brief @userdesc Command to tell the server when to copy data
            - VS_COPY_ASYNC = copies data asynchronously. (default)
            - VS_COPY_SCHEDULED = copies data as an automatic_last job in main thread
            - VS_COPY_TOP_OF_FRAME = copies data at top of frame
            @par Python Usage:
            @code trick.var_set_copy_mode(<mode>) @endcode
            @param mode - One of the above enumerations
            @return 0 if successful, -1 if error
        */
        virtual int var_set_copy_mode(int mode) ;

        /**
         @brief @userdesc Command to tell the server when to copy data
            - VS_WRITE_ASYNC = writes data asynchronously. (default)
            - VS_WRITE_WHEN_COPIED = writes data as soon as it's copied from sim
            @par Python Usage:
            @code trick.var_set_write_mode(<mode>) @endcode
            @param mode - One of the above enumerations
            @return 0 if successful, -1 if error
        */
        virtual int var_set_write_mode(int mode) ;

        /**
         @brief @userdesc Command to put the current variable server/client connection in sync mode,
            so that values to be sent to the client
            are guaranteed to be homogenous, that is from the same execution frame (the default is asynchronous).
            - async/async mode: the variable server thread itself copies the client data,
            independent of the execution frame so may not be homogenous.
            - sync/async mode: a variable server automatic_last job retrieves client requested data.
            Data is sent asynchronously on separate thread.
            - sync/sync mode: a variable server automatic_last job retrieves client requested data.
            Data is sent in same automatic_last job.
            .
            @par Python Usage:
            @code trick.var_sync(<on_off>) @endcode
            @param on_off - 0 = fully asynchronous. 1 = sync data gather, async socket write.
            2 = sync data gather, sync socket write
            @return always 0
        */
        virtual int var_sync(int on_off) ;

        /**
         @brief @userdesc Set the frame multiple
            @par Python Usage:
            @code trick.var_set_frame_multiple(<mult>) @endcode
            @param mult - The requested multiple
            @return 0
        */
        virtual int var_set_frame_multiple(unsigned int mult) ;

        /**
         @brief @userdesc Set the frame offset
            @par Python Usage:
            @code trick.var_set_frame_offset(<offset>) @endcode
            @param offset - The requested offset
            @return 0
        */
        virtual int var_set_frame_offset(unsigned int offset) ;

        /**
         @brief @userdesc Set the frame multiple
            @par Python Usage:
            @code trick.var_set_freeze_frame_multiple(<mult>) @endcode
            @param mult - The requested multiple
            @return 0
        */
        virtual int var_set_freeze_frame_multiple(unsigned int mult) ;

        /**
         @brief @userdesc Set the frame offset
            @par Python Usage:
            @code trick.var_set_freeze_frame_offset(<offset>) @endcode
            @param offset - The requested offset
            @return 0
        */
        virtual int var_set_freeze_frame_offset(unsigned int offset) ;

        /**
         @brief @userdesc Command to instruct the variable server to byteswap the return values
            (only has an effect in var_binary mode).
            The default is no byteswap - it is assumed server and client are same endianness.
            If not, the user must issue the var_byteswap command.
            @par Python Usage:
            @code trick.var_byteswap(<on_off>) @endcode
            @param on_off - true (or 1) to byteswap the return data, false (or 0) to return data as is
            @return always 0
        */
        virtual int var_byteswap(bool on_off) ;

        /**
         @brief @userdesc Command to toggle variable server logged messages to a playback file.
            All messages received from all clients will be saved to file named "playback" in the RUN directory.
            @par Python Usage:
            @code trick.set_log(<on_off>) @endcode
            @return always 0
        */
        virtual int set_log(bool on_off) ;

        /**
         @brief @userdesc Command to toggle variable server session log.
            Each session will create a file containing the IP and port of client and all messages.
            The file will be created in a session_log/ subdirectory under the RUN directory.
            @par Python Usage:
            @code trick.set_session_log(<on_off>) @endcode
            @return always 0
        */
        virtual int set_session_log (bool on_off) ;

        /**
         @brief @userdesc Command to set info messages.
            @return always 0
        */
        virtual int set_info_message(bool on) ;

        /**
         @brief Command to send the number of items in the var_add list.
            The variable server sends a message indicator of "3", followed by the total number of variables being sent.
        */
        virtual int send_list_size();

        /**
         @brief Special command to instruct the variable server to send the contents of the S_sie.resource file; used by TV.
            The variable server sends a message indicator of "2", followed by a tab, followed by the file contents
            which are then sent as sequential ASCII messages with a maximum size of 4096 bytes each.
        */
        virtual int send_sie_resource();

        /**
         @brief Special command to only send the class sie class information
        */
        virtual int send_sie_class();

        /**
         @brief Special command to only send the enumeration sie class information
        */
        virtual int send_sie_enum();

        /**
         @brief Special command to only send the top level objects sie class information
        */
        virtual int send_sie_top_level_objects();

        /**
         @brief Special command to send an arbitrary file through the variable server.
        */
        virtual int send_file(std::string file_name);

        /**
         @brief @userdesc Command to set the frequencty at which the variable server will send values
            of variables that have been registered using
            the var_add command. If var_cycle is not specified, the default cycle is 0.1 seconds.
            @par Python Usage:
            @code trick.var_cycle(<in_cycle>) @endcode
            @param in_cycle - the desired frequency in seconds
            @return always 0
        */
        virtual int var_cycle(double in_cycle) ;

        /**
         @brief @userdesc Command exit this variable server session.
            @par Python Usage:
            @code trick.var_exit() @endcode
            @return always 0
        */
        virtual int var_exit();

    private:
        static int instance_counter;
        static std::string log_subdir;

        pthread_mutex_t _copy_mutex;     /**<  trick_io(**) */

        ClientConnection * _connection;  /**<  trick_io(**) */

        // Helper method to send a file to connection
        virtual int transmit_file(std::string sie_file);

        // Helper methods to write out formatted data
        virtual int write_binary_data(const std::vector<VariableReference *>& given_vars, VS_MESSAGE_TYPE message_type);
        virtual int write_ascii_data(const std::vector<VariableReference *>& given_vars, VS_MESSAGE_TYPE message_type );

        virtual VariableReference * find_session_variable(std::string name) const;

        std::vector<VariableReference *> _session_variables; /**<  trick_io(**) */

        // Getters and setters for internal variables
        virtual long long get_cycle_tics() const; 

        virtual void set_freeze_next_tics(long long tics); 
        virtual void set_next_tics(long long tics); 

        virtual int get_frame_multiple () const;
        virtual int get_frame_offset () const;
        virtual int get_freeze_frame_multiple () const;
        virtual int get_freeze_frame_offset () const;
        virtual bool get_enabled () const;

        // Check settings and log to appropriate places
        void log_received_message(const std::string& msg);
        void log_connection_opened ();

        bool is_session_log_open();

        void open_session_log();

        void write_to_session_log(const std::string& msg);

        /** Value set in var_cycle command.\n */
        double _update_rate ;             /**<  trick_io(**) */

        /** The update rate in integer tics.\n */
        long long _cycle_tics ;           /**<  trick_io(**) */

        /** The next call time in integer tics of the job to copy client data (sync mode).\n */
        long long _next_tics ;            /**<  trick_io(**) */

        /** The next call time in integer tics of the job to copy client data (sync mode).\n */
        long long _freeze_next_tics ;     /**<  trick_io(**) */

        /** The simulation time converted to seconds\n */
        double _time ;                    /**<  trick_units(s) */

         /** Toggle to set variable server copy as top_of_frame, scheduled, async \n */
        VS_COPY_MODE _copy_mode ;         /**<  trick_io(**) */

        /** Toggle to set variable server writes as when copied or async.\n */
        VS_WRITE_MODE _write_mode ;       /**<  trick_io(**) */

        /** multiples of frame_count to copy data.  Only used at top_of_frame\n */
        int _frame_multiple ;             /**<  trick_io(**) */

        /** multiples of frame_count to copy data.  Only used at top_of_frame\n */
        int _frame_offset ;               /**<  trick_io(**) */

        /** multiples of frame_count to copy data.  Only used at top_of_frame\n */
        int _freeze_frame_multiple ;      /**<  trick_io(**) */

        /** multiples of frame_count to copy data.  Only used at top_of_frame\n */
        int _freeze_frame_offset ;        /**<  trick_io(**) */

        /** Toggle to tell variable server to byteswap returned values.\n */
        bool _byteswap ;                  /**<  trick_io(**) */

        /** Toggle to tell variable server return data in binary format.\n */
        bool _binary_data ;               /**<  trick_io(**) */

        /** Toggle to tell variable server return data in binary format without the variable names.\n */
        bool _binary_data_nonames ;       /**<  trick_io(**) */

        /** Value (1,2,or 3) that causes the variable server to output increasing amounts of debug information.\n */
        int _debug ;                      /**<  trick_io(**) */

        /** Toggle to enable/disable this variable server thread.\n */
        bool _enabled ;                   /**<  trick_io(**) */

        /** Toggle to turn on/off variable server logged messages to a playback file.\n */
        bool _log ;                       /**< trick_io(**)  */

        /** Toggle to turn on/off individual variable server session logs.\n */
        bool _session_log;              /**< trick_io(**)  */

        /** Toggle to turn on/off debug info messages.\n */
        bool _info_msg ;

        /** Message stream number for the session log file */
        int _session_log_msg_stream;

        /** Toggle to indicate var_pause commanded.\n */
        bool _pause_cmd ;                 /**<  trick_io(**) */

        /** Save pause state while reloading a checkpoint.\n */
        bool _saved_pause_cmd ;           /**<  trick_io(**) */

        bool _send_stdio;

        bool _validate_address;

        /** Toggle to indicate var_exit commanded.\n */
        bool _exit_cmd ;                  /**<  trick_io(**) */

        int _instance_num;

    };
}

#endif
