#ifndef DBQOP_H
#define DBQOP_H


//FOR LINUX OPERATIVE SYSTEM
#ifdef __linux__
#ifdef BUILD_DBQOP
#define DBQOP_ATTRS extern __attribute__((visibility("default")))
#define DBQOP_STRUCT_EXPORT __attribute__((visibility("default")))
#define DBQOP_DTS_ATTRS
#else
#define DBQOP_ATTRS extern
#define DBQOP_STRUCT_EXPORT
#define DBQOP_DTS_ATTRS
#endif // BUILD_DBQOP
#endif // __linux__

//FOR WINDOWS OPERATIVE SYSTEM
#ifdef __windows__
#ifdef BUILD_DBQOP
#define DBQOP_ATTRS extern __declspec(dllexport)
#define DBQOP_STRUCT_EXPORT __declspec(dllexport)
#define DBQOP_DTS_ATTRS STDCALL
#else
#define DBQOP_ATTRS extern __declspec(dllimport)
#define DBQOP_STRUCT_EXPORT __declspec(dllimport)
#define DBQOP_DTS_ATTRS STDCALL
#endif // BUILD_DBQOP
#include <windows.h>
#include <windef.h>
#endif // __windows__


//THE ORDER OF THESE INCLUDES MATTER

#include <iostream>
#include <cstring>
#include <vector>
#include <ctime>
#include <typeinfo>
#include <string>
#include <sql.h>
#include <sqltypes.h>
#include <sqlext.h>
#include <algorithm>
#include "interop.h"
#include "binrw.h"

#define MAX_COL_TYPE_NAME_LENGTH 64
#define MAX_TYPE_LENGTH sizeof(LONGLONG)
#define MAX_COMMAND_LENGTH 1048576//1024*1024
#define MAX_ERROR_MESSAGES 1024



using namespace canonbit_definitions::interoperatibility;
using namespace canonbit_algorithms::binary_reader_writer;
namespace brw=canonbit_algorithms::binary_reader_writer;

typedef SQLCHAR*              PSQLCHAR;
typedef SQLWCHAR*             PSQLWCHAR;
typedef SQLSMALLINT*          PSQLSMALLINT;
typedef SQLUSMALLINT*         PSQLUSMALLINT;
typedef SQLINTEGER*           PSQLINTEGER;
typedef SQLLEN*               PSQLLEN;
typedef SQL_TIMESTAMP_STRUCT* PTIMESTAMP;
typedef struct DataBuffer
{
    SQLLEN   buffer_size;
    PSQLCHAR buffer;
} CellData;
typedef std::vector<CellData>      RowData;
typedef std::vector<RowData>       TableData;//Collections of row buffers pointers


namespace canonbit_databases{

namespace odbc_database_operations{

    enum TABLE_COLUMN_TYPES
    {
        PRIMARY_KEY=0,
        FOREIGN_KEY,
        DATA_COLUMN
    };

