var
    net = require('net'),
    os = require('os'),
    Events = require('events'),
    stream = require('stream'),
    serialize = require('./serialize.js'),
    XdrReader = serialize.XdrReader,
    BlrReader = serialize.BlrReader,
    XdrWriter = serialize.XdrWriter,
    BlrWriter = serialize.BlrWriter,
	BitSet = serialize.BitSet,
    messages = require('./messages.js'),
    crypt = require('./unix-crypt.js'),
    path = require('path'),
	srp = require('./srp'),
	BigInt = require('big-integer');

if (typeof(setImmediate) === 'undefined') {
    global.setImmediate = function(cb) {
        process.nextTick(cb);
    };
}

/**
 * Parse date from string
 * @return {Date}
 */
if (String.prototype.parseDate === undefined) {
    String.prototype.parseDate = function() {
        var self = this.trim();
        var arr = self.indexOf(' ') === -1 ? self.split('T') : self.split(' ');
        var index = arr[0].indexOf(':');
        var length = arr[0].length;

        if (index !== -1) {
            var tmp = arr[1];
            arr[1] = arr[0];
            arr[0] = tmp;
        }

        if (arr[0] === undefined)
            arr[0] = '';

        var noTime = arr[1] === undefined ? true : arr[1].length === 0;

        for (var i = 0; i < length; i++) {
            var c = arr[0].charCodeAt(i);
            if (c > 47 && c < 58)
                continue;
            if (c === 45 || c === 46)
                continue;

            if (noTime)
                return new Date(self);
        }

        if (arr[1] === undefined)
            arr[1] = '00:00:00';

        var firstDay = arr[0].indexOf('-') === -1;

        var date = (arr[0] || '').split(firstDay ? '.' : '-');
        var time = (arr[1] || '').split(':');
        var parsed = [];

        if (date.length < 4 && time.length < 2) {
            return new Date(self);
        }

        index = (time[2] || '').indexOf('.');

        // milliseconds
        if (index !== -1) {
            time[3] = time[2].substring(index + 1);
            time[2] = time[2].substring(0, index);
        } else {
            time[3] = '0';
        }

        parsed.push(parseInt(date[firstDay ? 2 : 0], 10)); // year
        parsed.push(parseInt(date[1], 10)); // month
        parsed.push(parseInt(date[firstDay ? 0 : 2], 10)); // day
        parsed.push(parseInt(time[0], 10)); // hours
        parsed.push(parseInt(time[1], 10)); // minutes
        parsed.push(parseInt(time[2], 10)); // seconds
        parsed.push(parseInt(time[3], 10)); // miliseconds

        var def = new Date();

        for (var i = 0, length = parsed.length; i < length; i++) {
            if (isNaN(parsed[i]))
                parsed[i] = 0;

            var value = parsed[i];
            if (value !== 0)
                continue;

            switch (i) {
                case 0:
                    if (value <= 0)
                        parsed[i] = def.getFullYear();
                    break;
                case 1:
                    if (value <= 0)
                        parsed[i] = def.getMonth() + 1;
                    break;
                case 2:
                    if (value <= 0)
                        parsed[i] = def.getDate();
                    break;
            }
        }

        return new Date(parsed[0], parsed[1] - 1, parsed[2], parsed[3], parsed[4], parsed[5]);
    };
}

function noop() {}

const
    MAX_BUFFER_SIZE = 8192;

const
    op_void                   = 0,  // Packet has been voided
    op_connect                = 1,  // Connect to remote server
    op_exit                   = 2,  // Remote end has exitted
    op_accept                 = 3,  // Server accepts connection
    op_reject                 = 4,  // Server rejects connection
    op_disconnect             = 6,  // Connect is going away
    op_response               = 9,  // Generic response block

    // Full context server operations

    op_attach                 = 19, // Attach database
    op_create                 = 20, // Create database
    op_detach                 = 21, // Detach database
    op_compile                = 22, // Request based operations
    op_start                  = 23,
    op_start_and_send         = 24,
    op_send                   = 25,
    op_receive                = 26,
    op_unwind                 = 27, // apparently unused, see protocol.cpp's case op_unwind
    op_release                = 28,

    op_transaction            = 29, // Transaction operations
    op_commit                 = 30,
    op_rollback               = 31,
    op_prepare                = 32,
    op_reconnect              = 33,

    op_create_blob            = 34, // Blob operations
    op_open_blob              = 35,
    op_get_segment            = 36,
    op_put_segment            = 37,
    op_cancel_blob            = 38,
    op_close_blob             = 39,

    op_info_database          = 40, // Information services
    op_info_request           = 41,
    op_info_transaction       = 42,
    op_info_blob              = 43,

    op_batch_segments         = 44, // Put a bunch of blob segments

    op_que_events             = 48, // Que event notification request
    op_cancel_events          = 49, // Cancel event notification request
    op_commit_retaining       = 50, // Commit retaining (what else)
    op_prepare2               = 51, // Message form of prepare
    op_event                  = 52, // Completed event request (asynchronous)
    op_connect_request        = 53, // Request to establish connection
    op_aux_connect            = 54, // Establish auxiliary connection
    op_ddl                    = 55, // DDL call
    op_open_blob2             = 56,
    op_create_blob2           = 57,
    op_get_slice              = 58,
    op_put_slice              = 59,
    op_slice                  = 60, // Successful response to op_get_slice
    op_seek_blob              = 61, // Blob seek operation

// DSQL operations

    op_allocate_statement     = 62, // allocate a statment handle
    op_execute                = 63, // execute a prepared statement
    op_exec_immediate         = 64, // execute a statement
    op_fetch                  = 65, // fetch a record
    op_fetch_response         = 66, // response for record fetch
    op_free_statement         = 67, // free a statement
    op_prepare_statement      = 68, // prepare a statement
    op_set_cursor             = 69, // set a cursor name
    op_info_sql               = 70,

    op_dummy                  = 71, // dummy packet to detect loss of client
    op_response_piggyback     = 72, // response block for piggybacked messages
    op_start_and_receive      = 73,
    op_start_send_and_receive = 74,
    op_exec_immediate2        = 75, // execute an immediate statement with msgs
    op_execute2               = 76, // execute a statement with msgs
    op_insert                 = 77,
    op_sql_response           = 78, // response from execute, exec immed, insert
    op_transact               = 79,
    op_transact_response      = 80,
    op_drop_database          = 81,
    op_service_attach         = 82,
    op_service_detach         = 83,
    op_service_info           = 84,
    op_service_start          = 85,
    op_rollback_retaining     = 86,
    op_partial                = 89, // packet is not complete - delay processing
    op_trusted_auth           = 90,
    op_cancel                 = 91,
    op_cont_auth              = 92,
    op_ping                   = 93,
    op_accept_data            = 94, // Server accepts connection and returns some data to client
    op_abort_aux_connection   = 95, // Async operation - stop waiting for async connection to arrive
    op_crypt                  = 96,
    op_crypt_key_callback     = 97,
    op_cond_accept            = 98; // Server accepts connection, returns some data to client
                                    // and asks client to continue authentication before attach call

const
    CONNECT_VERSION2          = 2,
    CONNECT_VERSION3          = 3,
    ARCHITECTURE_GENERIC      = 1;

const
	// Protocol 10 includes support for warnings and removes the requirement for
	// encoding and decoding status codes
    PROTOCOL_VERSION10  = 10,

	// Since protocol 11 we must be separated from Borland Interbase.
	// Therefore always set highmost bit in protocol version to 1.
	// For unsigned protocol version this does not break version's compare.

    FB_PROTOCOL_FLAG    = 0x8000,
    FB_PROTOCOL_MASK    = ~FB_PROTOCOL_FLAG & 0xFFFF,

	// Protocol 11 has support for user authentication related
	// operations (op_update_account_info, op_authenticate_user and
	// op_trusted_auth). When specific operation is not supported,
	// we say "sorry".

    PROTOCOL_VERSION11  = (FB_PROTOCOL_FLAG | 11),

	// Protocol 12 has support for asynchronous call op_cancel.
	// Currently implemented asynchronously only for TCP/IP.

    PROTOCOL_VERSION12  = (FB_PROTOCOL_FLAG | 12),

	// Protocol 13 has support for authentication plugins (op_cont_auth).

    PROTOCOL_VERSION13  = (FB_PROTOCOL_FLAG | 13);

const
    CNCT_user = 1, // User name
    CNCT_passwd = 2,
	// CNCT_ppo = 3, // Apollo person, project, organization. OBSOLETE.
    CNCT_host = 4,
    CNCT_group = 5, // Effective Unix group id
    CNCT_user_verification = 6, // Attach/create using this connection will use user verification
    CNCT_specific_data = 7, // Some data, needed for user verification on server
    CNCT_plugin_name = 8, // Name of plugin, which generated that data
    CNCT_login = 9, // Same data as isc_dpb_user_name
    CNCT_plugin_list = 10, // List of plugins, available on client
    CNCT_client_crypt = 11, // Client encyption level (DISABLED/ENABLED/REQUIRED)
    WIRE_CRYPT_DISABLED = 0,
    WIRE_CRYPT_ENABLED = 1,
    WIRE_CRYPT_REQUIRED = 2;

const
    DSQL_close      = 1,
    DSQL_drop       = 2,
    DSQL_unprepare  = 4; // >= 2.5

// Protocols types (accept_type)
const
	ptype_rpc = 2, 			 // Simple remote procedure call
    ptype_batch_send = 3,    // Batch sends, no asynchrony
	ptype_out_of_band = 4,   // Batch sends w/ out of band notification
	ptype_lazy_send = 5,     // Deferred packets delivery;
	ptype_mask = 0xFF,       // Mask - up to 255 types of protocol
	pflag_compress = 0x100;  // Turn on compression if possible

const SUPPORTED_PROTOCOL = [
	[PROTOCOL_VERSION10, ARCHITECTURE_GENERIC, ptype_rpc, ptype_batch_send, 1],
	[PROTOCOL_VERSION11, ARCHITECTURE_GENERIC, ptype_lazy_send, ptype_lazy_send, 2],
	[PROTOCOL_VERSION12, ARCHITECTURE_GENERIC, ptype_lazy_send, ptype_lazy_send, 3],
	[PROTOCOL_VERSION13, ARCHITECTURE_GENERIC, ptype_lazy_send, ptype_lazy_send, 4],
];

const
    SQL_TEXT      = 452, // Array of char
    SQL_VARYING   = 448,
    SQL_SHORT     = 500,
    SQL_LONG      = 496,
    SQL_FLOAT     = 482,
    SQL_DOUBLE    = 480,
    SQL_D_FLOAT   = 530,
    SQL_TIMESTAMP = 510,
    SQL_BLOB      = 520,
    SQL_ARRAY     = 540,
    SQL_QUAD      = 550,
    SQL_TYPE_TIME = 560,
    SQL_TYPE_DATE = 570,
    SQL_INT64     = 580,
    SQL_BOOLEAN   = 32764, // >= 3.0
    SQL_NULL      = 32766; // >= 2.5

/***********************/
/*   ISC Services      */
/***********************/
const
	isc_action_svc_backup = 1, /* Starts database backup process on the server	*/
	isc_action_svc_restore = 2, /* Starts database restore process on the server */
	isc_action_svc_repair = 3, /* Starts database repair process on the server	*/
	isc_action_svc_add_user = 4, /* Adds	a new user to the security database	*/
	isc_action_svc_delete_user = 5, /* Deletes a user record from the security database	*/
	isc_action_svc_modify_user = 6, /* Modifies	a user record in the security database */
	isc_action_svc_display_user = 7, /* Displays	a user record from the security	database */
	isc_action_svc_properties = 8, /* Sets	database properties	*/
	isc_action_svc_add_license = 9, /* Adds	a license to the license file */
	isc_action_svc_remove_license = 10, /* Removes a license from the license file */
	isc_action_svc_db_stats = 11, /* Retrieves database statistics */
	isc_action_svc_get_ib_log = 12, /* Retrieves the InterBase log file	from the server	*/
    isc_action_svc_get_fb_log = isc_action_svc_get_ib_log, /* Retrieves the Firebird log file	from the server	*/
    isc_action_svc_nbak = 20, /* start nbackup */
    isc_action_svc_nrest = 21,  /* start nrestore */
    isc_action_svc_trace_start = 22,
    isc_action_svc_trace_stop = 23,
    isc_action_svc_trace_suspend = 24,
    isc_action_svc_trace_resume = 25,
    isc_action_svc_trace_list = 26;

const
	isc_info_svc_svr_db_info = 50, /* Retrieves the number	of attachments and databases */
	isc_info_svc_get_license = 51, /* Retrieves all license keys and IDs from the license file	*/
	isc_info_svc_get_license_mask = 52, /* Retrieves a bitmask representing	licensed options on	the	server */
	isc_info_svc_get_config = 53, /* Retrieves the parameters	and	values for IB_CONFIG */
	isc_info_svc_version = 54, /* Retrieves the version of	the	services manager */
	isc_info_svc_server_version = 55, /* Retrieves the version of	the	InterBase server */
	isc_info_svc_implementation = 56, /* Retrieves the implementation	of the InterBase server	*/
	isc_info_svc_capabilities = 57, /* Retrieves a bitmask representing	the	server's capabilities */
	isc_info_svc_user_dbpath = 58, /* Retrieves the path to the security database in use by the server	*/
	isc_info_svc_get_env = 59, /* Retrieves the setting of	$INTERBASE */
	isc_info_svc_get_env_lock = 60, /* Retrieves the setting of	$INTERBASE_LCK */
	isc_info_svc_get_env_msg = 61, /* Retrieves the setting of	$INTERBASE_MSG */
	isc_info_svc_line = 62, /* Retrieves 1 line	of service output per call */
	isc_info_svc_to_eof = 63, /* Retrieves as much of	the	server output as will fit in the supplied buffer */
	isc_info_svc_timeout = 64, /* Sets	/ signifies	a timeout value	for	reading	service	information	*/
	isc_info_svc_get_licensed_users = 65, /* Retrieves the number	of users licensed for accessing	the	server */
	isc_info_svc_limbo_trans = 66, /* Retrieve	the	limbo transactions */
	isc_info_svc_running = 67, /* Checks to see if	a service is running on	an attachment */
	isc_info_svc_get_users = 68, /* Returns the user	information	from isc_action_svc_display_users */
    isc_info_svc_stdin = 78;

/* Services Properties */
const
	isc_spb_prp_page_buffers = 5,
	isc_spb_prp_sweep_interval = 6,
	isc_spb_prp_shutdown_db = 7,
	isc_spb_prp_deny_new_attachments = 9,
	isc_spb_prp_deny_new_transactions = 10,
	isc_spb_prp_reserve_space = 11,
	isc_spb_prp_write_mode = 12,
	isc_spb_prp_access_mode = 13,
	isc_spb_prp_set_sql_dialect = 14,
    isc_spb_num_att = 5,
    isc_spb_num_db = 6,
    // SHUTDOWN OPTION FOR 2.0
    isc_spb_prp_force_shutdown = 41,
    isc_spb_prp_attachments_shutdown = 42,
    isc_spb_prp_transactions_shutdown = 43,
    isc_spb_prp_shutdown_mode = 44,
    isc_spb_prp_online_mode = 45,

    isc_spb_prp_sm_normal = 0,
    isc_spb_prp_sm_multi = 1,
    isc_spb_prp_sm_single = 2,
    isc_spb_prp_sm_full = 3,


	// WRITE_MODE_PARAMETERS
	isc_spb_prp_wm_async = 37,
	isc_spb_prp_wm_sync = 38,

	// ACCESS_MODE_PARAMETERS
	isc_spb_prp_am_readonly = 39,
	isc_spb_prp_am_readwrite = 40,

	// RESERVE_SPACE_PARAMETERS
	isc_spb_prp_res_use_full = 35,
	isc_spb_prp_res = 36,

	// Option Flags
	isc_spb_prp_activate = 0x0100,
	isc_spb_prp_db_online = 0x0200;

    // SHUTDOWN MODE

/* · Backup Service ·*/
const
	isc_spb_bkp_file = 5,
	isc_spb_bkp_factor = 6,
	isc_spb_bkp_length = 7,
    isc_spb_bkp_ignore_checksums = 0x01,
    isc_spb_bkp_ignore_limbo = 0x02,
    isc_spb_bkp_metadata_only = 0x04,
    isc_spb_bkp_no_garbage_collect = 0x08,
    isc_spb_bkp_old_descriptions = 0x10,
    isc_spb_bkp_non_transportable = 0x20,
    isc_spb_bkp_convert = 0x40,
    isc_spb_bkp_expand = 0x80,
    isc_spb_bkp_no_triggers = 0x8000,
    // nbackup
    isc_spb_nbk_level = 5,
    isc_spb_nbk_file = 6,
    isc_spb_nbk_direct = 7,
    isc_spb_nbk_no_triggers = 0x01;

