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,
  messages = require('./messages.js')

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,
  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,

  // 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
  DSQL_close = 1,
  DSQL_drop = 2,
  DSQL_unprepare = 4 // >= 2.5

const
  ptype_batch_send = 3

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

/*************************************/
/* 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'

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
 * @return {String}
 */
exports.escape = function (value) {
  if (value === null || value === undefined) { return 'NULL' }

  switch (typeof (value)) {
    case 'boolean':
      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().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') + "'" }

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

const
  DEFAULT_ENCODING = 'latin1',
  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) {
  var ret = data.readText(this.length, DEFAULT_ENCODING)
  if (!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) {
  var ret = data.readString(DEFAULT_ENCODING)
  if (!data.readInt()) {
    return removeDiacritics(ret)
  }
  return null
}

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

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

function SQLVarQuad () {}

SQLVarQuad.prototype.decode = function (data) {
  var ret = data.readQuad()
  if (!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) {
  var ret = data.readInt()
  if (!data.readInt()) {
    if (this.scale) {
      ret = ret / ScaleDivisor[Math.abs(this.scale)]
    }
    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) {
  var ret = data.readInt64()
  if (!data.readInt()) {
    if (this.scale) {
      ret = ret / ScaleDivisor[Math.abs(this.scale)]
    }
    return ret
  }
  return null
}

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

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

function SQLVarFloat () {}

SQLVarFloat.prototype.decode = function (data) {
  var ret = data.readFloat()
  if (!data.readInt()) {
    return ret
  }
  return null
}

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

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

function SQLVarDouble () {}

SQLVarDouble.prototype.decode = function (data) {
  var ret = data.readDouble()
  if (!data.readInt()) {
    return ret
  }
  return null
}

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

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

function SQLVarDate () {}

SQLVarDate.prototype.decode = function (data) {
  var ret = data.readInt()
  if (!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) {
  var ret = data.readUInt()
  if (!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) {
  var date = data.readInt()
  var time = data.readUInt()
  if (!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) {
  var ret = data.readInt()
  if (!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)
    data.addInt(0)
  } 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)
    data.addInt(0)
  } 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)
    data.addInt(0)
  } 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)
    data.addInt(0)
  } 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)
    data.addInt(0)
  } 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)
    data.addInt(0)
  } 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)
    data.addInt(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 (obj instanceof Object && obj.status)
}

function doCallback (obj, callback) {
  if (!callback) { return }

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

  if (isError(obj)) {
    callback(new Error(obj.message))
    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.allocateStatement(function (err, statement) {
      if (err) {
        doError(err, callback)
        return
      }
      cnx.prepareStatement(self, statement, query, false, callback)
    })
  }
}