    //Database connection parameters and values
    struct DBQOP_STRUCT_EXPORT DBConnection
    {
        DBConnection();
        DBConnection(const DBConnection& other);
        ~DBConnection();
        DBConnection& operator=(const DBConnection& rhs);
        SQLHENV     environment_handle;//Environment handle
        SQLHDBC     database_connection_handle;//Database connection handle
        bool        environment_handle_allocated;
        bool        connection_handle_allocated;
        bool        sql_command_prepared;
        bool        asynch_support;
        bool        is_connected;
        bool        is_open;
        int         flag;
        std::string connection_string;
        std::string output_connection_string;
        std::string server_string;
        std::string user_string;
        std::string password_string;
        std::string last_message;
    };
    struct DBQOP_STRUCT_EXPORT DBColumn
    {
        DBColumn();
        ~DBColumn();
        explicit DBColumn(const short& new_column_type,const long& buffer_length);
        DBColumn(const DBColumn& other);
        DBColumn& operator=(const DBColumn& rhs);
        brw::Archive& operator&(brw::Archive& ar);
        uintptr_t table_owner;
        short     column_type;
        short     catalog_name_length;
        short     schema_name_length;
        short     table_name_length;
        short     column_name_length;
        short     column_type_name_length;
        long      data_length_indicator;
        long      column_buffer_length;//Max byte length of the column
        SQLCHAR   catalog_name[SQL_MAX_CATALOG_NAME_LEN];
        SQLCHAR   schema_name[SQL_MAX_SCHEMA_NAME_LEN];
        SQLCHAR   table_name[SQL_MAX_TABLE_NAME_LEN];
        SQLCHAR   column_name[SQL_MAX_COLUMN_NAME_LEN];
        SQLCHAR   column_type_name[MAX_COL_TYPE_NAME_LENGTH];
        PSQLCHAR  column_buffer;
    };
    struct DBKeyColumn
    {
        SQLCHAR     key_catalog_name[SQL_MAX_CATALOG_NAME_LEN];
        SQLCHAR     key_schema_name[SQL_MAX_SCHEMA_NAME_LEN];
        SQLCHAR     key_table_name[SQL_MAX_TABLE_NAME_LEN];
        SQLCHAR     key_column_name[SQL_MAX_COLUMN_NAME_LEN];
        SQLSMALLINT key_ordinal;
        SQLCHAR     key_name[SQL_MAX_COLUMN_NAME_LEN];
        SQLSMALLINT key_catalog_name_length;
        SQLSMALLINT key_schema_name_length;
        SQLSMALLINT key_table_name_length;
        SQLSMALLINT key_column_name_length;
        SQLSMALLINT key_name_length;
    };
    struct DBQOP_STRUCT_EXPORT DBRow
    {
    public:
        DBRow();
        DBRow(const DBRow& other);
        explicit DBRow(const uintptr_t& row_table_owner,const UINT& new_row_index);
        DBRow& operator=(const DBRow& rhs);
        ~DBRow();
        uintptr_t table_owner;
        UINT      row_index;
        void*     getValue(const short& column_index) const;
        void      getValue(const short& column_index,CellData& value) const;
        void      setValue(const short& column_index,void* value) const;
        void      setValue(const short& column_index,const CellData& value);
        DBRow     cloneRow() const;
    private:
        RowData   row_data;
    };
    struct DBQOP_STRUCT_EXPORT DBTable
    {
    public:
        DBTable();
        ~DBTable();
        explicit DBTable(const short& new_total_columns,DBColumn* new_table_columns);
        DBTable(const DBTable& other);
        DBTable& operator=(const DBTable& rhs);
        brw::Archive& operator&(brw::Archive& ar);
        std::string sql_statement;
        std::string database_name;
        std::string catalog_name;
        std::string schema_name;
        std::string table_name;
        short       total_columns;
        short       total_primary_key_columns;
        short       total_foreign_key_columns;
        short*      primary_key_columns;//Ordinals in zero based index
        short*      foreign_key_columns;//Ordinals in zero based index
        UINT        total_rows;
        DBColumn*   table_columns;
        DBRow       newRow() const;
        DBRow       getRow(const UINT& row_index) const;
        void        addRow(UINT& new_row_index);
        void        addRow(const DBRow& new_row);
        void*       getValue(const UINT& row_index,const short& column_index) const;
        void getValue
            (
                const UINT& row_index,
                const short& column_index,
                CellData& value
            ) const;
        void setValue
            (
                const UINT& row_index,
                const short& column_index,
                void* value,
                const long& value_length=1
            );
        void setValue
            (
                const UINT& row_index,
                const short& column_index,
                const CellData& value
            );
        template<typename T>
        T getValue(const UINT& row_index,const short& column_index)
        {
            T value;
            T* value_ptr=reinterpret_cast<T*>(getValue(row_index,column_index));
            if(value_ptr)
            {
                value=*value_ptr;
            }
            return value;
        }
        bool findRow
            (
                const short& key_column_index,
                const CellData& value,
                const UINT& starting_row_index,
                DBRow& row
            );/*Binary search for a complete match value*/
        bool findRowPattern
            (
                const short& key_column_index,
                const std::string& value,
                const UINT& starting_row_index,
                DBRow& row
            ) const;/*String search with pattern*/
        bool getPrimaryKeys
            (
                short*& primary_keys,
                short& total_primary_keys,
                std::string& message
            );
        bool getForeignKeys
            (
                short*& foreign_keys,
                short& total_foreign_keys,
                std::string& message
            );

        DBTable cloneTable();//Clone table schema and data
        DBTable cloneSchema();//Just for cloning table schema
        DBTable getSubTable
            (
                const short columns_indices[],
                const short& total_sub_columns
            );
        void splitPKFKDC
            (
                DBTable& primary_keys,
                DBTable& foreign_keys,
                DBTable& data_columns,
                bool& has_primary_keys,
                bool& has_foreign_keys
            );
        friend SQLHSTMT& getStatementHandle(DBTable& table);
        friend void      setStatementHandle(DBTable& table,const SQLHSTMT handle);

    private:
        SQLHSTMT  statement_handle;//Statement handle
        TableData table_data;
        bool      is_primary_keys_set;
        bool      is_foreign_keys_set;
    };
    struct DBQOP_STRUCT_EXPORT DBDataSet
    {
        DBDataSet();
        explicit DBDataSet
            (
                const std::string& sql_statement,
                const std::string& driver,
                const std::string& server,
                const std::string& user,
                const std::string& password
            );
        ~DBDataSet();
        DBConnection                   data_connection;
        std::vector<DBTable>           data_tables;
        std::vector<DBTable>::iterator begin();
        std::vector<DBTable>::iterator end();
        int                            total_data_tables;
    };
    struct DBDataRelation
    {
        DBTable* table;
        short    column_ordinal;
    };

    //METHODS THE CONNECTION ARGUMENT WILL HOLD THE OUTPUT MESSAGE IN CASE
    //OF ERROR OR WARNING
    DBQOP_ATTRS
    bool DBConnect
        (
            DBConnection& connection,
            const std::string& driver,
            const std::string& server,
            const std::string& user,
            const std::string& password
        );

    DBQOP_ATTRS
    bool DBDisconnect(DBConnection& connection);