/*	Restore Service ·*/
const
	isc_spb_res_buffers = 9,
	isc_spb_res_page_size = 10,
	isc_spb_res_length = 11,
	isc_spb_res_access_mode = 12,
    isc_spb_res_fix_fss_data = 13,
    isc_spb_res_fix_fss_metadata = 14,
	isc_spb_res_am_readonly = isc_spb_prp_am_readonly,
	isc_spb_res_am_readwrite = isc_spb_prp_am_readwrite,
    isc_spb_res_deactivate_idx = 0x0100,
    isc_spb_res_no_shadow = 0x0200,
    isc_spb_res_no_validity = 0x0400,
    isc_spb_res_one_at_a_time = 0x0800,
    isc_spb_res_replace = 0x1000,
    isc_spb_res_create = 0x2000,
    isc_spb_res_use_all_space = 0x4000;


/* · Repair Service ·*/
const
    isc_spb_rpr_commit_trans = 15,
	isc_spb_rpr_rollback_trans = 34,
	isc_spb_rpr_recover_two_phase = 17,
	isc_spb_tra_id = 18,
	isc_spb_single_tra_id = 19,
	isc_spb_multi_tra_id = 20,
	isc_spb_tra_state = 21,
	isc_spb_tra_state_limbo = 22,
	isc_spb_tra_state_commit = 23,
	isc_spb_tra_state_rollback = 24,
	isc_spb_tra_state_unknown = 25,
	isc_spb_tra_host_site = 26,
	isc_spb_tra_remote_site = 27,
	isc_spb_tra_db_path = 28,
	isc_spb_tra_advise = 29,
	isc_spb_tra_advise_commit = 30,
	isc_spb_tra_advise_rollback = 31,
	isc_spb_tra_advise_unknown = 33,
    isc_spb_rpr_validate_db = 0x01,
    isc_spb_rpr_sweep_db = 0x02,
    isc_spb_rpr_mend_db = 0x04,
    isc_spb_rpr_list_limbo_trans = 0x08,
    isc_spb_rpr_check_db = 0x10,
    isc_spb_rpr_ignore_checksum = 0x20,
    isc_spb_rpr_kill_shadows = 0x40,
    isc_spb_rpr_full = 0x80,
    isc_spb_rpr_icu = 0x0800;

/* · Security Service ·*/
const
	isc_spb_sec_userid = 5,
	isc_spb_sec_groupid = 6,
	isc_spb_sec_username = 7,
	isc_spb_sec_password = 8,
	isc_spb_sec_groupname = 9,
	isc_spb_sec_firstname = 10,
	isc_spb_sec_middlename = 11,
	isc_spb_sec_lastname = 12,
    isc_spb_sec_admin = 13;

/* License Service */
const
    isc_spb_lic_key = 5,
    isc_spb_lic_id = 6,
    isc_spb_lic_desc = 7;

/* Statistics Service */
const
    isc_spb_sts_data_pages = 0x01,
    isc_spb_sts_db_log = 0x02,
    isc_spb_sts_hdr_pages = 0x04,
    isc_spb_sts_idx_pages = 0x08,
    isc_spb_sts_sys_relations = 0x10,
    isc_spb_sts_record_versions = 0x20,
    isc_spb_sts_table = 0x40,
    isc_spb_sts_nocreation = 0x80;

/* Trace Service */
const
    isc_spb_trc_id = 1,
    isc_spb_trc_name = 2,
    isc_spb_trc_cfg = 3;


/***********************/
/*   ISC Error Codes   */
/***********************/
const
    isc_arg_end                     = 0,  // end of argument list
    isc_arg_gds                     = 1,  // generic DSRI status value
    isc_arg_string                  = 2,  // string argument
    isc_arg_cstring                 = 3,  // count & string argument
    isc_arg_number                  = 4,  // numeric argument (long)
    isc_arg_interpreted             = 5,  // interpreted status code (string)
    isc_arg_unix                    = 7,  // UNIX error code
    isc_arg_next_mach               = 15, // NeXT/Mach error code
    isc_arg_win32                   = 17, // Win32 error code
    isc_arg_warning                 = 18, // warning argument
    isc_arg_sql_state               = 19; // SQLSTATE

const
    isc_sqlerr = 335544436;

/**********************************/
/* Database parameter block stuff */
/**********************************/
const
    isc_dpb_version1                = 1,
    isc_dpb_version2                = 2, // >= FB30
    isc_dpb_cdd_pathname            = 1,
    isc_dpb_allocation              = 2,
    isc_dpb_journal                 = 3,
    isc_dpb_page_size               = 4,
    isc_dpb_num_buffers             = 5,
    isc_dpb_buffer_length           = 6,
    isc_dpb_debug                   = 7,
    isc_dpb_garbage_collect         = 8,
    isc_dpb_verify                  = 9,
    isc_dpb_sweep                   = 10,
    isc_dpb_enable_journal          = 11,
    isc_dpb_disable_journal         = 12,
    isc_dpb_dbkey_scope             = 13,
    isc_dpb_number_of_users         = 14,
    isc_dpb_trace                   = 15,
    isc_dpb_no_garbage_collect      = 16,
    isc_dpb_damaged                 = 17,
    isc_dpb_license                 = 18,
    isc_dpb_sys_user_name           = 19,
    isc_dpb_encrypt_key             = 20,
    isc_dpb_activate_shadow         = 21,
    isc_dpb_sweep_interval          = 22,
    isc_dpb_delete_shadow           = 23,
    isc_dpb_force_write             = 24,
    isc_dpb_begin_log               = 25,
    isc_dpb_quit_log                = 26,
    isc_dpb_no_reserve              = 27,
    isc_dpb_user_name               = 28,
    isc_dpb_password                = 29,
    isc_dpb_password_enc            = 30,
    isc_dpb_sys_user_name_enc       = 31,
    isc_dpb_interp                  = 32,
    isc_dpb_online_dump             = 33,
    isc_dpb_old_file_size           = 34,
    isc_dpb_old_num_files           = 35,
    isc_dpb_old_file                = 36,
    isc_dpb_old_start_page          = 37,
    isc_dpb_old_start_seqno         = 38,
    isc_dpb_old_start_file          = 39,
    isc_dpb_old_dump_id             = 41,
    isc_dpb_lc_messages             = 47,
    isc_dpb_lc_ctype                = 48,
    isc_dpb_cache_manager           = 49,
    isc_dpb_shutdown                = 50,
    isc_dpb_online                  = 51,
    isc_dpb_shutdown_delay          = 52,
    isc_dpb_reserved                = 53,
    isc_dpb_overwrite               = 54,
    isc_dpb_sec_attach              = 55,
    isc_dpb_connect_timeout         = 57,
    isc_dpb_dummy_packet_interval   = 58,
    isc_dpb_gbak_attach             = 59,
    isc_dpb_sql_role_name           = 60,
    isc_dpb_set_page_buffers        = 61,
    isc_dpb_working_directory       = 62,
    isc_dpb_sql_dialect             = 63,
    isc_dpb_set_db_readonly         = 64,
    isc_dpb_set_db_sql_dialect      = 65,
    isc_dpb_gfix_attach             = 66,
    isc_dpb_gstat_attach            = 67,
    isc_dpb_set_db_charset          = 68,
    isc_dpb_gsec_attach             = 69,
    isc_dpb_address_path            = 70,
    isc_dpb_process_id              = 71,
    isc_dpb_no_db_triggers          = 72,
    isc_dpb_trusted_auth            = 73,
    isc_dpb_process_name            = 74,
    isc_dpb_trusted_role            = 75,
    isc_dpb_org_filename            = 76,
    isc_dpb_utf8_filename           = 77,
    isc_dpb_ext_call_depth          = 78,
	isc_dpb_auth_block 				= 79,
	isc_dpb_client_version 			= 80,
	isc_dpb_remote_protocol 		= 81,
	isc_dpb_host_name 				= 82,
	isc_dpb_os_user 				= 83,
	isc_dpb_specific_auth_data 		= 84,
	isc_dpb_auth_plugin_list 		= 85,
	isc_dpb_auth_plugin_name 		= 86,
	isc_dpb_config 					= 87,
	isc_dpb_nolinger 				= 88,
	isc_dpb_reset_icu 				= 89,
	isc_dpb_map_attach 				= 90,
	isc_dpb_session_time_zone 		= 91;

/*************************************/
/* Services parameter block stuff    */
/*************************************/
const
    isc_spb_version1 = 1,
    isc_spb_current_version = 2,
    isc_spb_version = isc_spb_current_version,
    isc_spb_user_name = isc_dpb_user_name,
    isc_spb_sys_user_name = isc_dpb_sys_user_name,
    isc_spb_sys_user_name_enc = isc_dpb_sys_user_name_enc,
    isc_spb_password = isc_dpb_password,
    isc_spb_password_enc = isc_dpb_password_enc,
    isc_spb_command_line = 105,
    isc_spb_dbname = 106,
    isc_spb_verbose = 107,
    isc_spb_options = 108;

/*************************************/
/* Transaction parameter block stuff */
/*************************************/
const
    isc_tpb_version1                =  1,
    isc_tpb_version3                =  3,
    isc_tpb_consistency             =  1,
    isc_tpb_concurrency             =  2,
    isc_tpb_shared                  =  3, // < FB21
    isc_tpb_protected               =  4, // < FB21
    isc_tpb_exclusive               =  5, // < FB21
    isc_tpb_wait                    =  6,
    isc_tpb_nowait                  =  7,
    isc_tpb_read                    =  8,
    isc_tpb_write                   =  9,
    isc_tpb_lock_read               =  10,
    isc_tpb_lock_write              =  11,
    isc_tpb_verb_time               =  12,
    isc_tpb_commit_time             =  13,
    isc_tpb_ignore_limbo            =  14,
    isc_tpb_read_committed          =  15,
    isc_tpb_autocommit              =  16,
    isc_tpb_rec_version             =  17,
    isc_tpb_no_rec_version          =  18,
    isc_tpb_restart_requests        =  19,
    isc_tpb_no_auto_undo            =  20,
    isc_tpb_lock_timeout            =  21; // >= FB20

/****************************/
/* Common, structural codes */
/****************************/
const
    isc_info_end                    = 1,
    isc_info_truncated              = 2,
    isc_info_error                  = 3,
    isc_info_data_not_ready         = 4,
    isc_info_length                 = 126,
    isc_info_flag_end               = 127;

/*************************/
/* SQL information items */
/*************************/
const
    isc_info_sql_select             = 4,
    isc_info_sql_bind               = 5,
    isc_info_sql_num_variables      = 6,
    isc_info_sql_describe_vars      = 7,
    isc_info_sql_describe_end       = 8,
    isc_info_sql_sqlda_seq          = 9,
    isc_info_sql_message_seq        = 10,
    isc_info_sql_type               = 11,
    isc_info_sql_sub_type           = 12,
    isc_info_sql_scale              = 13,
    isc_info_sql_length             = 14,
    isc_info_sql_null_ind           = 15,
    isc_info_sql_field              = 16,
    isc_info_sql_relation           = 17,
    isc_info_sql_owner              = 18,
    isc_info_sql_alias              = 19,
    isc_info_sql_sqlda_start        = 20,
    isc_info_sql_stmt_type          = 21,
    isc_info_sql_get_plan           = 22,
    isc_info_sql_records            = 23,
    isc_info_sql_batch_fetch        = 24,
    isc_info_sql_relation_alias     = 25, // >= 2.0
    isc_info_sql_explain_plan       = 26; // >= 3.0

/*******************/
/* Blr definitions */
/*******************/
const
    blr_text            = 14,
    blr_text2           = 15,
    blr_short           = 7,
    blr_long            = 8,
    blr_quad            = 9,
    blr_float           = 10,
    blr_double          = 27,
    blr_d_float         = 11,
    blr_timestamp       = 35,
    blr_varying         = 37,
    blr_varying2        = 38,
    blr_blob            = 261,
    blr_cstring         = 40,
    blr_cstring2        = 41,
    blr_blob_id         = 45,
    blr_sql_date        = 12,
    blr_sql_time        = 13,
    blr_int64           = 16,
    blr_blob2           = 17, // >= 2.0
    blr_domain_name     = 18, // >= 2.1
    blr_domain_name2    = 19, // >= 2.1
    blr_not_nullable    = 20, // >= 2.1
    blr_column_name     = 21, // >= 2.5
    blr_column_name2    = 22, // >= 2.5
    blr_bool            = 23, // >= 3.0

    blr_version4        = 4,
    blr_version5        = 5, // dialect 3
    blr_eoc             = 76,
    blr_end             = 255,

    blr_assignment      = 1,
    blr_begin           = 2,
    blr_dcl_variable    = 3,
    blr_message         = 4;

const
    isc_info_sql_stmt_select          = 1,
    isc_info_sql_stmt_insert          = 2,
    isc_info_sql_stmt_update          = 3,
    isc_info_sql_stmt_delete          = 4,
    isc_info_sql_stmt_ddl             = 5,
    isc_info_sql_stmt_get_segment     = 6,
    isc_info_sql_stmt_put_segment     = 7,
    isc_info_sql_stmt_exec_procedure  = 8,
    isc_info_sql_stmt_start_trans     = 9,
    isc_info_sql_stmt_commit          = 10,
    isc_info_sql_stmt_rollback        = 11,
    isc_info_sql_stmt_select_for_upd  = 12,
    isc_info_sql_stmt_set_generator   = 13,
    isc_info_sql_stmt_savepoint       = 14;

const
    isc_blob_text = 1;

const DESCRIBE = [
	isc_info_sql_stmt_type,
	isc_info_sql_select,
	isc_info_sql_describe_vars,
	isc_info_sql_sqlda_seq,
	isc_info_sql_type,
	isc_info_sql_sub_type,
	isc_info_sql_scale,
	isc_info_sql_length,
	isc_info_sql_field,
	isc_info_sql_relation,
	//isc_info_sql_owner,
	isc_info_sql_alias,
	isc_info_sql_describe_end,
	isc_info_sql_bind,
	isc_info_sql_describe_vars,
	isc_info_sql_sqlda_seq,
	isc_info_sql_type,
	isc_info_sql_sub_type,
	isc_info_sql_scale,
	isc_info_sql_length,
	isc_info_sql_describe_end
];

const
    ISOLATION_READ_UNCOMMITTED          = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_rec_version],
    ISOLATION_READ_COMMITED             = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_no_rec_version],
    ISOLATION_REPEATABLE_READ           = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_concurrency],
    ISOLATION_SERIALIZABLE              = [isc_tpb_version3, isc_tpb_write, isc_tpb_wait, isc_tpb_consistency],
    ISOLATION_READ_COMMITED_READ_ONLY   = [isc_tpb_version3, isc_tpb_read, isc_tpb_wait, isc_tpb_read_committed, isc_tpb_no_rec_version];

const
    DEFAULT_HOST = '127.0.0.1',
    DEFAULT_PORT = 3050,
    DEFAULT_USER = 'SYSDBA',
    DEFAULT_PASSWORD = 'masterkey',
    DEFAULT_LOWERCASE_KEYS = false,
    DEFAULT_PAGE_SIZE = 4096,
    DEFAULT_SVC_NAME = 'service_mgr';

const AUTH_PLUGIN_LEGACY = 'Legacy_Auth',
 	  AUTH_PLUGIN_SRP = 'Srp';
	  // AUTH_PLUGIN_SRP256 = 'Srp256';

const
	// AUTH_PLUGIN_LIST = [AUTH_PLUGIN_SRP256, AUTH_PLUGIN_SRP, AUTH_PLUGIN_LEGACY],
	AUTH_PLUGIN_LIST = [AUTH_PLUGIN_SRP, AUTH_PLUGIN_LEGACY],
	// AUTH_PLUGIN_SRP_LIST = [AUTH_PLUGIN_SRP256, AUTH_PLUGIN_SRP],
	AUTH_PLUGIN_SRP_LIST = [AUTH_PLUGIN_SRP],
    LEGACY_AUTH_SALT = '9z',
	WIRE_CRYPT_DISABLE = 0,
	WIRE_CRYPT_ENABLE = 1;

exports.AUTH_PLUGIN_LEGACY = AUTH_PLUGIN_LEGACY;
exports.AUTH_PLUGIN_SRP = AUTH_PLUGIN_SRP;
// exports.AUTH_PLUGIN_SRP256 = AUTH_PLUGIN_SRP256;

exports.WIRE_CRYPT_DISABLE = WIRE_CRYPT_DISABLE;
exports.WIRE_CRYPT_ENABLE = WIRE_CRYPT_ENABLE;

exports.ISOLATION_READ_UNCOMMITTED = ISOLATION_READ_UNCOMMITTED;
exports.ISOLATION_READ_COMMITED = ISOLATION_READ_COMMITED;
exports.ISOLATION_REPEATABLE_READ = ISOLATION_REPEATABLE_READ;
exports.ISOLATION_SERIALIZABLE = ISOLATION_SERIALIZABLE;
exports.ISOLATION_READ_COMMITED_READ_ONLY = ISOLATION_READ_COMMITED_READ_ONLY;

if (!String.prototype.padLeft) {
    String.prototype.padLeft = function(max, c) {
        var self = this;
        return new Array(Math.max(0, max - self.length + 1)).join(c || ' ') + self;
    };
}

/**
 * Escape value
 * @param {Object} value
 * @param {Number} protocolVersion (optional, default: PROTOCOL_VERSION13)
 * @return {String}
 */
