/*
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

#ifndef CURSOR_H
#define CURSOR_H

struct Connection;

struct ColumnInfo
{
    SQLSMALLINT sql_type;

    // The column size from SQLDescribeCol.  For character types, this is the maximum length, not including the NULL
    // terminator.  For binary values, this is the maximum length.  For numeric and decimal values, it is the defined
    // number of digits. For example, the precision of a column defined as NUMERIC(10,3) is 10.
    //
    // This value can be SQL_NO_TOTAL in which case the driver doesn't know the maximum length, such as for LONGVARCHAR
    // fields.
    SQLULEN column_size;

    // Tells us if an integer type is signed or unsigned.  This is determined after a query using SQLColAttribute.  All
    // of the integer types are the same size whether signed and unsigned, so we can allocate memory ahead of time
    // without knowing this.  We use this during the fetch when converting to a Python integer or long.
    bool is_unsigned;
};

struct ParamInfo
{
    // The following correspond to the SQLBindParameter parameters.
    SQLSMALLINT ValueType;
    SQLSMALLINT ParameterType;
    SQLSMALLINT IOType;
    SQLULEN     ColumnSize;
    SQLSMALLINT DecimalDigits;

    // The value pointer that will be bound.  If `alloc` is true, this was allocated with malloc and must be freed.
    // Otherwise it is zero or points into memory owned by the original Python parameter.
    SQLPOINTER ParameterValuePtr;

    SQLLEN BufferLength;
    SQLLEN StrLen_or_Ind;

    // If true, the memory in ParameterValuePtr was allocated via malloc and must be freed.
    bool allocated;

    PyObject* pObject;
    // An optional object that will be decremented at the end of the execute.
    // This is useful when the ParameterValuePtr data is in a Python object -
    // the object can be put here (and INCREFed if necessary!) instead of
    // copying the data out.
    //
    // If SQLPutData is used, this must be set to a bytes or bytearray object!

    SQLLEN maxlength;
    // If SQLPutData is being used, this must be set to the amount that can be
    // written to each SQLPutData call.  (It is not clear if they are limited
    // like SQLBindParameter or not.)

    // For TVPs, the nested descriptors and current row.
    struct ParamInfo *nested;
    SQLLEN curTvpRow;

    // Optional data.  If used, ParameterValuePtr will point into this.
    union
    {
        unsigned char ch;
        int i32;
        INT64 i64;
        double dbl;
        TIMESTAMP_STRUCT timestamp;
        DATE_STRUCT date;
        TIME_STRUCT time;
    } Data;
};

struct Cursor
{
    PyObject_HEAD

    // The Connection object (which is a PyObject) that created this cursor.
    Connection* cnxn;

    // Set to SQL_NULL_HANDLE when the cursor is closed.
    HSTMT hstmt;

    //
    // SQL Parameters
    //

    // If non-zero, a pointer to the previously prepared SQL string, allowing us to skip the prepare and gathering of
    // parameter data.
    PyObject* pPreparedSQL;

    // The number of parameter markers in pPreparedSQL.  This will be zero when pPreparedSQL is zero but is set
    // immediately after preparing the SQL.
    int paramcount;

    // If non-zero, a pointer to an array of SQL type values allocated via malloc.  This is zero until we actually ask
    // for the type of parameter, which is only when a parameter is None (NULL).  At that point, the entire array is
    // allocated (length == paramcount) but all entries are set to SQL_UNKNOWN_TYPE.
    SQLSMALLINT* paramtypes;

    // If non-zero, a pointer to a buffer containing the actual parameters bound.  If pPreparedSQL is zero, this should
    // be freed using free and set to zero.
    //
    // Even if the same SQL statement is executed twice, the parameter bindings are redone from scratch since we try to
    // bind into the Python objects directly.
    ParamInfo* paramInfos;

    // Parameter set array (used with executemany)
    unsigned char *paramArray;
    
    // Whether to use fast executemany with parameter arrays and other optimisations
    char fastexecmany;
    
    // The list of information for setinputsizes().
    PyObject *inputsizes;

    //
    // Result Information
    //

    // An array of ColumnInfos, allocated via malloc.  This will be zero when closed or when there are no query
    // results.
    ColumnInfo* colinfos;

    // The description tuple described in the DB API 2.0 specification.  Set to None when there are no results.
    PyObject* description;

    int arraysize;

    // The Cursor.rowcount attribute from the DB API specification.
    int rowcount;

    // A dictionary that maps from column name (PyString) to index into the result columns (PyInteger).  This is
    // constructed during an execute and shared with each row (reference counted) to implement accessing results by
    // column name.
    //
    // This duplicates some ODBC functionality, but allows us to use Row objects after the statement is closed and
    // should use less memory than putting each column into the Row's __dict__.
    //
    // Since this is shared by Row objects, it cannot be reused.  New dictionaries are created for every execute.  This
    // will be zero whenever there are no results.
    PyObject* map_name_to_index;

    // The messages attribute described in the DB API 2.0 specification.
    // Contains a list of all non-data messages provided by the driver, retrieved using SQLGetDiagRec.
    PyObject* messages;
};

int GetDiagRecs(Cursor* cur);

void Cursor_init();

Cursor* Cursor_New(Connection* cnxn);
PyObject* Cursor_execute(PyObject* self, PyObject* args);

#endif