    DBQOP_ATTRS
    VECSTR DBGetDrivers(DBConnection& connection);

    DBQOP_ATTRS
    VECSTR DBGetDataSources(DBConnection& connection);

    DBQOP_ATTRS
    bool DBSelect
        (
            DBConnection& connection,
            const std::string& sqlcmd,
            DBTable& select_table
        );

    DBQOP_ATTRS
    bool DBInsert
        (
            DBConnection& connection,
            const std::string& sqlcmd,
            const DBTable& rows_to_insert,
            DBTable& rows_rejected
        );

    DBQOP_ATTRS
    bool DBUpdate
        (
            DBConnection& connection,
            const std::string& sqlcmd,
            const DBTable& rows_to_update,
            const DBTable& primary_keys_values,
            DBTable& rows_rejected
        );

    DBQOP_ATTRS
    bool DBDelete
        (
            DBConnection& connection,
            const std::string& sqlcmd,
            const DBTable& primary_keys_values,
            DBTable& rows_rejected
        );

    DBQOP_ATTRS
    bool DBGetPrimaryKeys
        (
            DBTable& table,
            DBKeyColumn*& primary_keys,
            short& total_primary_keys,
            std::string& message
        );

    DBQOP_ATTRS
    bool DBGetForeignKeys
        (
            DBTable& table,
            DBKeyColumn*& foreign_keys,
            short& total_foreign_keys,
            std::string& message
        );

    DBQOP_ATTRS
    bool DBTransaction(const std::string& sqlcmd,std::string& message);

    DBQOP_ATTRS
    short DBGetNumberOfColumns(SQLHSTMT& statement_handle);//Statement handle must be prepared or executed

    DBQOP_ATTRS
    UINT DBGetNumberOfRows(SQLHSTMT& statement_handle);//Statement handle must be prepared or executed

    DBQOP_ATTRS
    std::string DBGetErrorMsg(SQLHANDLE handle,SQLSMALLINT handle_type);

    DBQOP_ATTRS
    std::string GetSQLTypeName(const short sql_type_id,bool& is_type_variable);

//==========================================================================================
//METHODS FOR INTEROPERABILITY WITH EXCEL AND LIBREOFFICE MACROS
    DBQOP_ATTRS DBQOP_DTS_ATTRS
    bool DTSDBConnect
        (
            const char*& driver,
            const char*& server,
            const char*& user,
            const char*& password
        );

    DBQOP_ATTRS DBQOP_DTS_ATTRS
    bool DTSDBDisconnect();

    DBQOP_ATTRS DBQOP_DTS_ATTRS
    bool DTSDBSelect(const char*& sqlcmd);

    DBQOP_ATTRS DBQOP_DTS_ATTRS
    bool DTSDBInsert(const char*& sqlcmd);

    DBQOP_ATTRS DBQOP_DTS_ATTRS
    bool DTSDBUpdate(const char*& sqlcmd);

    DBQOP_ATTRS DBQOP_DTS_ATTRS
    bool DTSDBDelete(const char*& sqlcmd);

    DBQOP_ATTRS DBQOP_DTS_ATTRS
    void DTSDBSetColumnIndex(const short& index);

    DBQOP_ATTRS DBQOP_DTS_ATTRS
    int DTSDBGetColumnType(const short& index);




#ifdef BUILD_DBQOP
//==========================================================================================
    //VERY IMPORTANT VARIABLES THAT ARE GOING TO BE USED AS A BUFFER
    //TO HOLD VARIABLE DATA ON FETCHING
    static RowData sql_cmd_buffer;
//==========================================================================================

//==========================================================================================
    //VERY IMPORTANT VARIABLES THAT ARE GOING TO BE USED WITH DATASHEET APPLICATIONS
    static DBConnection dts_connection;
    static DBTable      dts_select_table;
    static DBTable      dts_rows_to_insert;
    static DBTable      dts_rows_to_update;
    static DBTable      dts_primary_keys_values;
    static DBTable      dts_rows_rejected;
    static short        dts_current_column_index;
//==========================================================================================



    static
    bool DBGetColumnsAndBind
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const short& total_columns,
            DBColumn*& columns
        );

    static
    bool DBBindInsertParameters
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const short& total_columns,
            DBColumn*& columns
        );

    static
    bool DBBindUpdateParameters
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const short& total_data_columns,
            const short& total_primary_keys,
            DBColumn*& data_columns,
            DBColumn*& primary_keys
        );

    static
    bool DBBindDeleteParameters
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const short& total_primary_keys,
            DBColumn*& primary_keys
        );

    static
    bool DBPrepareSelect
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const std::string& sqlcmd
        );

    static
    bool DBPrepareInsert
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const std::string& sqlcmd
        );

    static
    bool DBPrepareUpdate
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const std::string& sqlcmd
        );

    static
    bool DBPrepareDelete
        (
            DBConnection& connection,
            SQLHSTMT& statement_handle,
            const std::string& sqlcmd
        );


#endif // BUILD_DBQOP

}//odbc_database_operations namespace

}//canonbit_databases namespace

#endif // DBQOP_H