exports.escape = function(value, protocolVersion) {

    if (value === null || value === undefined)
        return 'NULL';

    switch (typeof(value)) {
        case 'boolean':
            if ((protocolVersion || PROTOCOL_VERSION13) >= PROTOCOL_VERSION13)
                return value ? 'true' : 'false';
            else
                return value ? '1' : '0';
        case 'number':
            return value.toString();
        case 'string':
            return "'" + value.replace(/'/g, "''").replace(/\\/g, '\\\\') + "'";
    }

    if (value instanceof Date)
        return "'" + value.getFullYear() + '-' + (value.getMonth()+1).toString().padLeft(2, '0') + '-' + value.getDate().toString().padLeft(2, '0') + ' ' + value.getHours().toString().padLeft(2, '0') + ':' + value.getMinutes().toString().padLeft(2, '0') + ':' + value.getSeconds().toString().padLeft(2, '0') + '.' + value.getMilliseconds().toString().padLeft(3, '0') + "'";

    throw new Error('Escape supports only primitive values.');
};

const
    DEFAULT_ENCODING = 'utf8',
    DEFAULT_FETCHSIZE = 200;

const
    MAX_INT = Math.pow(2, 31) - 1,
    MIN_INT = - Math.pow(2, 31);

/***************************************
 *
 *   SQLVar
 *
 ***************************************/


const
    ScaleDivisor = [1,10,100,1000,10000,100000,1000000,10000000,100000000,1000000000,10000000000, 100000000000,1000000000000,10000000000000,100000000000000,1000000000000000];
const
    DateOffset = 40587,
    TimeCoeff = 86400000,
    MsPerMinute = 60000;

//------------------------------------------------------

function SQLVarText() {}

SQLVarText.prototype.decode = function(data, lowerV13) {
    var ret;
    if (this.subType > 1) {
        ret = data.readText(this.length, DEFAULT_ENCODING);
    } else {
        ret = data.readBuffer(this.length);
    }

	if (!lowerV13 || !data.readInt()) {
		return ret;
	}

    return null;
};

SQLVarText.prototype.calcBlr = function(blr) {
    blr.addByte(blr_text);
    blr.addWord(this.length);
};

//------------------------------------------------------

function SQLVarNull() {}
SQLVarNull.prototype = new SQLVarText();
SQLVarNull.prototype.constructor = SQLVarNull;

//------------------------------------------------------

function SQLVarString() {}

SQLVarString.prototype.decode = function(data, lowerV13) {
    var ret;
    if (this.subType > 1) {
        ret = data.readString(DEFAULT_ENCODING)
    } else {
        ret = data.readBuffer()
    }

    if (!lowerV13 || !data.readInt()) {
        return ret;
    }
    return null;
};

SQLVarString.prototype.calcBlr = function(blr) {
    blr.addByte(blr_varying);
    blr.addWord(this.length);
};

//------------------------------------------------------

function SQLVarQuad() {}

SQLVarQuad.prototype.decode = function(data, lowerV13) {
    var ret = data.readQuad();

    if (!lowerV13 || !data.readInt()) {
        return ret;
    }
    return null;
};

SQLVarQuad.prototype.calcBlr = function(blr) {
    blr.addByte(blr_quad);
    blr.addShort(this.scale);
};

//------------------------------------------------------

function SQLVarBlob() {}
SQLVarBlob.prototype = new SQLVarQuad();
SQLVarBlob.prototype.constructor = SQLVarBlob;

SQLVarBlob.prototype.calcBlr = function(blr) {
    blr.addByte(blr_quad);
    blr.addShort(0);
};

//------------------------------------------------------

function SQLVarArray() {}
SQLVarArray.prototype = new SQLVarQuad();
SQLVarArray.prototype.constructor = SQLVarArray;

SQLVarArray.prototype.calcBlr = function(blr) {
    blr.addByte(blr_quad);
    blr.addShort(0);
};

//------------------------------------------------------

function SQLVarInt() {}

SQLVarInt.prototype.decode = function(data, lowerV13) {
    var ret = data.readInt();

	if (this.scale) {
		ret = ret / ScaleDivisor[Math.abs(this.scale)];
	}

    if (!lowerV13 || !data.readInt()) {
        return ret;
    }

    return null;
};

SQLVarInt.prototype.calcBlr = function(blr) {
    blr.addByte(blr_long);
    blr.addShort(this.scale);
};

//------------------------------------------------------

function SQLVarShort() {}
SQLVarShort.prototype = new SQLVarInt();
SQLVarShort.prototype.constructor = SQLVarShort;

SQLVarShort.prototype.calcBlr = function(blr) {
    blr.addByte(blr_short);
    blr.addShort(this.scale);
};

//------------------------------------------------------

function SQLVarInt64() {}

SQLVarInt64.prototype.decode = function(data, lowerV13) {
    var ret = data.readInt64();

	if (this.scale) {
		ret = ret / ScaleDivisor[Math.abs(this.scale)];
	}

    if (!lowerV13 || !data.readInt()) {
        return ret;
    }
    return null;
};

SQLVarInt64.prototype.calcBlr = function(blr) {
    blr.addByte(blr_int64);
    blr.addShort(this.scale);
};

//------------------------------------------------------

function SQLVarFloat() {}

SQLVarFloat.prototype.decode = function(data, lowerV13) {
    var ret = data.readFloat();

    if (!lowerV13 || !data.readInt()) {
        return ret;
    }

    return null;
};

SQLVarFloat.prototype.calcBlr = function(blr) {
    blr.addByte(blr_float);
};

//------------------------------------------------------

function SQLVarDouble() {}

SQLVarDouble.prototype.decode = function(data, lowerV13) {
    var ret = data.readDouble();

    if (!lowerV13 || !data.readInt()) {
        return ret;
    }

    return null;
};

SQLVarDouble.prototype.calcBlr = function(blr) {
    blr.addByte(blr_double);
};

//------------------------------------------------------

function SQLVarDate() {}

SQLVarDate.prototype.decode = function(data, lowerV13) {
    var ret = data.readInt();

    if (!lowerV13 || !data.readInt()) {
        var d = new Date(0);
        d.setMilliseconds((ret - DateOffset) * TimeCoeff + d.getTimezoneOffset() * MsPerMinute);
        return d;
    }

    return null;
};

SQLVarDate.prototype.calcBlr = function(blr) {
    blr.addByte(blr_sql_date);
};

//------------------------------------------------------

function SQLVarTime() {}

SQLVarTime.prototype.decode = function(data, lowerV13) {
    var ret = data.readUInt();

    if (!lowerV13 || !data.readInt()) {
        var d = new Date(0);
        d.setMilliseconds(Math.floor(ret / 10) + d.getTimezoneOffset() * MsPerMinute);
        return d;
    }
    return null;
};

SQLVarTime.prototype.calcBlr = function(blr) {
    blr.addByte(blr_sql_time);
};

//------------------------------------------------------

function SQLVarTimeStamp() {}

SQLVarTimeStamp.prototype.decode = function(data, lowerV13) {
    var date = data.readInt();
    var time = data.readUInt();

    if (!lowerV13 || !data.readInt()) {
        var d = new Date(0);
        d.setMilliseconds((date - DateOffset) * TimeCoeff + Math.floor(time / 10) + d.getTimezoneOffset() * MsPerMinute);
        return d;
    }

    return null;
};

SQLVarTimeStamp.prototype.calcBlr = function(blr) {
    blr.addByte(blr_timestamp);
};

//------------------------------------------------------

function SQLVarBoolean() {}

SQLVarBoolean.prototype.decode = function(data, lowerV13) {
    var ret = data.readInt();

    if (!lowerV13 || !data.readInt()) {
        return Boolean(ret);
    }
    return null;
};

SQLVarBoolean.prototype.calcBlr = function(blr) {
    blr.addByte(blr_bool);
};

//------------------------------------------------------

function SQLParamInt(value){
    this.value = value;
}

SQLParamInt.prototype.calcBlr = function(blr) {
    blr.addByte(blr_long);
    blr.addShort(0);
};

SQLParamInt.prototype.encode = function(data) {
    if (this.value != null) {
        data.addInt(this.value);
    } else {
        data.addInt(0);
        data.addInt(1);
    }
};

//------------------------------------------------------

function SQLParamInt64(value){
    this.value = value;
}

SQLParamInt64.prototype.calcBlr = function(blr) {
    blr.addByte(blr_int64);
    blr.addShort(0);
};

SQLParamInt64.prototype.encode = function(data) {
    if (this.value != null) {
        data.addInt64(this.value);
    } else {
        data.addInt64(0);
        data.addInt(1);
    }
};

//------------------------------------------------------

function SQLParamDouble(value) {
    this.value = value;
}

SQLParamDouble.prototype.encode = function(data) {
    if (this.value != null) {
        data.addDouble(this.value);
    } else {
        data.addDouble(0);
        data.addInt(1);
    }
};

SQLParamDouble.prototype.calcBlr = function(blr) {
    blr.addByte(blr_double);
};

//------------------------------------------------------

function SQLParamString(value) {
    this.value = value;
}

SQLParamString.prototype.encode = function(data) {
    if (this.value != null) {
        data.addText(this.value, DEFAULT_ENCODING);
    } else {
        data.addInt(1);
    }
};

SQLParamString.prototype.calcBlr = function(blr) {
    blr.addByte(blr_text);
    var len = this.value ? Buffer.byteLength(this.value, DEFAULT_ENCODING) : 0;
    blr.addWord(len);
};

//------------------------------------------------------

function SQLParamQuad(value) {
    this.value = value;
}

SQLParamQuad.prototype.encode = function(data) {
    if (this.value != null) {
        data.addInt(this.value.high);
        data.addInt(this.value.low);
    } else {
        data.addInt(0);
        data.addInt(0);
        data.addInt(1);
    }
};

SQLParamQuad.prototype.calcBlr = function(blr) {
    blr.addByte(blr_quad);
    blr.addShort(0);
};

//------------------------------------------------------

function SQLParamDate(value) {
    this.value = value;
}

SQLParamDate.prototype.encode = function(data) {
    if (this.value != null) {

        var value = this.value.getTime() - this.value.getTimezoneOffset() * MsPerMinute;
        var time = value % TimeCoeff;
        var date = (value - time) / TimeCoeff + DateOffset;
        time *= 10;

        // check overflow
        if (time < 0) {
            date--;
            time = TimeCoeff*10 + time;
        }

        data.addInt(date);
        data.addUInt(time);
    } else {
        data.addInt(0);
        data.addUInt(0);
        data.addInt(1);
    }
};

SQLParamDate.prototype.calcBlr = function(blr) {
    blr.addByte(blr_timestamp);
};

//------------------------------------------------------

function SQLParamBool(value) {
    this.value = value;
}

SQLParamBool.prototype.encode = function(data) {
    if (this.value != null) {
        data.addInt(this.value ? 1 : 0);
    } else {
        data.addInt(0);
        data.addInt(1);
    }
};

SQLParamBool.prototype.calcBlr = function(blr) {
    blr.addByte(blr_short);
    blr.addShort(0);
};


/***************************************
 *
 *   Error handling
 *
 ***************************************/

function isError(obj) {
    return Boolean(
      obj != null && typeof obj === "object" && !Array.isArray(obj) && obj.status
    );
}

function doCallback(obj, callback) {

    if (!callback)
        return;

    if (obj instanceof Error) {
        callback(obj);
        return;
    }

    if (isError(obj)) {
        var error = new Error(obj.message);
        var status = obj.status && obj.status.length && obj.status[0] || {};
        error.gdscode = status.gdscode; // main error gds code
        error.gdsparams = status.params; // parameters (constraint name, table, etc.)
        callback(error);
        return;
    }

    callback(undefined, obj);

}

function doError(obj, callback) {
    if (callback)
        callback(obj)
}

/***************************************
 *
 *   Statement
 *
 ***************************************/

function Statement(connection) {
    this.connection = connection;
}

Statement.prototype.close = function(callback) {
    this.connection.closeStatement(this, callback);
};

Statement.prototype.drop = function(callback) {
    this.connection.dropStatement(this, callback);
};

Statement.prototype.release = function(callback) {
    var cache_query = this.connection.getCachedQuery(this.query);
    if (cache_query)
        this.connection.closeStatement(this, callback);
    else
        this.connection.dropStatement(this, callback);
};

Statement.prototype.execute = function(transaction, params, callback, custom) {

    if (params instanceof Function) {
        custom = callback;
        callback = params;
        params = undefined;
    }

    this.custom = custom;
    this.connection.executeStatement(transaction, this, params, callback, custom);
};

Statement.prototype.fetch = function(transaction, count, callback) {
    this.connection.fetch(this, transaction, count, callback);
};

Statement.prototype.fetchAll = function(transaction, callback) {
    this.connection.fetchAll(this, transaction, callback);
};

/***************************************
 *
 *   Transaction
 *
 ***************************************/

function Transaction(connection) {
    this.connection = connection;
    this.db = connection.db;
}

Transaction.prototype.newStatement = function(query, callback) {
    var cnx = this.connection;
    var self = this;
    var query_cache = cnx.getCachedQuery(query);

    if (query_cache) {
        callback(null, query_cache);
    } else {
    	cnx.prepare(self, query, false, callback);
    }
};

Transaction.prototype.execute = function(query, params, callback, custom) {

    if (params instanceof Function) {
        custom = callback;
        callback = params;
        params = undefined;
    }

    var self = this;
    this.newStatement(query, function(err, statement) {
        if (err) {
            doError(err, callback);
            return;
        }

        function dropError(err) {
            statement.release();
            doCallback(err, callback);
        }

        statement.execute(self, params, function(err, ret) {
            if (err) {
                dropError(err);
                return;
            }

            switch (statement.type) {
                case isc_info_sql_stmt_select:
                    statement.fetchAll(self, function(err, r) {
                        if (err) {
                            dropError(err);
                            return;
                        }

                        statement.release();

                        if (callback)
                            callback(undefined, r, statement.output, true);

                    });

                    break;

                case isc_info_sql_stmt_exec_procedure:
                	if (ret && ret.data && ret.data.length > 0) {
						statement.release();

						if (callback)
							callback(undefined, ret.data[0], statement.output, true);

						break;
					} else if (statement.output.length) {
                        statement.fetch(self, 1, function(err, ret) {
                            if (err) {
                                dropError(err);
                                return;
                            }

                            statement.release();

                            if (callback)
                                callback(undefined, ret.data[0], statement.output, false);
                        });

                        break;
                    }

                // Fall through is normal
                default:
                    statement.release();
                    if (callback)
                        callback()
                    break;
            }

        }, custom);
    });
};

Transaction.prototype.sequentially = function (query, params, on, callback, asArray) {

	if (params instanceof Function) {
		asArray = callback;
		callback = on;
		on = params;
		params = undefined;
	}

	if (on === undefined){
		throw new Error('Expected "on" delegate.');
	}

	if (callback instanceof Boolean) {
		asArray = callback;
		callback = undefined;
	}

	var self = this;
	self.execute(query, params, callback, { asObject: !asArray, asStream: true, on: on });
	return self;
};

Transaction.prototype.query = function(query, params, callback) {

    if (params instanceof Function) {
        callback = params;
        params = undefined;
    }

    if (callback === undefined)
        callback = noop;

    this.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null });

};

Transaction.prototype.commit = function(callback) {
    this.connection.commit(this, callback);
};

Transaction.prototype.rollback = function(callback) {
    this.connection.rollback(this, callback);
};

Transaction.prototype.commitRetaining = function(callback) {
    this.connection.commitRetaining(this, callback);
};

Transaction.prototype.rollbackRetaining = function(callback) {
    this.connection.rollbackRetaining(this, callback);
};

/***************************************
 *
 *   Database
 *
 ***************************************/

function Database(connection) {
    this.connection = connection;
    connection.db = this;
}

Database.prototype.__proto__ = Object.create(Events.EventEmitter.prototype, {
    constructor: {
        value: Database,
        enumberable: false
    }
});

Database.prototype.escape = function(value) {
    return exports.escape(value, this.connection.accept.protocolVersion);
};

Database.prototype.detach = function(callback, force) {

    var self = this;

    if (!force && self.connection._pending.length > 0) {
        self.connection._detachAuto = true;
        self.connection._detachCallback = callback;
        return self;
    }

    if (self.connection._pooled === false) {
        self.connection.detach(function (err, obj) {

            self.connection.disconnect();
            self.emit('detach', false);

            if (callback)
                callback(err, obj);

        }, force);
    } else {
        self.emit('detach', false);
        if (callback)
            callback();
    }

    return self;
};

Database.prototype.transaction = function(isolation, callback) {
    return this.startTransaction(isolation, callback);
};

Database.prototype.startTransaction = function(isolation, callback) {
    this.connection.startTransaction(isolation, callback);
    return this;
};

Database.prototype.newStatement = function (query, callback) {

    this.startTransaction(function(err, transaction) {

        if (err) {
            callback(err);
            return;
        }

        transaction.newStatement(query, function(err, statement) {

            if (err) {
                callback(err);
                return;
            }

            transaction.commit(function(err) {
                callback(err, statement);
            });
        });
    });

    return this;
};