Transaction.prototype.execute = function (query, params, callback, custom) {
  if (params instanceof Function) {
    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) {
      if (err) {
        dropError(err)
        return
      }

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

            statement.release()

            if (callback) { callback(undefined, ret, statement.output, true) }
          })

          break

        case isc_info_sql_stmt_exec_procedure:
          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.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)
}

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) {
    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.') }

  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
}

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.database || options.filename, function (err) {
      if (err) {
        doError(err, callback)
      } else {
        if (manager) { cnx.svcattach(options, callback) } else { cnx.attach(options, callback) }
      }
    })
  }, options)
}

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) {
    cnx.connect(options.database || options.filename, 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.database || options.filename, 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 */: '',
  '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_nba)
  blr.addString2(isc_spb_dbname, dbpath, DEFAULT_ENCODING)
  blr.addString2(isc_spb_dbname, 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 || 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_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 // 0 Forced, 1 deny transaction, 2 deny attachment
  var shutdowndelay = options.shutdowndelay || 0
  var shutdownmode = options.shutdownmode || null // 0 normal 1 multi 2 single 3 full
  var shadow = options.activateshadow || false
  var forcewrite = options.forcewrite != null ? options.forcewrite : null
  var reservespace = options.reservespace != null ? options.reservespace : null
  var accessmode = options.accessmode != null ? options.accesmode : null // 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) {
    switch (shutdown) {
      case 0:
        blr.addByteInt32(isc_spb_prp_shutdown_db, shutdowndelay)
        break
      case 1:
        blr.addByteInt32(isc_spb_prp_deny_new_transactions, shutdowndelay)
        break
      case 2:
        blr.addByteInt32(isc_spb_prp_deny_new_attachments, shutdowndelay)
        break
    }
    if (shutdownmode != null) {
      switch (shutdownmode) {
        case 0:
          blr.addByteInt32(isc_spb_prp_shutdown_mode, isc_spb_prp_sm_normal)
          break
        case 1:
          blr.addByteInt32(isc_spb_prp_shutdown_mode, isc_spb_prp_sm_multi)
          break
        case 2:
          blr.addByteInt32(isc_spb_prp_shutdown_mode, isc_spb_prp_sm_single)
          break
        case 3:
          blr.addByteInt32(isc_spb_prp_shutdown_mode, isc_spb_prp_sm_full)
          break
      }
    }
  }
  if (forcewrite) blr.addBytes([isc_spb_prp_write_mode, isc_spb_prp_wm_sync])
  if (forcewrite != null && !forcewrite) blr.addBytes([isc_spb_prp_write_mode, isc_spb_prp_wm_async])
  if (accessmode) blr.addBytes([isc_spb_prp_access_mode, isc_spb_prp_am_readwrite])
  if (accessmode != null && !accessmode) blr.addBytes([isc_spb_prp_access_mode, isc_spb_prp_am_readonly])
  if (reservespace) blr.addBytes([isc_spb_prp_reserve_space, sc_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 | sc_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 }, callaback)
}

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)
}