Database.prototype.execute = function(query, params, callback, custom) {

    if (params instanceof Function) {
        custom = callback;
        callback = params;
        params = undefined;
    }

    var self = this;

    self.connection.startTransaction(function(err, transaction) {

        if (err) {
            doError(err, callback);
            return;
        }

        transaction.execute(query, params, function(err, result, meta, isSelect) {
            if (err) {
                transaction.rollback(function() {
                    doError(err, callback);
                });
                return;
            }

            transaction.commit(function(err) {
                if (callback)
                    callback(err, result, meta, isSelect);
            });

        }, custom);
    });

    return self;
};

Database.prototype.sequentially = function(query, params, on, callback, asArray) {

	if (params instanceof Function) {
		asArray = callback;
		callback = on;
		on = params;
		params = undefined;
	}

	if (on === undefined){
		throw new Error('Expected "on" delegate.');
	}

	if (callback instanceof Boolean) {
		asArray = callback;
		callback = undefined;
	}

	var self = this;
	self.execute(query, params, callback, { asObject: !asArray, asStream: true, on: on });
	return self;
};

Database.prototype.query = function(query, params, callback) {

    if (params instanceof Function) {
        callback = params;
        params = undefined;
    }

    var self = this;
    self.execute(query, params, callback, { asObject: true, asStream: callback === undefined || callback === null });
    return self;
};

Database.prototype.drop = function(callback) {
	return this.connection.dropDatabase(callback);
};

exports.attach = function(options, callback) {

    var host = options.host || DEFAULT_HOST;
    var port = options.port || DEFAULT_PORT;
    var manager = options.manager || false;
    var cnx = this.connection = new Connection(host, port, function(err) {

        if (err) {
            doError(err, callback);
            return;
        }

        cnx.connect(options, function(err) {
            if (err) {
                doError(err, callback);
            } else {
                if (manager)
                    cnx.svcattach(options, callback);
                else
                    cnx.attach(options, callback);
            }
        });

    }, options);
};

exports.drop = function(options, callback) {
	exports.attach(options, function(err, db) {
		if (err) {
			callback({ error: err, message: "Drop error" });
			return;
		}

		db.drop(callback);
	});
};

exports.create = function(options, callback) {
    var host = options.host || DEFAULT_HOST;
    var port = options.port || DEFAULT_PORT;
    var cnx = this.connection = new Connection(host, port, function(err) {

        var self = cnx;

        if (err) {
            callback({ error: err, message: "Connect error" });
            return;
        }

        cnx.connect(options, function(err) {
            if (err) {
                self.db.emit('error', err);
                doError(err, callback);
                return;
            }

            cnx.createDatabase(options, callback);
        });
    }, options);
};

exports.attachOrCreate = function(options, callback) {

    var host = options.host || DEFAULT_HOST;
    var port = options.port || DEFAULT_PORT;

    var cnx = this.connection = new Connection(host, port, function(err) {

        var self = cnx;

        if (err) {
            callback({ error: err, message: "Connect error" });
            return;
        }

        cnx.connect(options, function(err) {

            if (err) {
                doError(err, callback);
                return;
            }

            cnx.attach(options, function(err, ret) {

                if (!err) {
                    if (self.db)
                        self.db.emit('connect', ret);
                    doCallback(ret, callback);
                    return;
                }

                cnx.createDatabase(options, callback);
            });
        });

    }, options);
};

/***************************************
 *
 *   Service Manager
 *
 ***************************************/

function ServiceManager(connection) {
    this.connection = connection;
    connection.svc = this;
}

ServiceManager.prototype.__proto__ = Object.create(Events.EventEmitter.prototype, {
    constructor: {
        value: ServiceManager,
        enumberable: false
    }
});

ServiceManager.prototype._createOutputStream = function (optread, buffersize, callback) {
    var self = this;
    optread = optread || 'byline';
    var t = new stream.Readable({ objectMode: optread === 'byline' }); // chunk by line
    t.__proto__._read = function () {
        var selfread = this;
        var fct = optread === 'byline' ? self.readline : self.readeof;
        fct.call(self, { buffersize: buffersize }, function (err, data) {
            if (err) {
                selfread.push(err.message, DEFAULT_ENCODING);
                return;
            }
            if (data.line && data.line.length)
                selfread.push(data.line, DEFAULT_ENCODING);
            else
                selfread.push(null);
        });
    }

    callback(null, t);
}


ServiceManager.prototype._infosmapping = {
    "50"/*isc_info_svc_svr_db_info*/ : "dbinfo",
    "51"/*isc_info_svc_get_license*/ : "licenses",
    "52"/*isc_info_svc_get_license_mask*/ : "licenseoptions",
    "53"/*isc_info_svc_get_config*/ : "fbconfig",
    "54"/*isc_info_svc_version*/ : "svcversion",
    "55"/*isc_info_svc_server_version*/ : "fbversion",
    "56"/*isc_info_svc_implementation*/ : "fbimplementation",
    "57"/*isc_info_svc_capabilities*/ : "fbcapatibilities",
    "58"/*isc_info_svc_user_dbpath*/ : "pathsecuritydb",
    "59"/*isc_info_svc_get_env*/ : "fbenv",
    "60"/*isc_info_svc_get_env_lock*/ : "fbenvlock",
    "61"/*isc_info_svc_get_env_msg*/ : "fbenvmsg",
    "62"/*isc_info_svc_line*/ : "",
    "63"/*isc_info_svc_to_eof*/ : "",
    "64"/*isc_info_svc_timeout*/ : "",
    "65"/*isc_info_svc_get_licensed_users*/ : "",
    "66"/*isc_info_svc_limbo_trans*/ : "limbotrans",
    "67"/*isc_info_svc_running*/ : "",
    "68"/*isc_info_svc_get_users*/ : "fbusers",
    "78"/*isc_info_svc_stdin*/ : ""
};

ServiceManager.prototype._processcapabilities = function (blr, res) {
    var capArray = [
        "WAL_SUPPORT",
        "MULTI_CLIENT_SUPPORT",
        "REMOTE_HOP_SUPPORT",
        "NO_SVR_STATS_SUPPORT",
        "NO_DB_STATS_SUPPORT",
        "LOCAL_ENGINE_SUPPORT",
        "NO_FORCED_WRITE_SUPPORT",
        "NO_SHUTDOWN_SUPPORT",
        "NO_SERVER_SHUTDOWN_SUPPORT",
        "SERVER_CONFIG_SUPPORT",
        "QUOTED_FILENAME_SUPPORT"
    ];
    var dbcapa = res[this._infosmapping[57]] = [];
    var caps = blr.readInt32();

	for (var i = 0; i < capArray.length; ++i)
		if (caps & (1 << i))
            dbcapa.push(capArray[i]);
}

ServiceManager.prototype._processdbinfo = function (blr, res) {
    var tinfo = blr.readByteCode();
    var dbinfo = res[this._infosmapping[50]] = {};

    dbinfo.database = [];
    for (; tinfo != isc_info_flag_end; tinfo = blr.readByteCode()) {
        switch (tinfo) {
            case isc_spb_dbname:
                dbinfo.database.push(blr.readString());
                break;
            case isc_spb_num_att:
                dbinfo.nbattachment = blr.readInt32();
                break;
            case isc_spb_num_db:
                dbinfo.nbdatabase = blr.readInt32();
                break;
        }
    }
}

ServiceManager.prototype._processquery = function (buffer, callback) {
    //console.log(buffer);
    var br = new BlrReader(buffer);
    var tinfo = br.readByteCode();
    var res = {};
    res.result = 0;
    for (; tinfo !== isc_info_end; tinfo = br.readByteCode()) {
        switch (tinfo) {
            case isc_info_svc_server_version:
            case isc_info_svc_implementation:
            case isc_info_svc_user_dbpath:
            case isc_info_svc_get_env:
            case isc_info_svc_get_env_lock:
            case isc_info_svc_get_env_msg:
                res[this._infosmapping[tinfo]] = br.readString();
                break;
            case isc_info_svc_version:
                res[this._infosmapping[tinfo]] = br.readInt32();
                break;
            case isc_info_svc_svr_db_info:
                this._processdbinfo(br, res);
                break;
            case isc_info_svc_limbo_trans:
                // not implemented
                for (; tinfo !== isc_info_flag_end; tinfo = br.readByteCode())
                break;
            case isc_info_svc_get_users:
                br.pos += 2
                res[this._infosmapping[tinfo]] = [];
                break;
            case isc_spb_sec_username:
                var tuser = res[this._infosmapping[68]];
                tuser.push({});
                tuser[tuser.length - 1].username = br.readString();
                break;
            case isc_spb_sec_firstname:
                var tuser = res[this._infosmapping[68]];
                var user = tuser[tuser.length-1];
                user.firstname = br.readString();
                break;
            case isc_spb_sec_middlename:
                var tuser = res[this._infosmapping[68]];
                var user = tuser[tuser.length-1];
                user.middlename = br.readString();
                break;
            case isc_spb_sec_lastname:
                var tuser = res[this._infosmapping[68]];
                var user = tuser[tuser.length-1];
                user.lastname = br.readString();
                break;
            case isc_spb_sec_groupid:
                var tuser = res[this._infosmapping[68]];
                var user = tuser[tuser.length-1];
                user.groupid = br.readInt32();
                break;
            case isc_spb_sec_userid:
                var tuser = res[this._infosmapping[68]];
                var user = tuser[tuser.length-1];
                user.userid = br.readInt32();

                break;
            case isc_spb_sec_admin:
                var tuser = res[this._infosmapping[68]];
                var user = tuser[tuser.length-1];
                user.admin = br.readInt32();
                break;

            case isc_info_svc_line:
                res.line = br.readString();
                break;

            case isc_info_svc_to_eof:
                res.line = br.readString();
                break;

            case isc_info_truncated:
                res.result = 1; // too much data for the result buffer increase size of it (buffersize parameter))
                break;

            case isc_info_data_not_ready:
                res.result = 2;
                break;

            case isc_info_svc_timeout:
                res.result = 3;
                break;

            case isc_info_svc_stdin:

                break;

            case isc_info_svc_capabilities:
                this._processcapabilities(br, res);
                break;
        }
    }
    callback(null, res);
}

ServiceManager.prototype.detach = function(callback, force) {
    var self = this;

    if (!force && self.connection._pending.length > 0) {
        self.connection._detachAuto = true;
        self.connection._detachCallback = callback;
        return self;
    }

    self.connection.svcdetach(function (err, obj) {

        self.connection.disconnect();
        self.emit('detach', false);

        if (callback)
            callback(err, obj);

    }, force);

    return self;
}

ServiceManager.prototype.backup = function (options, callback) {
    var dbpath = options.database || this.connection.options.filename || this.connection.options.database;
    var verbose = options.verbose || false;
    // format of bckfile {filename:'name', sizefile:''} sizefile is length of part in bytes
    var bckfiles = options.backupfiles || options.files || null;
    // for convenience
    if (bckfiles) bckfiles = bckfiles.constructor !== Array?[{ filename: bckfiles, sizefile: '0' }]:bckfiles;
    var factor = options.factor || 0; //If backing up to a physical tape device, this switch lets you specify the tape's blocking factor
    var ignorechecksums = options.ignorechecksums || false;
    var ignorelimbo = options.ignorelimbo || false;
    var metadataonly = options.metadataonly || false;
    var nogarbagecollect = options.nogarbasecollect || false;
    var olddescriptions = options.olddescriptions || false;
    var nontransportable = options.nontransportable || false;
    var convert = options.convert || false;
    var expand = options.expand || false;
    var notriggers = options.notriggers || false;

    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    if (bckfiles == null || bckfiles.length === 0) {
        doError(new Error('No backup path specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_backup);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    for (var i = 0; i < bckfiles.length; i++) {
        blr.addString2(isc_spb_bkp_file, bckfiles[i].filename, DEFAULT_ENCODING);
        if (i !== bckfiles.length - 1) // not the end, so we need to write the size of this part (gsplit)
            blr.addString2(isc_spb_bkp_length, bckfiles[i].sizefile, DEFAULT_ENCODING);
    }
    if (factor)
        blr.addByteInt32(isc_spb_bkp_factor, factor);

    var opts = 0;
    if (ignorechecksums) opts = opts | isc_spb_bkp_ignore_checksums;
    if (ignorelimbo) opts = opts | isc_spb_bkp_ignore_limbo;
    if (metadataonly) opts = opts | isc_spb_bkp_metadata_only;
    if (nogarbagecollect) opts = opts | isc_spb_bkp_no_garbage_collect;
    if (olddescriptions) opts = opts | isc_spb_bkp_old_descriptions;
    if (nontransportable) opts = opts | isc_spb_bkp_non_transportable;
    if (convert) opts = opts | isc_spb_bkp_convert;
    if (expand) opts = opts | isc_spb_bkp_expand;
    if (notriggers) opts = opts | isc_spb_bkp_no_triggers;
    if (opts)
        blr.addByteInt32(isc_spb_options, opts);
    if (verbose)
        blr.addByte(isc_spb_verbose);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.nbackup = function (options, callback) {
    var dbpath = options.database || this.connection.options.filename || this.connection.options.database;
    var bckfile = options.backupfile || options.file || null;
    var level = options.level || 0; // nb day for incremental
    var notriggers = options.notriggers || false;
    var direct = options.direct || 'on'; // on or off direct write I/O

    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    if (bckfile == null || bckfile.length === 0) {
        doError(new Error('No backup path specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_nbak);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    blr.addString2(isc_spb_nbk_file, bckfile, DEFAULT_ENCODING);
    blr.addByteInt32(isc_spb_nbk_level, level);
    blr.addString2(isc_spb_nbk_direct, direct, DEFAULT_ENCODING);
    var opts = 0;
    if (notriggers) opts = opts | isc_spb_nbk_no_triggers;
    blr.addByteInt32(isc_spb_options, opts);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.restore = function(options, callback) {
    var bckfiles = options.backupfiles || options.files || null; // format bckfiles ['file1', 'file2', 'file3']
    // for convenience
    if (bckfiles) bckfiles = bckfiles.constructor !== Array?[bckfiles]:bckfiles;
    var dbfile = options.database || this.connection.options.filename || this.connection.options.database;;
    var verbose = options.verbose || false;
    var cachebuffers = options.cachebuffers || 2048; // gbak -buffers
    var pagesize = options.pagesize || 4096; // gbak -page_size
    var readonly = options.readonly || false; // gbak -mode
    var deactivateindexes = options.deactivateindexes || false;
    var	noshadow = options.noshadow || false;
    var	novalidity = options.novalidity || false;
    var	individualcommit = options.individualcommit || true; // otherwise no data
    var	replace = options.replace || false;
    var	create = options.create || true;
    var useallspace = options.useallspace || false;
    var metadataonly = options.metadataonly || false;
    var fixfssdata = options.fixfssdata || null;
    var fixfssmetadata = options.fixfssmetadata || null;

    if (bckfiles == null || bckfiles.length === 0) {
        doError(new Error('No backup file specified'), callback);
        return;
    }

    if (dbfile == null || dbfile.length === 0) {
        doError(new Error('No database path specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_restore);
    for (var i = 0; i < bckfiles.length; i++) {
        blr.addString2(isc_spb_bkp_file, bckfiles[i], DEFAULT_ENCODING);
    }
    blr.addString2(isc_spb_dbname, dbfile, DEFAULT_ENCODING);
    blr.addByte(isc_spb_res_buffers);
    blr.addInt32(cachebuffers);
    blr.addByte(isc_spb_res_page_size);
    blr.addInt32(pagesize);
    blr.addByte(isc_spb_res_access_mode);
    if (readonly)
        blr.addByte(isc_spb_prp_am_readonly);
    else
        blr.addByte(isc_spb_prp_am_readwrite);
    if (fixfssdata) blr.addString2(isc_spb_res_fix_fss_data, fixfssdata, DEFAULT_ENCODING);
    if (fixfssmetadata) blr.addString2(isc_spb_res_fix_fss_metadata, fixfssmetadata, DEFAULT_ENCODING);
    var opts = 0;
    if (deactivateindexes) opts = opts | isc_spb_res_deactivate_idx;
    if (noshadow) opts = opts | isc_spb_res_no_shadow;
    if (novalidity) opts = opts | isc_spb_res_no_validity;
    if (individualcommit) opts = opts | isc_spb_res_one_at_a_time;
    if (replace) opts = opts | isc_spb_res_replace;
    if (create) opts = opts | isc_spb_res_create;
    if (useallspace) opts = opts | isc_spb_res_use_all_space;
    if (metadataonly) opts = opts | isc_spb_res_fix_fss_metadata;
    if (opts)
        blr.addByteInt32(isc_spb_options, opts);
    if (verbose)
        blr.addByte(isc_spb_verbose);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.nrestore = function (options, callback) {
    var bckfiles = options.backupfiles || options.files || null; // format bckfiles ['file1', 'file2', 'file3']
    // for convenience
    if (bckfiles) bckfiles = bckfiles.constructor !== Array?[bckfiles]:bckfiles;
    var dbpath = options.database || this.connection.options.filename || this.connection.options.database;

    if (bckfiles == null || bckfiles.length === 0) {
        doError(new Error('No backup file specified'), callback);
        return;
    }

    if (dbpath == null || bckfiles.length === 0) {
        doError(new Error('No database path specified'), callback);
        return;
    }
    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_nrest);
    for (var i = 0; i < bckfiles.length; i++) {
        blr.addString2(isc_spb_nbk_file, bckfiles[i], DEFAULT_ENCODING);
    }
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

// only one at time don't use this function directly
ServiceManager.prototype._fixpropertie = function (options, callback) {
    var dbpath = options.database || this.connection.options.filename || this.connection.options.database;
    var dialect = options.dialect || null;
    var sweep = options.sweepinterval || null;
    var pagebuffers = options.nbpagebuffers || null;
    var online = options.bringonline || false;
    var shutdown = options.shutdown != null ? options.shutdown : null; // 0 Forced, 1 deny transaction, 2 deny attachment
    var shutdowndelay = options.shutdowndelay || 0;
    var shutdownmode = options.shutdownmode; // 0 normal 1 multi 2 single 3 full
    var shadow = options.activateshadow || false;
    var forcewrite = options.forcewrite;
    var reservespace = options.reservespace;
    var accessmode = options.accessmode; // 0 readonly 1 readwrite

    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_properties);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    if (dialect) blr.addByteInt32(isc_spb_prp_set_sql_dialect, dialect);
    if (sweep) blr.addByteInt32(isc_spb_prp_sweep_interval, sweep);
    if (pagebuffers) blr.addByteInt32(isc_spb_prp_page_buffers, pagebuffers);
    if (shutdown != null) {
    	if (shutdownmode != null) {
    		if (SHUTDOWNEX_KIND[shutdown] === undefined) {
				doError(new Error('Invalid shutdown kind'), callback);
				return;
			}
    		if (SHUTDOWNEX_MODE[shutdownmode] === undefined) {
    			doError(new Error('Invalid shutdown mode'), callback);
    			return;
			}

    		// New shutdown with mode
			blr.addBytes([isc_spb_prp_shutdown_mode, SHUTDOWNEX_MODE[shutdownmode]]);
			blr.addByteInt32(SHUTDOWNEX_KIND[shutdown], shutdowndelay);
		} else {
    		// Old shutdown
    		blr.addByteInt32(SHUTDOWN_KIND[shutdown], shutdowndelay);
		}
    }
    if (forcewrite) blr.addBytes([isc_spb_prp_write_mode, isc_spb_prp_wm_sync]);
    if (forcewrite === false) blr.addBytes([isc_spb_prp_write_mode, isc_spb_prp_wm_async]);
    if (accessmode === 1) blr.addBytes([isc_spb_prp_access_mode, isc_spb_prp_am_readwrite]);
    if (accessmode === 0) blr.addBytes([isc_spb_prp_access_mode, isc_spb_prp_am_readonly]);
    if (reservespace) blr.addBytes([isc_spb_prp_reserve_space, isc_spb_prp_res]);
    if (reservespace != null && !reservespace) blr.addBytes([isc_spb_prp_reserve_space, isc_spb_prp_res_use_full]);
    var opts = 0;
    if (shadow) opts = opts | isc_spb_prp_activate;
    if (online) opts = opts | isc_spb_prp_db_online;
    if (opts)
        blr.addByteInt32(isc_spb_options, opts);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.setDialect = function (db, dialect, callback) {
    this._fixpropertie({ database: db, dialect: dialect }, callback);
}

ServiceManager.prototype.setSweepinterval = function (db, sweepinterval, callback) {
    this._fixpropertie({ database: db, sweepinterval: sweepinterval }, callback);
}

ServiceManager.prototype.setCachebuffer = function (db, nbpages, callback) {
    this._fixpropertie({ database: db, nbpagebuffers: nbpages }, callback);
}

ServiceManager.prototype.BringOnline = function (db, callback) {
    this._fixpropertie({ database: db, bringonline: true }, callback);
}

const SHUTDOWN_KIND = {
	0: isc_spb_prp_shutdown_db,
	1: isc_spb_prp_deny_new_transactions,
	2: isc_spb_prp_deny_new_attachments
};
const SHUTDOWNEX_KIND = {
	0: isc_spb_prp_force_shutdown,
	1: isc_spb_prp_transactions_shutdown,
	2: isc_spb_prp_attachments_shutdown
};
const SHUTDOWNEX_MODE = {
	//0: isc_spb_prp_sm_normal,
	1: isc_spb_prp_sm_multi,
	2: isc_spb_prp_sm_single,
	3: isc_spb_prp_sm_full
};
const ShutdownMode = { NORMAL: 0, MULTI: 1, SINGLE: 2, FULL: 3 };
const ShutdownKind = { FORCED: 0, DENY_TRANSACTION: 1, DENY_ATTACHMENT: 2 };
exports.ShutdownMode = ShutdownMode;
exports.ShutdownKind = ShutdownKind;

ServiceManager.prototype.Shutdown = function (db, kind, delay, mode, callback) {
    // mode parameter is for server version >= 2.0
	if (mode instanceof Function) {
		callback = mode;
		mode = undefined;
	}

    this._fixpropertie({ database: db, shutdown: kind, shutdowndelay: delay, shutdownmode: mode }, callback);
}

ServiceManager.prototype.setShadow = function (db, val, callback) {
    this._fixpropertie({ database: db, activateshadow : val }, callback);
}

ServiceManager.prototype.setForcewrite = function (db, val, callback) {
    this._fixpropertie({ database: db, forcewrite : val }, callback);
}

ServiceManager.prototype.setReservespace = function (db, val, callback) {
    this._fixpropertie({ database: db, reservespace : val }, callback);
}

ServiceManager.prototype.setReadonlyMode = function (db, callback) {
    this._fixpropertie({ database: db, accessmode : 0 }, callback);
}

ServiceManager.prototype.setReadwriteMode = function (db, callback) {
    this._fixpropertie({ database: db, accessmode : 1 }, callback);
}

ServiceManager.prototype.validate = function (options, callback) {
    var dbpath = options.database || this.connection.options.filename || this.connection.options.database;
    var checkdb = options.checkdb || false;
    var ignorechecksums = options.ignorechecksums || false;
    var killshadows = options.killshadows || false;
    var mend = options.mend || false;
    var validate = options.validate || false;
    var full = options.full || false;
    var sweep = options.sweep || false;
    var listlimbo = options.listlimbo || false;
    var icu = options.icu || false;

    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_repair);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    var opts = 0;
    if (checkdb) opts = opts | isc_spb_rpr_check_db;
    if (ignorechecksums) opts = opts | isc_spb_rpr_ignore_checksum;
    if (killshadows) opts = opts | isc_spb_rpr_kill_shadows;
    if (mend) opts = opts | isc_spb_rpr_mend_db;
    if (validate) opts = opts | isc_spb_rpr_validate_db;
    if (full) opts = opts | isc_spb_rpr_full;
    if (sweep) opts = opts | isc_spb_rpr_sweep_db;
    if (listlimbo) opts = opts | isc_spb_rpr_list_limbo_trans;
    if (icu) opts = opts | isc_spb_rpr_icu;
    blr.addByteInt32(isc_spb_options, opts);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.commit = function(db, transactid, callback) {
    var dbpath = db || this.connection.options.filename || this.connection.options.database;
    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_repair);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    blr.addByteInt32(isc_spb_rpr_commit_trans, transactid);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(null, null, callback);
    });
}

ServiceManager.prototype.rollback = function (db, transactid, callback) {
    var dbpath = db || this.connection.options.filename || this.connection.options.database;
    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_repair);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    blr.addByteInt32(isc_spb_rpr_rollback_trans, transactid);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(null, null, callback);
    });
}

ServiceManager.prototype.recover = function (db, transactid, callback) {
    var dbpath = db || this.connection.options.filename || this.connection.options.database;
    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_repair);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    blr.addByteInt32(isc_spb_rpr_recover_two_phase, transactid);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(null, null, callback);
    });
}

ServiceManager.prototype.getStats = function (options, callback) {
    var dbpath = options.database || this.connection.options.filename || this.connection.options.database;
    var record = options.record || false;
    var nocreation = options.nocreation || false;
    var tables = options.tables || false;
    var pages = options.pages || false;
    var header = options.header || false;
    var indexes = options.indexes || false;
    var tablesystem = options.tablesystem || false;
    var encryption = options.encryption || false;
    var objects = options.objects || null; // space-separated list of object index,table,systemtable
    if (dbpath == null || dbpath.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_db_stats);
    blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING);
    var opts = 0;
    if (record) opts = opts | isc_spb_sts_record_versions;
    if (nocreation) opts = opts | isc_spb_sts_nocreation;
    if (tables) opts = opts | isc_spb_sts_table;
    if (pages) opts = opts | isc_spb_sts_data_pages;
    if (header) opts = opts | isc_spb_sts_hdr_pages;
    if (indexes) opts = opts | isc_spb_sts_idx_pages;
    if (tablesystem) opts = opts | isc_spb_sts_sys_relations;
    if (encryption) opts = opts | isc_spb_sts_encryption;
    if (opts)
        blr.addByteInt32(isc_spb_options, opts);
    if (objects) blr.addString2(isc_spb_command_line, objects, DEFAULT_ENCODING);
    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });

}

ServiceManager.prototype.getLog = function (options, callback) {
    var self = this;
    var blr = this.connection._blr;
    var optread = options.optread || 'byline';
    blr.pos = 0;
    blr.addByte(isc_action_svc_get_fb_log);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.getUsers = function (username, callback) {
    var self = this;
    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_display_user);
    if (username) blr.addString2(isc_spb_sec_username, username, DEFAULT_ENCODING);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self.readusers({}, callback);
    });
}

ServiceManager.prototype.addUser = function (username, password, options, callback) {
    var rolename = options.rolename || null;
    var groupname = options.groupname || null;
    var firsname = options.firstname || null;
    var middlename = options.middlename || null;
    var lastname = options.lastname || null;
    var userid = options.userid || null;
    var groupid = options.groupid || null;
    var admin = options.admin || null;

    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_add_user);
    blr.addString2(isc_spb_sec_username, username, DEFAULT_ENCODING);
    blr.addString2(isc_spb_sec_password, password, DEFAULT_ENCODING);
    if (rolename) blr.addString2(isc_dpb_sql_role_name, rolename, DEFAULT_ENCODING);
    if (groupname) blr.addString2(isc_spb_sec_groupname, groupname, DEFAULT_ENCODING);
    if (firsname) blr.addString2(isc_spb_sec_firstname, firsname, DEFAULT_ENCODING);
    if (middlename) blr.addString2(isc_spb_sec_middlename, middlename, DEFAULT_ENCODING);
    if (lastname) blr.addString2(isc_spb_sec_lastname, lastname, DEFAULT_ENCODING);
    if (userid != null) blr.addByteInt32(isc_spb_sec_userid, userid);
    if (groupid != null) blr.addByteInt32(isc_spb_sec_groupid, groupid);
    if (admin != null) blr.addByteInt32(isc_spb_sec_admin, admin);

	var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.editUser = function (username, options, callback) {
    var rolename = options.rolename || null;
    var groupname = options.groupname || null;
    var firsname = options.firstname || null;
    var middlename = options.middlename || null;
    var lastname = options.lastname || null;
    var userid = options.userid || null;
    var groupid = options.groupid || null;
    var admin = options.admin || null;
	var password = options.password || null;
    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_modify_user);
    blr.addString2(isc_spb_sec_username, username, DEFAULT_ENCODING);
    if (password) blr.addString2(isc_spb_sec_password, password, DEFAULT_ENCODING);
    if (rolename) blr.addString2(isc_dpb_sql_role_name, rolename, DEFAULT_ENCODING);
    if (groupname) blr.addString2(isc_spb_sec_groupname, groupname, DEFAULT_ENCODING);
    if (firsname) blr.addString2(isc_spb_sec_firstname, firsname, DEFAULT_ENCODING);
    if (middlename) blr.addString2(isc_spb_sec_middlename, middlename, DEFAULT_ENCODING);
    if (lastname) blr.addString2(isc_spb_sec_lastname, lastname, DEFAULT_ENCODING);
    if (userid != null) blr.addByteInt32(isc_spb_sec_userid, userid);
    if (groupid != null) blr.addByteInt32(isc_spb_sec_groupid, groupid);
    if (admin != null) blr.addByteInt32(isc_spb_sec_admin, admin);

    var self = this;
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.removeUser = function (username, rolename, callback) {
    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_delete_user);
    blr.addString2(isc_spb_sec_username, username, DEFAULT_ENCODING);
    if (rolename) blr.addString2(isc_dpb_sql_role_name, rolename, DEFAULT_ENCODING);

    var self = this, options = {};
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.getFbserverInfos = function (infos, options, callback) {
    var buffersize = options.buffersize || 2048;
    var timeout = options.timeout || 1;
    var opts = {
        "dbinfo" : isc_info_svc_svr_db_info,
        "fbconfig" : isc_info_svc_get_config,
        "svcversion" : isc_info_svc_version,
        "fbversion" : isc_info_svc_server_version,
        "fbimplementation" : isc_info_svc_implementation,
        "fbcapatibilities" : isc_info_svc_capabilities,
        "pathsecuritydb" : isc_info_svc_user_dbpath,
        "fbenv" : isc_info_svc_get_env,
        "fbenvlock" : isc_info_svc_get_env_lock,
        "fbenvmsg" : isc_info_svc_get_env_msg
    };
    // if infos is empty all options are asked to the service

    var tops = [], empty = isEmpty(infos);
    for (popts in opts)
        if (empty || infos[popts])
            tops.push(opts[popts]);

    var self = this;
    this.connection.svcquery(tops, buffersize, timeout, function (err, data) {
        if (err || !data.buffer) {
            doError(new Error(err||'Bad query return'), callback);
            return;
        }
        self._processquery(data.buffer, callback);
    });
}

function isEmpty(obj){
    for(var p in obj) return false;
    return true;
}