ServiceManager.prototype.Shutdown = function (db, kind, delay, mode, callback) {
  // mode parameter is for server version >= 2.0
  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(options.optread, options.buffersize, 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(options.optread, options.buffersize, 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(options.optread, options.buffersize, 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)
  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)
  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)
  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 = []
  for (popts in opts) {
    if (infos[popts] || infos.length == 0) { 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)
  })
}

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
  var tracename = options.tracename || ''

  if (traceid == null) {
    doError(new Error('No traceid specified'), callback)
    return
  }
  if (tracename.length == 0) {
    doError(new Error('No tracename specified'), callback)
    return
  }
  blr.pos = 0
  blr.addByte(isc_action_svc_trace_suspend)
  blr.addString2(isc_spb_trc_name, tracename, DEFAULT_ENCODING)
  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
  var tracename = options.tracename || ''

  if (traceid == null) {
    doError(new Error('No traceid specified'), callback)
    return
  }
  if (tracename.length == 0) {
    doError(new Error('No tracename specified'), callback)
    return
  }
  blr.pos = 0
  blr.addByte(isc_action_svc_trace_resume)
  blr.addString2(isc_spb_trc_name, tracename, DEFAULT_ENCODING)
  blr.addByteInt32(isc_spb_trc_id, traceid)
  this.connection.svcstart(blr, function (err, data) {
    if (err) {
      doError(new Error('Invalid RESUMETRACE Action'), 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
  var tracename = options.tracename || ''

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

  if (tracename.length == 0) {
    doError(new Error('No tracename specified'), callback)
    return
  }
  blr.pos = 0
  blr.addByte(isc_action_svc_trace_stop)
  blr.addString2(isc_spb_trc_name, tracename, DEFAULT_ENCODING)
  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
  var optread = options.optread || 'byline'
  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, callback) {
  var pool = new Pool(max, options)
  options.isPool = true
  return pool
}

/***************************************
 *
 *   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 () {
  var self = this
  this.internaldb.forEach(function (db) {
    if (db.connection._pooled == false) { 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()
    }
  })
}

/***************************************
 *
 *   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._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._max_cached_query = options.maxCachedQuery || -1
  this._cache_query = options.cacheQuery ? {} : null
}

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) {
  if (this._cache_query) { return this._cache_query[query] }
  return null
}

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

  self._socket.on('close', function () {
    self._isClosed = true

    if (self._isDetach) { return }

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

    setImmediate(function () {
      self._socket = null
      self._msg = null
      self._blr = null

      var ctx = new Connection(host, port, function (err) {
        ctx.connect(self.options.filename, function (err) {
          if (err) {
            self.emit('error', err)
            return
          }

          ctx.attach(self.options, function (err) {
            if (err) {
              self.emit('error', err)
              return
            }

            ctx._queue = ctx._queue.concat(self._queue)
            ctx._pending = ctx._pending.concat(self._pending)
            self.db.emit('reconnect')
          }, self.db)
        })
      }, self.options, self.db)
    })
  })

  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 obj, cb, pos, xdr, buf

    if (!self._xdr) {
      xdr = new XdrReader(data)
    } else {
      xdr = self._xdr
      delete (self._xdr)
      buf = new Buffer(data.length + xdr.buffer.length)
      xdr.buffer.copy(buf)
      data.copy(buf, xdr.buffer.length)
      xdr.buffer = buf
    }

    while (xdr.pos < xdr.buffer.length) {
      pos = xdr.pos

      try {
        cb = self._queue[0]
        obj = decodeResponse(xdr, cb, self.db, self._lowercase_keys)
        if (obj && obj.error) {
          // packet is not complete
          xdr.buffer = xdr.buffer.slice(xdr.pos)
          xdr.pos = 0
          self._xdr = xdr
          return
        }
        // remove the op flag, needed for partial packet
        if (xdr.r) delete (xdr.r)
      } catch (err) {
        xdr.buffer = xdr.buffer = xdr.buffer.slice(pos)
        xdr.pos = 0
        self._xdr = xdr
        return
      }

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

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

    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, db, lowercase_keys) {
  do {
    var r = data.r || data.readInt()
  } while (r === op_dummy)

  var item, op

  switch (r) {
    case op_response:

      var response

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

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

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

      var num
      while (true) {
        op = data.readInt()
        switch (op) {
          case isc_arg_end:
            return 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:
            throw new Error('Unexpected: ' + op)
        }
      }
      break

    case op_fetch_response:
      var statement = callback.statement
      var output = statement.output
      var custom = statement.custom || {}
      statement.nbrowsfetched = statement.nbrowsfetched || 0

      if (data.fop) { // could be set when a packet is not complete
        op = data.readInt() // ??
        data.fop = false
      }
      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)) {
        for (length = output.length; data.fcolumn < length; data.fcolumn++) {
          item = output[data.fcolumn]
          try {
            var _xdrpos = data.pos
            var value = item.decode(data)
            if (custom.asObject) {
              if (item.type === SQL_BLOB) { value = fetch_blob_async(statement, value, data.fcols[data.fcolumn]) }
              data.frow[data.fcols[data.fcolumn]] = value
            } else {
              if (item.type === SQL_BLOB) { value = fetch_blob_async(statement, value, data.fcolumn) }
              data.frow[data.fcolumn] = value
            }
          } catch (e) {
            // uncomplete packet read
            data.pos = _xdrpos
            data.r = r
            return { error: new Error('Packet is not complete') }
          }
        }

        data.fcolumn = 0
        statement.connection.db.emit('row', data.frow, statement.nbrowsfetched, custom.asObject)

        if (!custom.asStream) { data.frows.push(data.frow) }
        if (custom.on) { custom.on(data.frow, statement.nbrowsfetched) }
        data.frow = custom.asObject ? {} : new Array(output.length)

        try {
          delete data.fstatus
          delete data.fcount
          var _xdrpos = data.pos
          op = data.readInt() // ??
          data.fstatus = data.readInt()
          data.fcount = data.readInt()
        } catch (e) {
          if (_xdrpos == data.pos) { data.fop = true }
          data.r = r
          return { error: new Error('Packet is not complete') }
        }
        statement.nbrowsfetched++
      }

      statement.connection.db.emit('result', data.frows)
      return { data: data.frows, fetched: Boolean(data.fstatus === 100) }

    case op_accept:
      if (data.readInt() !== PROTOCOL_VERSION10 || data.readInt() !== ARCHITECTURE_GENERIC || data.readInt() !== ptype_batch_send) { throw new Error('Invalid connect result') }
      return {}

    default:
      throw new Error('Unexpected:' + r)
  }
}

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 (database, callback) {
  var msg = this._msg
  var blr = this._blr

  msg.pos = 0
  blr.pos = 0

  msg.addInt(op_connect)
  msg.addInt(op_attach)
  msg.addInt(CONNECT_VERSION2)
  msg.addInt(ARCHITECTURE_GENERIC)
  msg.addString(database || '', DEFAULT_ENCODING)
  msg.addInt(1) // Protocol version understood count.

  blr.addString(1, process.env['USER'] || process.env['USERNAME'] || 'Unknown', DEFAULT_ENCODING)
  var hostname = os.hostname()
  blr.addString(4, hostname, DEFAULT_ENCODING)
  blr.addBytes([6, 0])
  msg.addBlr(this._blr)

  msg.addInt(PROTOCOL_VERSION10)
  msg.addInt(ARCHITECTURE_GENERIC)
  msg.addInt(2) // Min type
  msg.addInt(3) // Max type
  msg.addInt(2) // Preference weight

  this._queueEvent(callback)
}

Connection.prototype.attach = function (options, callback, db) {
  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 self = this
  var msg = this._msg
  var blr = this._blr
  msg.pos = 0
  blr.pos = 0

  blr.addByte(1)
  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)

  if (role) { blr.addString(isc_dpb_sql_role_name, role, DEFAULT_ENCODING) }

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

  var self = this

  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) {
    delete (self.dbhandle)
    if (callback) { callback(err, ret) }
  })
}

Connection.prototype.createDatabase = function (options, callback) {
  var database = options.database || options.filename
  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(1)
  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)
  blr.addString(isc_dpb_password, password, DEFAULT_ENCODING)

  if (role) { blr.addString(isc_dpb_sql_role_name, role, 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.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

  if (isolation instanceof Function) {
    callback = isolation
    isolation = null
  }

  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)
}

function describe (ret, statement) {
  var br = new BlrReader(ret.buffer)
  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, 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

  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 = new Buffer(value, DEFAULT_ENCODING) } else if (!isStream) { b = new Buffer(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 === undefined || 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) {
      var msg = self._msg
      var blr = self._blr
      msg.pos = 0
      blr.pos = 0
      CalcBlr(blr, prms)

      msg.addInt(op_execute)
      msg.addInt(statement.handle)
      msg.addInt(transaction.handle)
      msg.addBlr(blr)
      msg.addInt(0) // message number
      msg.addInt(1) // param count

      for (var i = 0, length = prms.length; i < length; i++) { prms[i].encode(msg) }

      self._queueEvent(callback)
    })

    return
  }

  var msg = this._msg
  var blr = this._blr
  msg.pos = 0
  blr.pos = 0

  msg.addInt(op_execute)
  msg.addInt(statement.handle)
  msg.addInt(transaction.handle)

  msg.addBlr(blr) // empty
  msg.addInt(0) // message number
  msg.addInt(0) // param count

  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

  if (!transaction) {
    callback.statement = statement
    this._queueEvent(callback)
    return
  }

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

Connection.prototype.fetchAll = function (statement, transaction, callback) {
  var self = this
  var data
  var loop = function (err, ret) {
    if (err) {
      callback(err)
      return
    }

    if (!data) {
      data = ret.data
    } else {
      for (var i = 0, length = ret.data.length; i < length; i++) { data.push(ret.data[i]) }
    }

    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 self = this
  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)
  })
}

var defaultDiacriticsRemovalMap = [
    {'base':'A', 'letters':'\u0041\u24B6\uFF21\u00C0\u00C1\u00C2\u1EA6\u1EA4\u1EAA\u1EA8\u00C3\u0100\u0102\u1EB0\u1EAE\u1EB4\u1EB2\u0226\u01E0\u00C4\u01DE\u1EA2\u00C5\u01FA\u01CD\u0200\u0202\u1EA0\u1EAC\u1EB6\u1E00\u0104\u023A\u2C6F'},
    {'base':'AA','letters':'\uA732'},
    {'base':'AE','letters':'\u00C6\u01FC\u01E2'},
    {'base':'AO','letters':'\uA734'},
    {'base':'AU','letters':'\uA736'},
    {'base':'AV','letters':'\uA738\uA73A'},
    {'base':'AY','letters':'\uA73C'},
    {'base':'B', 'letters':'\u0042\u24B7\uFF22\u1E02\u1E04\u1E06\u0243\u0182\u0181'},
    {'base':'C', 'letters':'\u0043\u24B8\uFF23\u0106\u0108\u010A\u010C\u00C7\u1E08\u0187\u023B\uA73E'},
    {'base':'D', 'letters':'\u0044\u24B9\uFF24\u1E0A\u010E\u1E0C\u1E10\u1E12\u1E0E\u0110\u018B\u018A\u0189\uA779\u00D0'},
    {'base':'DZ','letters':'\u01F1\u01C4'},
    {'base':'Dz','letters':'\u01F2\u01C5'},
    {'base':'E', 'letters':'\u0045\u24BA\uFF25\u00C8\u00C9\u00CA\u1EC0\u1EBE\u1EC4\u1EC2\u1EBC\u0112\u1E14\u1E16\u0114\u0116\u00CB\u1EBA\u011A\u0204\u0206\u1EB8\u1EC6\u0228\u1E1C\u0118\u1E18\u1E1A\u0190\u018E'},
    {'base':'F', 'letters':'\u0046\u24BB\uFF26\u1E1E\u0191\uA77B'},
    {'base':'G', 'letters':'\u0047\u24BC\uFF27\u01F4\u011C\u1E20\u011E\u0120\u01E6\u0122\u01E4\u0193\uA7A0\uA77D\uA77E'},
    {'base':'H', 'letters':'\u0048\u24BD\uFF28\u0124\u1E22\u1E26\u021E\u1E24\u1E28\u1E2A\u0126\u2C67\u2C75\uA78D'},
    {'base':'I', 'letters':'\u0049\u24BE\uFF29\u00CC\u00CD\u00CE\u0128\u012A\u012C\u0130\u00CF\u1E2E\u1EC8\u01CF\u0208\u020A\u1ECA\u012E\u1E2C\u0197'},
    {'base':'J', 'letters':'\u004A\u24BF\uFF2A\u0134\u0248'},
    {'base':'K', 'letters':'\u004B\u24C0\uFF2B\u1E30\u01E8\u1E32\u0136\u1E34\u0198\u2C69\uA740\uA742\uA744\uA7A2'},
    {'base':'L', 'letters':'\u004C\u24C1\uFF2C\u013F\u0139\u013D\u1E36\u1E38\u013B\u1E3C\u1E3A\u0141\u023D\u2C62\u2C60\uA748\uA746\uA780'},
    {'base':'LJ','letters':'\u01C7'},
    {'base':'Lj','letters':'\u01C8'},
    {'base':'M', 'letters':'\u004D\u24C2\uFF2D\u1E3E\u1E40\u1E42\u2C6E\u019C'},
    {'base':'N', 'letters':'\u004E\u24C3\uFF2E\u01F8\u0143\u00D1\u1E44\u0147\u1E46\u0145\u1E4A\u1E48\u0220\u019D\uA790\uA7A4'},
    {'base':'NJ','letters':'\u01CA'},
    {'base':'Nj','letters':'\u01CB'},
    {'base':'O', 'letters':'\u004F\u24C4\uFF2F\u00D2\u00D3\u00D4\u1ED2\u1ED0\u1ED6\u1ED4\u00D5\u1E4C\u022C\u1E4E\u014C\u1E50\u1E52\u014E\u022E\u0230\u00D6\u022A\u1ECE\u0150\u01D1\u020C\u020E\u01A0\u1EDC\u1EDA\u1EE0\u1EDE\u1EE2\u1ECC\u1ED8\u01EA\u01EC\u00D8\u01FE\u0186\u019F\uA74A\uA74C'},
    {'base':'OI','letters':'\u01A2'},
    {'base':'OO','letters':'\uA74E'},
    {'base':'OU','letters':'\u0222'},
    {'base':'OE','letters':'\u008C\u0152'},
    {'base':'oe','letters':'\u009C\u0153'},
    {'base':'P', 'letters':'\u0050\u24C5\uFF30\u1E54\u1E56\u01A4\u2C63\uA750\uA752\uA754'},
    {'base':'Q', 'letters':'\u0051\u24C6\uFF31\uA756\uA758\u024A'},
    {'base':'R', 'letters':'\u0052\u24C7\uFF32\u0154\u1E58\u0158\u0210\u0212\u1E5A\u1E5C\u0156\u1E5E\u024C\u2C64\uA75A\uA7A6\uA782'},
    {'base':'S', 'letters':'\u0053\u24C8\uFF33\u1E9E\u015A\u1E64\u015C\u1E60\u0160\u1E66\u1E62\u1E68\u0218\u015E\u2C7E\uA7A8\uA784'},
    {'base':'T', 'letters':'\u0054\u24C9\uFF34\u1E6A\u0164\u1E6C\u021A\u0162\u1E70\u1E6E\u0166\u01AC\u01AE\u023E\uA786'},
    {'base':'TZ','letters':'\uA728'},
    {'base':'U', 'letters':'\u0055\u24CA\uFF35\u00D9\u00DA\u00DB\u0168\u1E78\u016A\u1E7A\u016C\u00DC\u01DB\u01D7\u01D5\u01D9\u1EE6\u016E\u0170\u01D3\u0214\u0216\u01AF\u1EEA\u1EE8\u1EEE\u1EEC\u1EF0\u1EE4\u1E72\u0172\u1E76\u1E74\u0244'},
    {'base':'V', 'letters':'\u0056\u24CB\uFF36\u1E7C\u1E7E\u01B2\uA75E\u0245'},
    {'base':'VY','letters':'\uA760'},
    {'base':'W', 'letters':'\u0057\u24CC\uFF37\u1E80\u1E82\u0174\u1E86\u1E84\u1E88\u2C72'},
    {'base':'X', 'letters':'\u0058\u24CD\uFF38\u1E8A\u1E8C'},
    {'base':'Y', 'letters':'\u0059\u24CE\uFF39\u1EF2\u00DD\u0176\u1EF8\u0232\u1E8E\u0178\u1EF6\u1EF4\u01B3\u024E\u1EFE'},
    {'base':'Z', 'letters':'\u005A\u24CF\uFF3A\u0179\u1E90\u017B\u017D\u1E92\u1E94\u01B5\u0224\u2C7F\u2C6B\uA762'},
    {'base':'a', 'letters':'\u0061\u24D0\uFF41\u1E9A\u00E0\u00E1\u00E2\u1EA7\u1EA5\u1EAB\u1EA9\u00E3\u0101\u0103\u1EB1\u1EAF\u1EB5\u1EB3\u0227\u01E1\u00E4\u01DF\u1EA3\u00E5\u01FB\u01CE\u0201\u0203\u1EA1\u1EAD\u1EB7\u1E01\u0105\u2C65\u0250'},
    {'base':'aa','letters':'\uA733'},
    {'base':'ae','letters':'\u00E6\u01FD\u01E3'},
    {'base':'ao','letters':'\uA735'},
    {'base':'au','letters':'\uA737'},
    {'base':'av','letters':'\uA739\uA73B'},
    {'base':'ay','letters':'\uA73D'},
    {'base':'b', 'letters':'\u0062\u24D1\uFF42\u1E03\u1E05\u1E07\u0180\u0183\u0253'},
    {'base':'c', 'letters':'\u0063\u24D2\uFF43\u0107\u0109\u010B\u010D\u00E7\u1E09\u0188\u023C\uA73F\u2184'},
    {'base':'d', 'letters':'\u0064\u24D3\uFF44\u1E0B\u010F\u1E0D\u1E11\u1E13\u1E0F\u0111\u018C\u0256\u0257\uA77A'},
    {'base':'dz','letters':'\u01F3\u01C6'},
    {'base':'e', 'letters':'\u0065\u24D4\uFF45\u00E8\u00E9\u00EA\u1EC1\u1EBF\u1EC5\u1EC3\u1EBD\u0113\u1E15\u1E17\u0115\u0117\u00EB\u1EBB\u011B\u0205\u0207\u1EB9\u1EC7\u0229\u1E1D\u0119\u1E19\u1E1B\u0247\u025B\u01DD'},
    {'base':'f', 'letters':'\u0066\u24D5\uFF46\u1E1F\u0192\uA77C'},
    {'base':'g', 'letters':'\u0067\u24D6\uFF47\u01F5\u011D\u1E21\u011F\u0121\u01E7\u0123\u01E5\u0260\uA7A1\u1D79\uA77F'},
    {'base':'h', 'letters':'\u0068\u24D7\uFF48\u0125\u1E23\u1E27\u021F\u1E25\u1E29\u1E2B\u1E96\u0127\u2C68\u2C76\u0265'},
    {'base':'hv','letters':'\u0195'},
    {'base':'i', 'letters':'\u0069\u24D8\uFF49\u00EC\u00ED\u00EE\u0129\u012B\u012D\u00EF\u1E2F\u1EC9\u01D0\u0209\u020B\u1ECB\u012F\u1E2D\u0268\u0131'},
    {'base':'j', 'letters':'\u006A\u24D9\uFF4A\u0135\u01F0\u0249'},
    {'base':'k', 'letters':'\u006B\u24DA\uFF4B\u1E31\u01E9\u1E33\u0137\u1E35\u0199\u2C6A\uA741\uA743\uA745\uA7A3'},
    {'base':'l', 'letters':'\u006C\u24DB\uFF4C\u0140\u013A\u013E\u1E37\u1E39\u013C\u1E3D\u1E3B\u017F\u0142\u019A\u026B\u2C61\uA749\uA781\uA747'},
    {'base':'lj','letters':'\u01C9'},
    {'base':'m', 'letters':'\u006D\u24DC\uFF4D\u1E3F\u1E41\u1E43\u0271\u026F'},
    {'base':'n', 'letters':'\u006E\u24DD\uFF4E\u01F9\u0144\u00F1\u1E45\u0148\u1E47\u0146\u1E4B\u1E49\u019E\u0272\u0149\uA791\uA7A5'},
    {'base':'nj','letters':'\u01CC'},
    {'base':'o', 'letters':'\u006F\u24DE\uFF4F\u00F2\u00F3\u00F4\u1ED3\u1ED1\u1ED7\u1ED5\u00F5\u1E4D\u022D\u1E4F\u014D\u1E51\u1E53\u014F\u022F\u0231\u00F6\u022B\u1ECF\u0151\u01D2\u020D\u020F\u01A1\u1EDD\u1EDB\u1EE1\u1EDF\u1EE3\u1ECD\u1ED9\u01EB\u01ED\u00F8\u01FF\u0254\uA74B\uA74D\u0275'},
    {'base':'oi','letters':'\u01A3'},
    {'base':'ou','letters':'\u0223'},
    {'base':'oo','letters':'\uA74F'},
    {'base':'p','letters':'\u0070\u24DF\uFF50\u1E55\u1E57\u01A5\u1D7D\uA751\uA753\uA755'},
    {'base':'q','letters':'\u0071\u24E0\uFF51\u024B\uA757\uA759'},
    {'base':'r','letters':'\u0072\u24E1\uFF52\u0155\u1E59\u0159\u0211\u0213\u1E5B\u1E5D\u0157\u1E5F\u024D\u027D\uA75B\uA7A7\uA783'},
    {'base':'s','letters':'\u0073\u24E2\uFF53\u00DF\u015B\u1E65\u015D\u1E61\u0161\u1E67\u1E63\u1E69\u0219\u015F\u023F\uA7A9\uA785\u1E9B'},
    {'base':'t','letters':'\u0074\u24E3\uFF54\u1E6B\u1E97\u0165\u1E6D\u021B\u0163\u1E71\u1E6F\u0167\u01AD\u0288\u2C66\uA787'},
    {'base':'tz','letters':'\uA729'},
    {'base':'u','letters': '\u0075\u24E4\uFF55\u00F9\u00FA\u00FB\u0169\u1E79\u016B\u1E7B\u016D\u00FC\u01DC\u01D8\u01D6\u01DA\u1EE7\u016F\u0171\u01D4\u0215\u0217\u01B0\u1EEB\u1EE9\u1EEF\u1EED\u1EF1\u1EE5\u1E73\u0173\u1E77\u1E75\u0289'},
    {'base':'v','letters':'\u0076\u24E5\uFF56\u1E7D\u1E7F\u028B\uA75F\u028C'},
    {'base':'vy','letters':'\uA761'},
    {'base':'w','letters':'\u0077\u24E6\uFF57\u1E81\u1E83\u0175\u1E87\u1E85\u1E98\u1E89\u2C73'},
    {'base':'x','letters':'\u0078\u24E7\uFF58\u1E8B\u1E8D'},
    {'base':'y','letters':'\u0079\u24E8\uFF59\u1EF3\u00FD\u0177\u1EF9\u0233\u1E8F\u00FF\u1EF7\u1E99\u1EF5\u01B4\u024F\u1EFF'},
    {'base':'z','letters':'\u007A\u24E9\uFF5A\u017A\u1E91\u017C\u017E\u1E93\u1E95\u01B6\u0225\u0240\u2C6C\uA763'}
];

var diacriticsMap = {};
for (var i=0; i < defaultDiacriticsRemovalMap .length; i++){
    var letters = defaultDiacriticsRemovalMap [i].letters;
    for (var j=0; j < letters.length ; j++){
        diacriticsMap[letters[j]] = defaultDiacriticsRemovalMap [i].base;
    }
}

// "what?" version ... http://jsperf.com/diacritics/12
// function removeDiacritics (str) {
//     return str.replace(/[^\u0000-\u007E]/g, function(a){
//        return diacriticsMap[a] || a;
//     });
// }

function removeDiacritics(strAccents) {
    var strAccents = strAccents.split('');
    var strAccentsOut = new Array();
    var strAccentsLen = strAccents.length;
    var accents = 'ÀÁÂÃÄÅàáâãäåÒÓÔÕÕÖØòóôõöøÈÉÊËèéêëðÇçÐÌÍÎÏìíîïÙÚÛÜùúûüÑñŠšŸÿýŽž';
    var accentsOut = "AAAAAAaaaaaaOOOOOOOooooooEEEEeeeeeCcDIIIIiiiiUUUUuuuuNnSsYyyZz";
    for (var y = 0; y < strAccentsLen; y++) {
      if (accents.indexOf(strAccents[y]) != -1) {
        strAccentsOut[y] = accentsOut.substr(accents.indexOf(strAccents[y]), 1);
      } else
        strAccentsOut[y] = strAccents[y];
    }
    strAccentsOut = strAccentsOut.join('');
    return strAccentsOut;
  }