ServiceManager.prototype.startTrace = function (options, callback) {
    var self = this;
    var blr = this.connection._blr;
    var configfile = options.configfile || '';
    var tracename = options.tracename || '';

    if (configfile.length === 0) {
        doError(new Error('No config filename specified'), callback);
        return;
    }
    if (tracename.length === 0) {
        doError(new Error('No tracename specified'), callback);
        return;
    }

    blr.pos = 0;
    blr.addByte(isc_action_svc_trace_start);
    blr.addString2(isc_spb_trc_cfg, configfile, DEFAULT_ENCODING);
    blr.addString2(isc_spb_trc_name, tracename, DEFAULT_ENCODING);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.suspendTrace = function (options, callback) {
    var self = this;
    var blr = this.connection._blr;
    var traceid = options.traceid || null;

    if (traceid == null) {
        doError(new Error('No traceid specified'), callback);
        return;
    }

    blr.pos = 0;
    blr.addByte(isc_action_svc_trace_suspend);
    blr.addByteInt32(isc_spb_trc_id, traceid);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.resumeTrace = function (options, callback) {
    var self = this;
    var blr = this.connection._blr;
    var traceid = options.traceid || null;

    if (traceid == null) {
        doError(new Error('No traceid specified'), callback);
        return;
    }

    blr.pos = 0;
    blr.addByte(isc_action_svc_trace_resume);
    blr.addByteInt32(isc_spb_trc_id, traceid);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.stopTrace = function (options, callback) {
    var self = this;
    var blr = this.connection._blr;
    var traceid = options.traceid || null;

    if (traceid == null) {
        doError(new Error('No traceid specified'), callback);
        return;
    }

    blr.pos = 0;
    blr.addByte(isc_action_svc_trace_stop);
    blr.addByteInt32(isc_spb_trc_id, traceid);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.getTraceList = function (options, callback) {
    var self = this;
    var blr = this.connection._blr;
    blr.pos = 0;
    blr.addByte(isc_action_svc_trace_list);
    this.connection.svcstart(blr, function (err, data) {
        if (err) {
            doError(new Error(err), callback);
            return;
        }
        self._createOutputStream(options.optread, options.buffersize, callback);
    });
}

ServiceManager.prototype.readline = function (options, callback) {
    var buffersize = options.buffersize || 2048;
    var timeout = options.timeout || 60;
    var self = this;
    this.connection.svcquery([isc_info_svc_line], buffersize, timeout, function (err, data) {
        if (err || !data.buffer) {
            doError(new Error(err||'Bad query return'), callback);
            return;
        }
        self._processquery(data.buffer, callback);
    });
}

ServiceManager.prototype.readeof = function (options, callback) {
    var buffersize = options.buffersize || (8 * 1024);
    var timeout = options.timeout || 60;
    var self = this;
    this.connection.svcquery([isc_info_svc_to_eof], buffersize, timeout, function (err, data) {
        if (err || !data.buffer) {
            doError(new Error(err||'Bad query return'), callback);
            return;
        }
        self._processquery(data.buffer, callback);
    });
}

ServiceManager.prototype.hasRunningAction = function (options, callback) {
    var buffersize = options.buffersize || 2048;
    var timeout = options.timeout || 60;
    var self = this;
    this.connection.svcquery([isc_info_svc_running], buffersize, timeout, function (err, data) {
        if (err || !data.buffer) {
            doError(new Error(err||'Bad query return'), callback);
            return;
        }
        self._processquery(data.buffer, callback);
    });
}

ServiceManager.prototype.readusers = function (options, callback) {
    var buffersize = options.buffersize || 2048;
    var timeout = options.timeout || 60;
    var self = this;
    this.connection.svcquery([isc_info_svc_get_users], buffersize, timeout, function (err, data) {
        if (err || !data.buffer) {
            doError(new Error(err||'Bad query return'), callback);
            return;
        }
        self._processquery(data.buffer, callback);
    });
}

ServiceManager.prototype.readlimbo = function (options, callback) {
    var buffersize = options.buffersize || 2048;
    var timeout = options.timeout || 60;
    var self = this;
    this.connection.svcquery([isc_info_svc_limbo_trans], buffersize, timeout, function (err, data) {
        if (err || !data.buffer) {
            doError(new Error(err||'Bad query return'), callback);
            return;
        }
        self._processquery(data.buffer, callback);
    });
}

// Pooling
exports.pool = function(max, options) {
	return new Pool(max, Object.assign({}, options, { isPool: true }));
};

/***************************************
 *
 *   Simple Pooling
 *
 ***************************************/

function Pool(max, options) {
    this.internaldb = []; // connection created by the pool (for destroy)
    this.pooldb = []; // available connection in the pool
    this.dbinuse = 0; // connection currently in use into the pool
    this.max = max || 4;
    this.pending = [];
    this.options = options;
}

Pool.prototype.get = function(callback) {
    var self = this;
    self.pending.push(callback);
    self.check();
    return self;
};

Pool.prototype.check = function() {

    var self = this;
    if (self.dbinuse >= self.max)
        return self;

    var cb = self.pending.shift();
    if (!cb)
        return self;
    self.dbinuse++;
    if (self.pooldb.length) {
        cb(null, self.pooldb.shift());
    } else {
        exports.attach(self.options, function (err, db) {
            if (!err) {
                self.internaldb.push(db);
                db.on('detach', function () {
                    // also in pool (could be a twice call to detach)
                    if (self.pooldb.indexOf(db) !== -1 || self.internaldb.indexOf(db) === -1)
                        return;
                    // if not usable don't put in again in the pool and remove reference on it
                    if (db.connection._isClosed || db.connection._isDetach || db.connection._pooled === false)
                        self.internaldb.splice(self.internaldb.indexOf(db), 1);
                    else
                        self.pooldb.push(db);

                    if (db.connection._pooled)
                        self.dbinuse--;
                    self.check();
                });
            } else {
                // attach fail so not in the pool
                self.dbinuse--;
            }

            cb(err, db);
        });
    }
    setImmediate(function() {
        self.check();
    });

    return self;
};

Pool.prototype.destroy = function(callback) {
    var self = this;

    var connectionCount = this.internaldb.length;

    if (connectionCount === 0 && callback) {
        callback();
    }

    function detachCallback(err) {
        if (err) {
            if (callback) {
                callback(err);
            }
            return;
        }

        connectionCount--;
        if (connectionCount === 0 && callback) {
            callback();
        }
    }

    this.internaldb.forEach(function(db) {
        if (db.connection._pooled === false) {
            detachCallback();
            return;
        }
        // check if the db is not free into the pool otherwise user should manual detach it
        var _db_in_pool = self.pooldb.indexOf(db);
        if (_db_in_pool !== -1) {
            self.pooldb.splice(_db_in_pool, 1);
            db.connection._pooled = false;
            db.detach(detachCallback);
        }
    });
};

/***************************************
 *
 *   Connection
 *
 ***************************************/

var Connection = exports.Connection = function (host, port, callback, options, db, svc) {
    var self = this;
    this.db = db;
    this.svc = svc
    this._msg = new XdrWriter(32);
    this._blr = new BlrWriter(32);
    this._queue = [];
    this._detachTimeout;
    this._detachCallback;
    this._detachAuto;
    this._socket = net.createConnection(port, host);
    this._pending = [];
    this._isOpened = false;
    this._isClosed = false;
    this._isDetach = false;
    this._isUsed = false;
    this._pooled = options.isPool||false;
    this.options = options;
    this._bind_events(host, port, callback);
    this.error;
    this._retry_connection_id;
    this._retry_connection_interval = options.retryConnectionInterval || 1000;
    this._max_cached_query = options.maxCachedQuery || -1;
    this._cache_query = options.cacheQuery?{}:null;
    this._messageFile = options.messageFile || path.join(__dirname, 'firebird.msg');
};

exports.Connection.prototype._setcachedquery = function (query, statement) {
    if (this._cache_query){
		if (this._max_cached_query === -1 || this._max_cached_query > Object.keys(this._cache_query).length){
			this._cache_query[query] = statement;
		}
	}


};

exports.Connection.prototype.getCachedQuery = function (query) {
        return this._cache_query ? this._cache_query[query] : null;
};

exports.Connection.prototype._bind_events = function(host, port, callback) {

    var self = this;

    self._socket.on('close', function() {

        if (!self._isOpened || self._isDetach) {
            return;
        }

        self._isOpened = false;

        if (!self.db) {
            if (callback)
                callback(self.error);
            return;
        }

       self._retry_connection_id = setTimeout(function() {
            self._socket.removeAllListeners();
            self._socket = null;

            var ctx = new Connection(host, port, function(err) {
                ctx.connect(self.options, function(err) {

                    if (err) {
                        self.db.emit('error', err);
                        return;
                    }

                    ctx.attach(self.options, function(err) {

                        if (err) {
                            self.db.emit('error', err);
                            return;
                        }

                        ctx._queue = ctx._queue.concat(self._queue);
                        ctx._pending = ctx._pending.concat(self._pending);
                        self.db.emit('reconnect');

                    }, self.db);
                });

				Object.assign(self, ctx);

            }, self.options, self.db);
        }, self._retry_connection_interval);

    });

    self._socket.on('error', function(e) {

        self.error = e;

        if (self.db)
            self.db.emit('error', e)

        if (callback)
            callback(e);

    });

    self._socket.on('connect', function() {
        self._isClosed = false;
        self._isOpened = true;
        if (callback)
            callback();
    });

    self._socket.on('data', function (data) {
        var xdr;

        if (!self._xdr) {
            xdr = new XdrReader(data);
        } else {
            xdr = new XdrReader(Buffer.concat([self._xdr.buffer, data], self._xdr.buffer.length + data.length));
            delete (self._xdr);
        }

        while (xdr.pos < xdr.buffer.length) {
            var cb = self._queue[0], pos = xdr.pos;

            decodeResponse(xdr, cb, self, self._lowercase_keys, function (err, obj) {
				// STAFOCHER
				/*if (err) {
					xdr.buffer = xdr.buffer.slice(pos);
					xdr.pos = 0;
					self._xdr = xdr;

					if (self.accept.protocolMinimumType === ptype_lazy_send && self._queue.length > 0 || self._queue[0] || self._queue[0].lazy_count) {
						self._queue[0].lazy_count = 2;
					}
					return;
				}*/

                if (err) {
                    xdr.buffer = xdr.buffer.slice(pos);
                    xdr.pos = 0;
                    self._xdr = xdr;

                    if (self.accept.protocolMinimumType === ptype_lazy_send && self._queue.length > 0) {
                        self._queue[0].lazy_count = 2;					
					}
					
                    return;
                }

                // remove the op flag, needed for partial packet
                if (xdr.r) {
                    delete (xdr.r);
                }

                self._queue.shift();
                self._pending.shift();

                if (obj && obj.status) {
                    messages.lookupMessages(obj.status, self._messageFile, function (message) {
                        obj.message = message;
                        doCallback(obj, cb);
                    });

                } else {
                    doCallback(obj, cb);
                }

            });

            if (xdr.pos === 0) {
                break;
            }
        }

        if (!self._detachAuto || self._pending.length !== 0) {
            return;
        }

        clearTimeout(self._detachTimeout);
        self._detachTimeout = setTimeout(function () {
            self.db.detach(self._detachCallback);
            self._detachAuto = false;
        }, 100);

    });
}

exports.Connection.prototype.disconnect = function() {
    this._socket.end();
};


function decodeResponse(data, callback, cnx, lowercase_keys, cb) {
    try {
        do {
            var r = data.r || data.readInt();
        } while (r === op_dummy);

        var item, op, response;

        switch (r) {
            case op_response:

                if (callback) {
                    response = callback.response || {};
                } else {
                    response = {};
                }

                let loop = function (err) {
                    if (err) {
                        return cb(err);
                    } else {
                        if (callback && callback.lazy_count) {
                            callback.lazy_count--;
                            if (callback.lazy_count > 0) {
                                r = data.readInt(); // Read new op
                                parseOpResponse(data, response, loop);
                            } else {
                                cb(null, response);
                            }
                        } else {
                            cb(null, response);
                        }
                    }
                };
                // Parse normal and lazy response
                return parseOpResponse(data, response, loop);
            case op_fetch_response:
            case op_sql_response:
                var statement = callback.statement;
                var output = statement.output;
                var custom = statement.custom || {};
                var isOpFetch = r === op_fetch_response;
                var _xdrpos;
                statement.nbrowsfetched = statement.nbrowsfetched || 0;

                if (isOpFetch && data.fop) { // could be set when a packet is not complete
                    data.readBuffer(68); // ??
                    op = data.readInt(); // ??
                    data.fop = false;
                    if (op === op_response) {
                        return parseOpResponse(data, {}, cb);
                    }
                }

                if (!isOpFetch) {
                    data.fstatus = 0;
                }

                data.fstatus = data.fstatus !== undefined ? data.fstatus : data.readInt();
                data.fcount = data.fcount !== undefined ? data.fcount : data.readInt();
                data.fcolumn = data.fcolumn || 0;
                data.frow = data.frow || (custom.asObject ? {} : new Array(output.length));
                data.frows = data.frows || [];

                if (custom.asObject && !data.fcols) {
                    data.fcols = [];
                    for (var i = 0, length = output.length; i < length; i++) {
                        data.fcols.push(lowercase_keys ? output[i].alias.toLowerCase() : output[i].alias);
                    }
                }

                while (data.fcount && (data.fstatus !== 100)) {
                    var lowerV13 = statement.connection.accept.protocolVersion < PROTOCOL_VERSION13;

                    if (lowerV13) {
                        for (length = output.length; data.fcolumn < length; data.fcolumn++) {
                            item = output[data.fcolumn];

                            try {
                                _xdrpos = data.pos;
                                let key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
                                let value = item.decode(data, lowerV13);
                                if (item.type === SQL_BLOB) {
                                    value = fetch_blob_async(statement, value, key);
                                }
                                data.frow[key] = value;
                            } catch (e) {
                                // uncomplete packet read
                                data.pos = _xdrpos;
                                data.r = r;
                                return cb(new Error("Packet is not complete"));
                            }

                        }
                    } else {
                        var nullBitsLen = Math.floor((output.length + 7) / 8);
                        var nullBitSet = new BitSet(data.readBuffer(nullBitsLen, false));
                        data.readBuffer((4 - nullBitsLen) & 3, false); // Skip padding

                        for (length = output.length; data.fcolumn < length; data.fcolumn++) {
                            item = output[data.fcolumn];

                            if (nullBitSet.get(data.fcolumn)) {
                                if (custom.asObject) {
                                    data.frow[data.fcols[data.fcolumn]] = null;
                                } else {
                                    data.frow[data.fcolumn] = null;
                                }

                                continue;
                            }

                            try {
                                _xdrpos = data.pos;
                                var key = custom.asObject ? data.fcols[data.fcolumn] : data.fcolumn;
                                var value = item.decode(data, lowerV13);
                                if (item.type === SQL_BLOB) {
                                    value = fetch_blob_async(statement, value, key);
                                }
                                data.frow[key] = value;
                            } catch (e) {
                                // uncomplete packet read
                                data.pos = _xdrpos;
                                data.r = r;
                                return cb(new Error("Packet is not complete"));
                            }
                        }
                    }

                    data.fcolumn = 0;
                    statement.connection.db.emit('row', data.frow, statement.nbrowsfetched, custom.asObject);
                    data.frows.push(data.frow);
                    data.frow = custom.asObject ? {} : new Array(output.length);

                    try {
                        _xdrpos = data.pos;
                        if (isOpFetch) {
                            delete data.fstatus;
                            delete data.fcount;
                            op = data.readInt(); // ??
                            if (op === op_response) {
                                return parseOpResponse(data, {}, cb);
                            }
                            data.fstatus = data.readInt();
                            data.fcount = data.readInt();
                        } else {
                            data.fcount--;
                            if (r === op_sql_response) {
                                op = data.readInt();
                                if (op === op_response) {
                                    parseOpResponse(data, {});
                                }
                            }
                        }
                    } catch (e) {
                        if (_xdrpos === data.pos) {
                            data.fop = true;
                        }
                        data.r = r;
                        return cb(new Error("Packet is not complete"));
                    }
                    statement.nbrowsfetched++;
                }

                statement.connection.db.emit('result', data.frows);
                return cb(null, {data: data.frows, fetched: Boolean(!isOpFetch || data.fstatus === 100)});
            case op_accept:
            case op_cond_accept:
            case op_accept_data:
                let accept = {
                    protocolVersion: data.readInt(),
                    protocolArchitecture: data.readInt(),
                    protocolMinimumType: data.readInt(),
                    pluginName: '',
                    authData: '',
					sessionKey: ''
                };

                accept.protocolMinimumType = accept.protocolMinimumType & 0xFF;
				//accept.compress = (accept.acceptType & pflag_compress) !== 0; // TODO Handle zlib compression
                if (accept.protocolVersion < 0) {
                    accept.protocolVersion = (accept.protocolVersion & FB_PROTOCOL_MASK) | FB_PROTOCOL_FLAG;
                }

                if (r === op_cond_accept || r === op_accept_data) {
                    var d = new BlrReader(data.readArray());
                    accept.pluginName = data.readString(DEFAULT_ENCODING);
                    var is_authenticated = data.readInt();
                    var keys = data.readString(DEFAULT_ENCODING); // keys

                    if (is_authenticated === 0) {
                    	if (cnx.options.pluginName && cnx.options.pluginName !== accept.pluginName) {
                    		doError(new Error('Server don\'t accept plugin : ' + cnx.options.pluginName + ', but support : ' + accept.pluginName), callback);
						}

                    	if (AUTH_PLUGIN_SRP_LIST.indexOf(accept.pluginName) !== -1) {
							var crypto = {
								Srp: 'sha1',
								Srp256: 'sha256'
							};
							accept.srpAlgo = crypto[accept.pluginName];

							// TODO : Fallback Srp256 to Srp ?
							/*if (!d.buffer) {
								cnx.sendOpContAuth(
									cnx.clientKeys.public.toString(16),
									DEFAULT_ENCODING,
									accept.pluginName
								);

								return cb(new Error('login'));
							}*/

							// Check buffer contains salt
							var saltLen = d.buffer.readUInt16LE(0);
							if (saltLen > 32 * 2) {
								console.log('salt to long'); // TODO : Throw error
							}

							// Check buffer contains key
							var keyLen = d.buffer.readUInt16LE(saltLen + 2);
							var keyStart = saltLen + 4;
							if (d.buffer.length - keyStart !== keyLen) {
								console.log('key error'); // TODO : Throw error
							}

							// Server keys
							cnx.serverKeys = {
								salt: d.buffer.slice(2, saltLen + 2).toString('utf8'),
								public: BigInt(d.buffer.slice(keyStart, d.buffer.length).toString('utf8'), 16)
							};

							var proof = srp.clientProof(
								cnx.options.user.toUpperCase(),
								cnx.options.password,
								cnx.serverKeys.salt,
								cnx.clientKeys.public,
								cnx.serverKeys.public,
								cnx.clientKeys.private,
								accept.srpAlgo
							);

							accept.authData = proof.authData.toString(16);
							accept.sessionKey = proof.clientSessionKey;
						} else if (accept.pluginName === AUTH_PLUGIN_LEGACY) {
                            accept.authData = crypt.crypt(cnx.options.password, LEGACY_AUTH_SALT).substring(2);
                        } else {
                            return cb(new Error('Unknow auth plugin : ' + accept.pluginName));
                        }
                    } else {
                        accept.authData = '';
                        accept.sessionKey = '';
                    }
                }

                return cb(undefined, accept);
			case op_cont_auth:
				var d = new BlrReader(data.readArray());
				var pluginName = data.readString(DEFAULT_ENCODING);
				data.readString(DEFAULT_ENCODING); // plist
				data.readString(DEFAULT_ENCODING); // pkey

				if (!cnx.options.pluginName) {
					if (cnx.accept.pluginName === pluginName) {
						// Erreur plugin not able to connect
						return cb(new Error("Unable to connect with plugin " + cnx.accept.pluginName));
					}

					if (pluginName === AUTH_PLUGIN_LEGACY) { // Fallback to LegacyAuth
						cnx.accept.pluginName = pluginName;
						cnx.accept.authData = crypt.crypt(cnx.options.password, LEGACY_AUTH_SALT).substring(2);

						cnx.sendOpContAuth(
							cnx.accept.authData,
							DEFAULT_ENCODING,
							pluginName
						);

						return {error: new Error('login')};
					}
				}

				return data.accept;
            default:
                return cb(new Error('Unexpected:' + r));
        }
    } catch (err) {
        if (err instanceof RangeError) {
            return cb(err);
        }
        throw err;
    }
}

function parseOpResponse(data, response, cb) {
    var handle = data.readInt();

    if (!response.handle) {
        response.handle = handle;
    }

    var oid = data.readQuad();
    if (oid.low || oid.high) {
        response.oid = oid;
    }

    var buf = data.readArray();
    if (buf) {
        response.buffer = buf;
    }

    var num, op, item = {};
    while (true) {
        op = data.readInt();

        switch (op) {
            case isc_arg_end:
                return cb ? cb(undefined, response) : response;
            case isc_arg_gds:
                num = data.readInt();
                if (!num) {
                    break;
                }

                item = {gdscode: num};

                if (response.status) {
                    response.status.push(item);
                } else {
                    response.status = [item];
                }

                break;
            case isc_arg_string:
            case isc_arg_interpreted:
            case isc_arg_sql_state:
                if (item.params) {
                    var str = data.readString(DEFAULT_ENCODING);
                    item.params.push(str);
                } else {
                    item.params = [data.readString(DEFAULT_ENCODING)];
                }

                break;
            case isc_arg_number:
                num = data.readInt();

                if (item.params) {
                    item.params.push(num);
                } else {
                    item.params = [num];
                }

                if (item.gdscode === isc_sqlerr) {
                    response.sqlcode = num;
                }

                break;
            default:
                if (cb) {
                    cb(new Error('Unexpected: ' + op))
                } else {
                    throw new Error('Unexpected: ' + op);
                }
        }
    }
}

Connection.prototype.sendOpContAuth = function(authData, authDataEnc, pluginName) {
	var msg = this._msg;
	msg.pos = 0;

	msg.addInt(op_cont_auth);
	msg.addString(authData, authDataEnc);
	msg.addString(pluginName, DEFAULT_ENCODING)
	msg.addString(AUTH_PLUGIN_LIST.join(','), DEFAULT_ENCODING);
	// msg.addInt(0); // p_list
	msg.addInt(0); // keys

	this._socket.write(msg.getData());
}

Connection.prototype._queueEvent = function(callback){
    var self = this;

    if (self._isClosed) {
        if (callback)
            callback(new Error('Connection is closed.'));
        return;
    }

    self._queue.push(callback);
    self._socket.write(self._msg.getData());
};

Connection.prototype.connect = function (options, callback) {
	var pluginName = options.manager ? AUTH_PLUGIN_LEGACY : options.pluginName || AUTH_PLUGIN_LIST[0]; // TODO Srp for service
    var msg = this._msg;
    var blr = this._blr;

    this._pending.push('connect');

    msg.pos = 0;
    blr.pos = 0;

	blr.addString(CNCT_login, options.user, DEFAULT_ENCODING);
	blr.addString(CNCT_plugin_name, pluginName, DEFAULT_ENCODING);
	blr.addString(CNCT_plugin_list, AUTH_PLUGIN_LIST.join(','), DEFAULT_ENCODING);

	var specificData = '';
	if (AUTH_PLUGIN_SRP_LIST.indexOf(pluginName) > -1) {
		this.clientKeys = srp.clientSeed();
		specificData = this.clientKeys.public.toString(16);
		blr.addMultiblockPart(CNCT_specific_data, specificData, DEFAULT_ENCODING);
	} else if (pluginName === AUTH_PLUGIN_LEGACY) {
		specificData = crypt.crypt(options.password, LEGACY_AUTH_SALT).substring(2);
		blr.addMultiblockPart(CNCT_specific_data, specificData, DEFAULT_ENCODING);
	} else {
		doError(new Error('Invalide auth plugin \'' + pluginName + '\''), callback);
		return;
	}
	blr.addBytes([CNCT_client_crypt, 4, WIRE_CRYPT_DISABLE, 0, 0, 0]); // WireCrypt = Disabled
	blr.addString(CNCT_user, os.userInfo().username || 'Unknown', DEFAULT_ENCODING);
    blr.addString(CNCT_host, os.hostname(), DEFAULT_ENCODING);
    blr.addBytes([CNCT_user_verification, 0]);

	msg.addInt(op_connect);
	msg.addInt(op_attach);
	msg.addInt(CONNECT_VERSION3);
	msg.addInt(ARCHITECTURE_GENERIC);
	msg.addString(options.database || options.filename, DEFAULT_ENCODING);
	msg.addInt(SUPPORTED_PROTOCOL.length);  // Count of Protocol version understood count.
    msg.addBlr(this._blr);

    for (var protocol of SUPPORTED_PROTOCOL) {
    	msg.addInt(protocol[0]); // Version
    	msg.addInt(protocol[1]); // Architecture
    	msg.addInt(protocol[2]); // Min type
    	msg.addInt(protocol[3]); // Max type
    	msg.addInt(protocol[4]); // Preference weight
	}

	var self = this;
	function cb(err, ret) {
		if (err) {
			doError(err, callback);
			return;
		}

		self.accept = ret;
		if (callback)
			callback(undefined, ret);
	}

    this._queueEvent(cb);
};

Connection.prototype.attach = function (options, callback, db) {
    this._lowercase_keys = options.lowercase_keys || DEFAULT_LOWERCASE_KEYS;

    var database = options.database || options.filename;
    if (database == null || database.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var user = options.user || DEFAULT_USER;
    var password = options.password || DEFAULT_PASSWORD;
    var role = options.role;
    var self = this;
    var msg = this._msg;
    var blr = this._blr;
    msg.pos = 0;
    blr.pos = 0;

    blr.addByte(isc_dpb_version1);
    blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING);
    blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING);
	if (options.password && !this.accept.authData) {
		if (this.accept.protocolVersion < PROTOCOL_VERSION13) {
			if (this.accept.protocolVersion === PROTOCOL_VERSION10) {
				blr.addString(isc_dpb_password, password, DEFAULT_ENCODING);
			} else {
				blr.addString(isc_dpb_password_enc, crypt.crypt(password, LEGACY_AUTH_SALT).substring(2), DEFAULT_ENCODING);
			}
		}
	}

    if (role)
        blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING);

	blr.addBytes([isc_dpb_process_id, 4]);
	blr.addInt32(process.pid);

	let processName  = process.title || "";
	blr.addString(isc_dpb_process_name, processName.length > 255 ? processName.substring(processName.length - 255,  processName.length) : processName, DEFAULT_ENCODING);

	if (this.accept.authData) {
		blr.addString(isc_dpb_specific_auth_data, this.accept.authData, DEFAULT_ENCODING);
	}

    msg.addInt(op_attach);
    msg.addInt(0);  // Database Object ID
    msg.addString(database, DEFAULT_ENCODING);
    msg.addBlr(this._blr);

    function cb(err, ret) {
        if (err) {
            doError(err, callback);
            return;
        }

        self.dbhandle = ret.handle;
        if (callback)
            callback(undefined, ret);
    }

    // For reconnect
    if (db) {
        db.connection = this;
        cb.response = db;
    } else {
        cb.response = new Database(this);
        cb.response.removeAllListeners('error');
        cb.response.on('error', noop);
    }

    this._queueEvent(cb);
};

Connection.prototype.detach = function (callback) {

    var self = this;

    if (self._isClosed)
        return;

    self._isUsed = false;
    self._isDetach = true;

    var msg = self._msg;

    msg.pos = 0;
    msg.addInt(op_detach);
    msg.addInt(0); // Database Object ID

    self._queueEvent(function(err, ret) {
        clearTimeout(self._retry_connection_id);
        delete(self.dbhandle);
        if (callback)
            callback(err, ret);
    });
};

Connection.prototype.createDatabase = function (options, callback) {
    var database = options.database || options.filename;
    if (database == null || database.length === 0) {
        doError(new Error('No database specified'), callback);
        return;
    }

    var user = options.user || DEFAULT_USER;
    var password = options.password || DEFAULT_PASSWORD;
    var pageSize = options.pageSize || DEFAULT_PAGE_SIZE;
    var role = options.role;
    var blr = this._blr;

    blr.pos = 0;
    blr.addByte(isc_dpb_version1);
    blr.addString(isc_dpb_set_db_charset, 'UTF8', DEFAULT_ENCODING);
    blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING);
    blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING);
	if (this.accept.protocolVersion < PROTOCOL_VERSION13) {
		if (this.accept.protocolVersion === PROTOCOL_VERSION10) {
			blr.addString(isc_dpb_password, password, DEFAULT_ENCODING);
		} else {
			blr.addString(isc_dpb_password_enc, crypt.crypt(password, LEGACY_AUTH_SALT).substring(2), DEFAULT_ENCODING);
		}
	}
    if (role)
        blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING);

	blr.addBytes([isc_dpb_process_id, 4]);
	blr.addInt32(process.pid);

	let processName  = process.title || "";
	blr.addString(isc_dpb_process_name, processName.length > 255 ? processName.substring(processName.length - 255,  processName.length) : processName, DEFAULT_ENCODING);

	if (this.accept.authData) {
		blr.addString(isc_dpb_specific_auth_data, this.accept.authData, DEFAULT_ENCODING);
	}

    blr.addNumeric(isc_dpb_sql_dialect, 3);
    blr.addNumeric(isc_dpb_force_write, 1);
    blr.addNumeric(isc_dpb_overwrite, 1);
    blr.addNumeric(isc_dpb_page_size, pageSize);

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_create);  // op_create
    msg.addInt(0);          // Database Object ID
    msg.addString(database, DEFAULT_ENCODING);
    msg.addBlr(blr);

    var self = this;

    function cb(err, ret) {

        if (ret)
            self.dbhandle = ret.handle;

        setImmediate(function() {
            if (self.db)
                self.db.emit('attach', ret);
        });

        if (callback)
            callback(err, ret);
    }

    cb.response = new Database(this);
    this._queueEvent(cb);
};

Connection.prototype.dropDatabase = function (callback) {
	var msg = this._msg;
	msg.pos = 0;

	msg.addInt(op_drop_database);
	msg.addInt(this.dbhandle);

	var self = this;
	this._queueEvent(function(err) {
		self.detach(function() {
			self.disconnect();

			if (callback)
				callback(err);
		});
	});
};

Connection.prototype.throwClosed = function(callback) {
    var err = new Error('Connection is closed.');
    this.db.emit('error', err);
    if (callback)
        callback(err);
    return this;
};

Connection.prototype.startTransaction = function(isolation, callback) {

    if (typeof(isolation) === 'function') {
        var tmp = isolation;
        isolation = callback;
        callback = tmp;
    }

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('startTransaction');

    var blr = this._blr;
    var msg = this._msg;

    blr.pos = 0;
    msg.pos = 0;

    blr.addBytes(isolation || ISOLATION_REPEATABLE_READ);
    msg.addInt(op_transaction);
    msg.addInt(this.dbhandle);
    msg.addBlr(blr);
    callback.response = new Transaction(this);

    this.db.emit('transaction', isolation);
    this._queueEvent(callback);
};

Connection.prototype.commit = function (transaction, callback) {

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('commit');

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_commit);
    msg.addInt(transaction.handle);
    this.db.emit('commit');
    this._queueEvent(callback);
};

Connection.prototype.rollback = function (transaction, callback) {

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('rollback');

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_rollback);
    msg.addInt(transaction.handle);
    this.db.emit('rollback');
    this._queueEvent(callback);
};

Connection.prototype.commitRetaining = function (transaction, callback) {

    if (this._isClosed)
        throw new Error('Connection is closed.');

    // for auto detach
    this._pending.push('commitRetaining');

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_commit_retaining);
    msg.addInt(transaction.handle);
    this._queueEvent(callback);
};

Connection.prototype.rollbackRetaining = function (transaction, callback) {

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('rollbackRetaining');

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_rollback_retaining);
    msg.addInt(transaction.handle);
    this._queueEvent(callback);
};

Connection.prototype.allocateStatement = function (callback) {

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('allocateStatement');

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_allocate_statement);
    msg.addInt(this.dbhandle);
    callback.response = new Statement(this);
    this._queueEvent(callback);
};

Connection.prototype.dropStatement = function (statement, callback) {

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('dropStatement');

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_free_statement);
    msg.addInt(statement.handle);
    msg.addInt(DSQL_drop);
    this._queueEvent(callback);
};

Connection.prototype.closeStatement = function (statement, callback) {

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('closeStatement');

    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_free_statement);
    msg.addInt(statement.handle);
    msg.addInt(DSQL_close);

    this._queueEvent(callback);
};

Connection.prototype.allocateAndPrepareStatement = function (transaction, query, plan, callback) {
	var self = this;
	var mainCallback = function(err, ret) {
		if (!err) {
			mainCallback.response.handle = ret.handle;
			describe(ret.buffer, mainCallback.response);
			mainCallback.response.query = query;
			self.db.emit('query', query);
			ret = mainCallback.response;
			self._setcachedquery(query, ret);
		}

		if (callback)
			callback(err, ret);
	};

	// for auto detach
	this._pending.push('allocateAndPrepareStatement');

	var msg = this._msg;
	var blr = this._blr;

	msg.pos = 0;
	blr.pos = 0;

	msg.addInt(op_allocate_statement);
	msg.addInt(this.dbhandle);
	mainCallback.lazy_count = 1;

	blr.addBytes(DESCRIBE);
	if (plan)
		blr.addByte(isc_info_sql_get_plan);

	msg.addInt(op_prepare_statement);
	msg.addInt(transaction.handle);
	msg.addInt(0xFFFF);
	msg.addInt(3); // dialect = 3
	msg.addString(query, DEFAULT_ENCODING);
	msg.addBlr(blr);
	msg.addInt(65535); // buffer_length
	mainCallback.lazy_count += 1;

	mainCallback.response = new Statement(this);
	this._queueEvent(mainCallback);
};

Connection.prototype.prepare = function (transaction, query, plan, callback) {
	var self = this;

	if (this.accept.protocolMinimumType === ptype_lazy_send) { // V11 Statement or higher
		self.allocateAndPrepareStatement(transaction, query, plan, callback);
	} else { // V10 Statement
		self.allocateStatement(function (err, statement) {
			if (err) {
				doError(err, callback);
				return;
			}

			self.prepareStatement(transaction, statement, query, plan, callback);
		});
	}
};

function describe(buff, statement) {
    var br = new BlrReader(buff);
    var parameters = null;
    var type, param;

    while (br.pos < br.buffer.length) {
        switch (br.readByteCode()) {
            case isc_info_sql_stmt_type:
                statement.type = br.readInt();
                break;
            case isc_info_sql_get_plan:
                statement.plan = br.readString(DEFAULT_ENCODING);
                break;
            case isc_info_sql_select:
                statement.output = parameters = [];
                break;
            case isc_info_sql_bind:
                statement.input = parameters = [];
                break;
            case isc_info_sql_num_variables:
                br.readInt(); // eat int
                break;
            case isc_info_sql_describe_vars:
                if (!parameters) {return}
                br.readInt(); // eat int ?
                var finishDescribe = false;
                param = null;
                while (!finishDescribe){
                    switch (br.readByteCode()) {
                        case isc_info_sql_describe_end:
                            break;
                        case isc_info_sql_sqlda_seq:
                            var num = br.readInt();
                            break;
                        case isc_info_sql_type:
                            type = br.readInt();
                            switch (type&~1) {
                                case SQL_VARYING:   param = new SQLVarString(); break;
                                case SQL_NULL:      param = new SQLVarNull(); break;
                                case SQL_TEXT:      param = new SQLVarText(); break;
                                case SQL_DOUBLE:    param = new SQLVarDouble(); break;
                                case SQL_FLOAT:
                                case SQL_D_FLOAT:   param = new SQLVarFloat(); break;
                                case SQL_TYPE_DATE: param = new SQLVarDate(); break;
                                case SQL_TYPE_TIME: param = new SQLVarTime(); break;
                                case SQL_TIMESTAMP: param = new SQLVarTimeStamp(); break;
                                case SQL_BLOB:      param = new SQLVarBlob(); break;
                                case SQL_ARRAY:     param = new SQLVarArray(); break;
                                case SQL_QUAD:      param = new SQLVarQuad(); break;
                                case SQL_LONG:      param = new SQLVarInt(); break;
                                case SQL_SHORT:     param = new SQLVarShort(); break;
                                case SQL_INT64:     param = new SQLVarInt64(); break;
                                case SQL_BOOLEAN:   param = new SQLVarBoolean(); break;
                                default:
                                    throw new Error('Unexpected');
                            }
                            parameters[num-1] = param;
                            param.type = type;
                            param.nullable = Boolean(param.type & 1);
                            param.type &= ~1;
                            break;
                        case isc_info_sql_sub_type:
                            param.subType = br.readInt();
                            break;
                        case isc_info_sql_scale:
                            param.scale = br.readInt();
                            break;
                        case isc_info_sql_length:
                            param.length = br.readInt();
                            break;
                        case isc_info_sql_null_ind:
                            param.nullable = Boolean(br.readInt());
                            break;
                        case isc_info_sql_field:
                            param.field = br.readString(DEFAULT_ENCODING);
                            break;
                        case isc_info_sql_relation:
                            param.relation = br.readString(DEFAULT_ENCODING);
                            break;
                        case isc_info_sql_owner:
                            param.owner = br.readString(DEFAULT_ENCODING);
                            break;
                        case isc_info_sql_alias:
                            param.alias = br.readString(DEFAULT_ENCODING);
                            break;
                        case isc_info_sql_relation_alias:
                            param.relationAlias = br.readString(DEFAULT_ENCODING);
                            break;
                        case isc_info_truncated:
                            throw new Error('Truncated');
                        default:
                            finishDescribe = true;
                            br.pos--;
                    }
                }
        }
    }
}

Connection.prototype.prepareStatement = function (transaction, statement, query, plan, callback) {

    if (this._isClosed)
        return this.throwClosed(callback);

    var msg = this._msg;
    var blr = this._blr;

    msg.pos = 0;
    blr.pos = 0;

    if (plan instanceof Function) {
        callback = plan;
        plan = false;
    }

    blr.addBytes(DESCRIBE);

    if (plan)
        blr.addByte(isc_info_sql_get_plan);

    msg.addInt(op_prepare_statement);
    msg.addInt(transaction.handle);
    msg.addInt(statement.handle);
    msg.addInt(3); // dialect = 3
    msg.addString(query, DEFAULT_ENCODING);
    msg.addBlr(blr);
    msg.addInt(65535); // buffer_length

    var self = this;
    this._queueEvent(function(err, ret) {

        if (!err) {
            describe(ret.buffer, statement);
            statement.query = query;
            self.db.emit('query', query);
            ret = statement;
            self._setcachedquery(query, ret);
        }

        if (callback)
            callback(err, ret);
    });

};

function CalcBlr(blr, xsqlda) {
    blr.addBytes([blr_version5, blr_begin, blr_message, 0]); // + message number
    blr.addWord(xsqlda.length * 2);

    for (var i = 0, length = xsqlda.length; i < length; i++) {
        xsqlda[i].calcBlr(blr);
        blr.addByte(blr_short);
        blr.addByte(0);
    }

    blr.addByte(blr_end);
    blr.addByte(blr_eoc);
}

Connection.prototype.executeStatement = function(transaction, statement, params, callback, custom) {

    if (this._isClosed)
        return this.throwClosed(callback);

    // for auto detach
    this._pending.push('executeStatement');

    if (params instanceof Function) {
        callback = params;
        params = undefined;
    }

    var self = this;

	var op = op_execute;
	if (
		this.accept.protocolVersion >= PROTOCOL_VERSION13 &&
		statement.type === isc_info_sql_stmt_exec_procedure &&
		statement.output.length
	) {
		op = op_execute2;
	}

    function PrepareParams(params, input, callback) {

        var value, meta;
        var ret = new Array(params.length);
        var wait = params.length;

        function done() {
            wait--;
            if (wait === 0)
                callback(ret);
        }

        function putBlobData(index, value, callback) {

            self.createBlob2(transaction, function(err, blob) {

                var b;
                var isStream = value.readable;

                if (Buffer.isBuffer(value))
                    b = value;
                else if (typeof(value) === 'string')
                    b = Buffer.from(value, DEFAULT_ENCODING);
                else if (!isStream)
                    b = Buffer.from(JSON.stringify(value), DEFAULT_ENCODING);

                if (Buffer.isBuffer(b)) {
                    bufferReader(b, 1024, function(b, next) {
                        self.batchSegments(blob, b, next);
                    }, function() {
                        ret[index] = new SQLParamQuad(blob.oid);
                        self.closeBlob(blob, callback);
                    });
                    return;
                }

                var isReading = false;
                var isEnd = false;

                value.on('data', function(chunk) {
                    value.pause();
                    isReading = true;
                    bufferReader(chunk, 1024, function(b, next) {
                        self.batchSegments(blob, b, next);
                    }, function() {
                        isReading = false;

                        if (isEnd) {
                            ret[index] = new SQLParamQuad(blob.oid);
                            self.closeBlob(blob, callback);
                        } else
                            value.resume();
                    });
                });

                value.on('end', function() {
                    isEnd = true;
                    if (isReading)
                        return;
                    ret[index] = new SQLParamQuad(blob.oid);
                    self.closeBlob(blob, callback);
                });
            });
        }

        for (var i = 0, length = params.length; i < length; i++) {
            value = params[i];
            meta = input[i];

            if (value === null || value === undefined) {
                switch (meta.type) {
                    case SQL_VARYING:
                    case SQL_NULL:
                    case SQL_TEXT:
                        ret[i] = new SQLParamString(null);
                        break;
                    case SQL_DOUBLE:
                    case SQL_FLOAT:
                    case SQL_D_FLOAT:
                        ret[i] = new SQLParamDouble(null);
                        break;
                    case SQL_TYPE_DATE:
                    case SQL_TYPE_TIME:
                    case SQL_TIMESTAMP:
                        ret[i] = new SQLParamDate(null);
                        break;
                    case SQL_BLOB:
                    case SQL_ARRAY:
                    case SQL_QUAD:
                        ret[i] = new SQLParamQuad(null);
                        break;
                    case SQL_LONG:
                    case SQL_SHORT:
                    case SQL_INT64:
                    case SQL_BOOLEAN:
                        ret[i] = new SQLParamInt(null);
                        break;
                    default:
                        ret[i] = null;
                }
                done();
            } else {
                switch (meta.type) {
                    case SQL_BLOB:
                        putBlobData(i, value, done);
                        break;

                    case SQL_TIMESTAMP:
                    case SQL_TYPE_DATE:
                    case SQL_TYPE_TIME:

                        if (value instanceof Date)
                            ret[i] = new SQLParamDate(value);
                        else if (typeof(value) === 'string')
                            ret[i] = new SQLParamDate(value.parseDate());
                        else
                            ret[i] = new SQLParamDate(new Date(value));

                        done();
                        break;

                    default:
                        switch (typeof value) {
                            case 'number':
                                if (value % 1 === 0) {
                                    if (value >= MIN_INT && value <= MAX_INT)
                                        ret[i] = new SQLParamInt(value);
                                    else
                                        ret[i] = new SQLParamInt64(value);
                                } else
                                    ret[i] = new SQLParamDouble(value);
                                break;
                            case 'string':
                                ret[i] = new SQLParamString(value);
                                break;
                            case 'boolean':
                                ret[i] = new SQLParamBool(value);
                                break;
                            default:
                                //throw new Error('Unexpected parametter: ' + JSON.stringify(params) + ' - ' + JSON.stringify(input));
                                ret[i] = new SQLParamString(value.toString());
                                break;
                        }
                        done();
                }
            }
        }
    }

    var input = statement.input;

    if (input.length) {

        if (!(params instanceof Array)) {
            if (params !== undefined)
                params = [params];
            else
                params = [];
        }

        if (params.length !== input.length) {
            self._pending.pop();
            callback(new Error('Expected parameters: (params=' + params.length + ' vs. expected=' + input.length + ') - ' + statement.query));
            return;
        }

        PrepareParams(params, input, function(prms) {
			self.sendExecute(op, statement, transaction, callback, prms);
        });

        return;
    }

    this.sendExecute(op, statement, transaction, callback);
};

Connection.prototype.sendExecute = function (op, statement, transaction, callback, parameters) {
	var msg = this._msg;
	var blr = this._blr;
	msg.pos = 0;
	blr.pos = 0;

	msg.addInt(op);
	msg.addInt(statement.handle);
	msg.addInt(transaction.handle);

	if (parameters && parameters.length) {
		CalcBlr(blr, parameters);
		msg.addBlr(blr);    // params blr
		msg.addInt(0); // message number
		msg.addInt(1); // param count

		if (this.accept.protocolVersion >= PROTOCOL_VERSION13) {
			// start with null indicator bitmap
			var nullBits = new BitSet();

			for (var i = 0; i < parameters.length; i++) {
				nullBits.set(i, (parameters[i].value === null) & 1);
			}

			var nullBuffer = nullBits.toBuffer();
			var requireBytes = Math.floor((parameters.length + 7) / 8);
			var remainingBytes = requireBytes - nullBuffer.length;

			if (nullBuffer.length) {
				msg.addBuffer(nullBuffer);
			}
			if (remainingBytes > 0) {
				msg.addBuffer(Buffer.alloc(remainingBytes));
			}
			msg.addAlignment(requireBytes);

			for(var i = 0; i < parameters.length; i++) {
				if (parameters[i].value !== null) {
					parameters[i].encode(msg);
				}
			}
		} else {
			for(var i = 0; i < parameters.length; i++) {
				parameters[i].encode(msg);
                if (parameters[i].value !== null) {
                    msg.addInt(0);
                }
            }
		}
	} else {
		msg.addBlr(blr);    // empty
		msg.addInt(0); // message number
		msg.addInt(0); // param count
	}

	if (op === op_execute2) {
		var outputBlr = new BlrWriter(32);

		if (statement.output && statement.output.length) {
			CalcBlr(outputBlr, statement.output);
			msg.addBlr(outputBlr);
		} else {
			msg.addBlr(outputBlr); // empty
		}
		msg.addInt(0); // out_message_number = out_message_type
	}

	callback.statement = statement;
	this._queueEvent(callback);
}

function fetch_blob_async(statement, id, name) {

    if (!id)
        return null;

    return function(callback) {
        // callback(err, buffer, name);
        statement.connection.startTransaction(ISOLATION_READ_UNCOMMITTED, function(err, transaction) {

            if (err) {
                callback(err);
                return;
            }

            statement.connection._pending.push('openBlob');
            statement.connection.openBlob(id, transaction, function(err, blob) {

                var e = new Events.EventEmitter();

                e.pipe = function(stream) {
                    e.on('data', function(chunk) {
                        stream.write(chunk);
                    });
                    e.on('end', function() {
                        stream.end();
                    });
                };

                if (err) {
                    callback(err, name, e);
                    return;
                }

                function read() {
                    statement.connection.getSegment(blob, function(err, ret) {

                        if (err) {
                            transaction.rollback(function() {
                                e.emit('error', err);
                            });
                            return;
                        }

                        if (ret.buffer) {
                            var blr = new BlrReader(ret.buffer);
                            var data = blr.readSegment();

                            e.emit('data', data);
                        }

                        if (ret.handle !== 2) {
                            read();
                            return;
                        }

                        statement.connection.closeBlob(blob);
                        transaction.commit(function(err) {
                            if (err) {
                                e.emit('error', err);
                            } else {
                                e.emit('end');
                            }
                            e = null;
                        });

                    });
                }

                callback(err, name, e);
                read();

            });
        });
    };
}

Connection.prototype.fetch = function(statement, transaction, count, callback) {

    var msg = this._msg;
    var blr = this._blr;

    msg.pos = 0;
    blr.pos = 0;

    if (count instanceof Function) {
        callback = count;
        count = DEFAULT_FETCHSIZE;
    }

    msg.addInt(op_fetch);
    msg.addInt(statement.handle);
    CalcBlr(blr, statement.output);
    msg.addBlr(blr);
    msg.addInt(0); // message number
    msg.addInt(count || DEFAULT_FETCHSIZE); // fetch count

    callback.statement = statement;
    this._queueEvent(callback);
};

Connection.prototype.fetchAll = function (statement, transaction, callback) {
	var self = this, data = [];
	var loop = function (err, ret) {
		if (err) {
			return callback(err);
		} else if (ret && ret.data && ret.data.length) {
			for (var i = 0; i < ret.data.length; i++) {
				var pos = data.push(ret.data[i]);
				if (statement.custom && statement.custom.asStream && statement.custom.on) {
					statement.custom.on(ret.data[i], pos - 1);
				}
				if (i === ret.data.length - 1) {
					if (ret.fetched) {
						return callback(undefined, data);
					} else {
						self.fetch(statement, transaction, DEFAULT_FETCHSIZE, loop);
					}
				}
			}
		} else if (ret.fetched) {
			callback(undefined, data);
		} else {
			self.fetch(statement, transaction, DEFAULT_FETCHSIZE, loop);
		}
	}

	this.fetch(statement, transaction, DEFAULT_FETCHSIZE, loop);
};


Connection.prototype.openBlob = function(blob, transaction, callback) {
    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_open_blob);
    msg.addInt(transaction.handle);
    msg.addQuad(blob);
    this._queueEvent(callback);
};

Connection.prototype.closeBlob = function(blob, callback) {
    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_close_blob);
    msg.addInt(blob.handle);
    this._queueEvent(callback);
};

Connection.prototype.getSegment = function(blob, callback) {
    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_get_segment);
    msg.addInt(blob.handle);
    msg.addInt(1024); // buffer length
    msg.addInt(0); // ???
    this._queueEvent(callback);
};

Connection.prototype.createBlob2 = function (transaction, callback) {
    var msg = this._msg;
    msg.pos = 0;
    msg.addInt(op_create_blob2);
    msg.addInt(0);
    msg.addInt(transaction.handle);
    msg.addInt(0);
    msg.addInt(0);
    this._queueEvent(callback);
};

Connection.prototype.batchSegments = function(blob, buffer, callback){
    var msg = this._msg;
    var blr = this._blr;
    msg.pos = 0;
    blr.pos = 0;
    msg.addInt(op_batch_segments);
    msg.addInt(blob.handle);
    msg.addInt(buffer.length + 2);
    blr.addBuffer(buffer);
    msg.addBlr(blr);
    this._queueEvent(callback);
};

Connection.prototype.svcattach = function (options, callback, svc) {
    this._lowercase_keys = options.lowercase_keys || DEFAULT_LOWERCASE_KEYS;
    var database = options.database || options.filename;
    var user = options.user || DEFAULT_USER;
    var password = options.password || DEFAULT_PASSWORD;
    var role = options.role;
    var msg = this._msg;
    var blr = this._blr;
    msg.pos = 0;
    blr.pos = 0;

    blr.addBytes([isc_dpb_version2, isc_dpb_version2]);
    blr.addString(isc_dpb_lc_ctype, 'UTF8', DEFAULT_ENCODING);
    blr.addString(isc_dpb_user_name, user, DEFAULT_ENCODING);
    blr.addString(isc_dpb_password, password, DEFAULT_ENCODING);
    blr.addByte(isc_dpb_dummy_packet_interval);
    blr.addByte(4);
    blr.addBytes([120, 10, 0, 0]); // FROM DOT NET PROVIDER
    if (role)
        blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING);

    msg.addInt(op_service_attach);
    msg.addInt(0);
    msg.addString(DEFAULT_SVC_NAME, DEFAULT_ENCODING); // only local for moment
    msg.addBlr(this._blr);

    var self = this;

    function cb(err, ret) {

        if (err) {
            doError(err, callback);
            return;
        }

        self.svchandle = ret.handle;
        if (callback)
            callback(undefined, ret);
    }

    // For reconnect
    if (svc) {
        svc.connection = this;
        cb.response = svc;
    } else {
        cb.response = new ServiceManager(this);
        cb.response.removeAllListeners('error');
        cb.response.on('error', noop);
    }

    this._queueEvent(cb);
}

Connection.prototype.svcstart = function (spbaction, callback) {
    var msg = this._msg;
    var blr = this._blr;
    msg.pos = 0;
    msg.addInt(op_service_start);
    msg.addInt(this.svchandle);
    msg.addInt(0)
    msg.addBlr(spbaction);
    this._queueEvent(callback);
}

Connection.prototype.svcquery = function (spbquery, resultbuffersize, timeout,callback) {
    if (resultbuffersize > MAX_BUFFER_SIZE) {
        doError(new Error('Buffer is too big'), callback);
        return;
    }

    var msg = this._msg;
    var blr = this._blr;
    msg.pos = 0;
    blr.pos = 0;
    blr.addByte(isc_spb_current_version);
    //blr.addByteInt32(isc_info_svc_timeout, timeout);
    msg.addInt(op_service_info);
    msg.addInt(this.svchandle);
    msg.addInt(0)
    msg.addBlr(blr);
    blr.pos = 0
    blr.addBytes(spbquery);
    msg.addBlr(blr);
    msg.addInt(resultbuffersize);
    this._queueEvent(callback);
}

Connection.prototype.svcdetach = function (callback) {
    var self = this;

    if (self._isClosed)
        return;

    self._isUsed = false;
    self._isDetach = true;

    var msg = self._msg;

    msg.pos = 0;
    msg.addInt(op_service_detach);
    msg.addInt(this.svchandle); // Database Object ID

    self._queueEvent(function (err, ret) {
        delete (self.svchandle);
        if (callback)
            callback(err, ret);
    });
}

function bufferReader(buffer, max, writer, cb, beg, end) {

    if (!beg)
        beg = 0;

    if (!end)
        end = max;

    if (end >= buffer.length)
        end = undefined;

    var b = buffer.slice(beg, end);

    writer(b, function() {

        if (end === undefined) {
            cb();
            return;
        }

        bufferReader(buffer, max, writer, cb, beg + max, end + max);
    });
}
