// Generated by CoffeeScript 1.10.0
(function() {
  var CONNECTION_STRING_NAMED_INSTANCE, CONNECTION_STRING_PORT, DECLARATIONS, EMPTY_BUFFER, ISOLATION_LEVEL, JSON_COLUMN_ID, Pool, TYPES, UDT, XML_COLUMN_ID, castParameter, createColumns, declare, isolationLevelDeclaration, msnodesql, ref, util, valueCorrection,
    extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
    hasProp = {}.hasOwnProperty;

  Pool = require('generic-pool').Pool;

  msnodesql = require('msnodesqlv8');

  util = require('util');

  ref = require('./datatypes'), TYPES = ref.TYPES, declare = ref.declare;

  UDT = require('./udt').PARSERS;

  ISOLATION_LEVEL = require('./isolationlevel');

  DECLARATIONS = require('./datatypes').DECLARATIONS;

  EMPTY_BUFFER = new Buffer(0);

  JSON_COLUMN_ID = 'JSON_F52E2B61-18A1-11d1-B105-00805F49916B';

  XML_COLUMN_ID = 'XML_F52E2B61-18A1-11d1-B105-00805F49916B';

  CONNECTION_STRING_PORT = 'Driver={SQL Server Native Client 11.0};Server={#{server},#{port}};Database={#{database}};Uid={#{user}};Pwd={#{password}};Trusted_Connection={#{trusted}};';

  CONNECTION_STRING_NAMED_INSTANCE = 'Driver={SQL Server Native Client 11.0};Server={#{server}\\#{instance}};Database={#{database}};Uid={#{user}};Pwd={#{password}};Trusted_Connection={#{trusted}};';


  /*
  @ignore
   */

  castParameter = function(value, type) {
    if (value == null) {
      if (type === TYPES.Binary || type === TYPES.VarBinary || type === TYPES.Image) {
        return EMPTY_BUFFER;
      }
      return null;
    }
    switch (type) {
      case TYPES.VarChar:
      case TYPES.NVarChar:
      case TYPES.Char:
      case TYPES.NChar:
      case TYPES.Xml:
      case TYPES.Text:
      case TYPES.NText:
        if (typeof value !== 'string' && !(value instanceof String)) {
          value = value.toString();
        }
        break;
      case TYPES.Int:
      case TYPES.TinyInt:
      case TYPES.BigInt:
      case TYPES.SmallInt:
        if (typeof value !== 'number' && !(value instanceof Number)) {
          value = parseInt(value);
          if (isNaN(value)) {
            value = null;
          }
        }
        break;
      case TYPES.Float:
      case TYPES.Real:
      case TYPES.Decimal:
      case TYPES.Numeric:
      case TYPES.SmallMoney:
      case TYPES.Money:
        if (typeof value !== 'number' && !(value instanceof Number)) {
          value = parseFloat(value);
          if (isNaN(value)) {
            value = null;
          }
        }
        break;
      case TYPES.Bit:
        if (typeof value !== 'boolean' && !(value instanceof Boolean)) {
          value = Boolean(value);
        }
        break;
      case TYPES.DateTime:
      case TYPES.SmallDateTime:
      case TYPES.DateTimeOffset:
      case TYPES.Date:
        if (!(value instanceof Date)) {
          value = new Date(value);
        }
        break;
      case TYPES.Binary:
      case TYPES.VarBinary:
      case TYPES.Image:
        if (!(value instanceof Buffer)) {
          value = new Buffer(value.toString());
        }
    }
    return value;
  };


  /*
  @ignore
   */

  createColumns = function(metadata) {
    var column, i, index, len, out;
    out = {};
    for (index = i = 0, len = metadata.length; i < len; index = ++i) {
      column = metadata[index];
      out[column.name] = {
        index: index,
        name: column.name,
        length: column.size,
        type: DECLARATIONS[column.sqlType]
      };
      if (column.udtType != null) {
        out[column.name].udt = {
          name: column.udtType
        };
        if (DECLARATIONS[column.udtType]) {
          out[column.name].type = DECLARATIONS[column.udtType];
        }
      }
    }
    return out;
  };


  /*
  @ignore
   */

  isolationLevelDeclaration = function(type) {
    switch (type) {
      case ISOLATION_LEVEL.READ_UNCOMMITTED:
        return "READ UNCOMMITTED";
      case ISOLATION_LEVEL.READ_COMMITTED:
        return "READ COMMITTED";
      case ISOLATION_LEVEL.REPEATABLE_READ:
        return "REPEATABLE READ";
      case ISOLATION_LEVEL.SERIALIZABLE:
        return "SERIALIZABLE";
      case ISOLATION_LEVEL.SNAPSHOT:
        return "SNAPSHOT";
      default:
        throw new TransactionError("Invalid isolation level.");
    }
  };


  /*
  @ignore
   */

  valueCorrection = function(value, metadata) {
    if (metadata.sqlType === 'time' && (value != null)) {
      value.setFullYear(1970);
      return value;
    } else if (metadata.sqlType === 'udt' && (value != null)) {
      if (UDT[metadata.udtType]) {
        return UDT[metadata.udtType](value);
      } else {
        return value;
      }
    } else {
      return value;
    }
  };


  /*
  @ignore
   */

  module.exports = function(Connection, Transaction, Request, ConnectionError, TransactionError, RequestError) {
    var MsnodesqlConnection, MsnodesqlRequest, MsnodesqlTransaction;
    MsnodesqlConnection = (function(superClass) {
      extend(MsnodesqlConnection, superClass);

      function MsnodesqlConnection() {
        return MsnodesqlConnection.__super__.constructor.apply(this, arguments);
      }

      MsnodesqlConnection.prototype.pool = null;

      MsnodesqlConnection.prototype.connect = function(config, callback) {
        var cfg, cfg_pool, defaultConnectionString, key, ref1, ref2, ref3, ref4, value;
        defaultConnectionString = CONNECTION_STRING_PORT;
        if (config.options.instanceName != null) {
          defaultConnectionString = CONNECTION_STRING_NAMED_INSTANCE;
        }
        cfg = {
          conn_str: (ref1 = config.connectionString) != null ? ref1 : defaultConnectionString,
          conn_timeout: ((ref2 = (ref3 = config.connectionTimeout) != null ? ref3 : config.timeout) != null ? ref2 : 15000) / 1000
        };
        cfg.conn_str = cfg.conn_str.replace(new RegExp('#{([^}]*)}', 'g'), function(p) {
          var key, ref4;
          key = p.substr(2, p.length - 3);
          if (key === 'instance') {
            return config.options.instanceName;
          } else if (key === 'trusted') {
            if (config.options.trustedConnection) {
              return 'Yes';
            } else {
              return 'No';
            }
          } else {
            return (ref4 = config[key]) != null ? ref4 : '';
          }
        });
        cfg_pool = {
          name: 'mssql',
          max: 10,
          min: 0,
          idleTimeoutMillis: 30000,
          create: (function(_this) {
            return function(callback) {
              return msnodesql.open(cfg, function(err, c) {
                if (err) {
                  err = ConnectionError(err);
                }
                if (err) {
                  return callback(err, null);
                }
                return callback(null, c);
              });
            };
          })(this),
          validate: function(c) {
            return (c != null) && !c.hasError;
          },
          destroy: function(c) {
            return c != null ? c.close() : void 0;
          }
        };
        if (config.pool) {
          ref4 = config.pool;
          for (key in ref4) {
            value = ref4[key];
            cfg_pool[key] = value;
          }
        }
        this.pool = Pool(cfg_pool, cfg);
        return this.pool.acquire((function(_this) {
          return function(err, connection) {
            if (err && !(err instanceof Error)) {
              err = new Error(err);
            }
            if (err) {
              _this.pool.drain(function() {
                var ref5;
                if ((ref5 = _this.pool) != null) {
                  ref5.destroyAllNow();
                }
                return _this.pool = null;
              });
            } else {
              _this.pool.release(connection);
            }
            return callback(err);
          };
        })(this));
      };

      MsnodesqlConnection.prototype.close = function(callback) {
        if (!this.pool) {
          return callback(null);
        }
        return this.pool.drain((function(_this) {
          return function() {
            var ref1;
            if ((ref1 = _this.pool) != null) {
              ref1.destroyAllNow();
            }
            _this.pool = null;
            return callback(null);
          };
        })(this));
      };

      return MsnodesqlConnection;

    })(Connection);
    MsnodesqlTransaction = (function(superClass) {
      extend(MsnodesqlTransaction, superClass);

      function MsnodesqlTransaction() {
        return MsnodesqlTransaction.__super__.constructor.apply(this, arguments);
      }

      MsnodesqlTransaction.prototype.begin = function(callback) {
        return this.connection.pool.acquire((function(_this) {
          return function(err, connection) {
            if (err) {
              return callback(err);
            }
            _this._pooledConnection = connection;
            return _this.request()._dedicated(_this._pooledConnection).query("set transaction isolation level " + (isolationLevelDeclaration(_this.isolationLevel)) + ";begin tran;", callback);
          };
        })(this));
      };

      MsnodesqlTransaction.prototype.commit = function(callback) {
        return this.request()._dedicated(this._pooledConnection).query('commit tran', (function(_this) {
          return function(err) {
            _this.connection.pool.release(_this._pooledConnection);
            _this._pooledConnection = null;
            return callback(err);
          };
        })(this));
      };

      MsnodesqlTransaction.prototype.rollback = function(callback) {
        return this.request()._dedicated(this._pooledConnection).query('rollback tran', (function(_this) {
          return function(err) {
            _this.connection.pool.release(_this._pooledConnection);
            _this._pooledConnection = null;
            return callback(err);
          };
        })(this));
      };

      return MsnodesqlTransaction;

    })(Transaction);
    MsnodesqlRequest = (function(superClass) {
      extend(MsnodesqlRequest, superClass);

      function MsnodesqlRequest() {
        return MsnodesqlRequest.__super__.constructor.apply(this, arguments);
      }

      MsnodesqlRequest.prototype.batch = function(batch, callback) {
        return MsnodesqlRequest.prototype.query.call(this, batch, callback);
      };

      MsnodesqlRequest.prototype.bulk = function(table, callback) {
        var started;
        table._makeBulk();
        if (!table.name) {
          process.nextTick(function() {
            return callback(RequestError("Table name must be specified for bulk insert.", "ENAME"));
          });
        }
        if (table.name.charAt(0) === '@') {
          process.nextTick(function() {
            return callback(RequestError("You can't use table variables for bulk insert.", "ENAME"));
          });
        }
        started = Date.now();
        return this._acquire((function(_this) {
          return function(err, connection) {
            var done, elapsed, go, objectid, req;
            if (!err) {
              if (_this.verbose) {
                _this._log("-------- sql bulk load --------\n    table: " + table.name);
              }
              done = function(err, rowCount) {
                var e, elapsed;
                if (err) {
                  if ('string' === typeof err.sqlstate && err.sqlstate.toLowerCase() === '08s01') {
                    connection.hasError = true;
                  }
                  e = RequestError(err);
                  if (/^\[Microsoft\]\[SQL Server Native Client 11\.0\](?:\[SQL Server\])?([\s\S]*)$/.exec(err.message)) {
                    e.message = RegExp.$1;
                  }
                  e.code = 'EREQUEST';
                  if (_this.verbose && !_this.nested) {
                    _this._log("    error: " + e);
                  }
                }
                if (_this.verbose) {
                  elapsed = Date.now() - started;
                  _this._log(" duration: " + elapsed + "ms");
                  _this._log("---------- completed ----------");
                }
                _this._release(connection);
                if (e) {
                  return typeof callback === "function" ? callback(e) : void 0;
                } else {
                  return typeof callback === "function" ? callback(null, table.rows.length) : void 0;
                }
              };
              go = function() {
                var tm;
                tm = connection.tableMgr();
                return tm.bind(table.path.replace(/\[|\]/g, ''), function(mgr) {
                  var col, i, index, item, j, len, len1, ref1, ref2, row, rows;
                  if (mgr.columns.length === 0) {
                    return done(new RequestError("Table was not found on the server.", "ENAME"));
                  }
                  rows = [];
                  ref1 = table.rows;
                  for (i = 0, len = ref1.length; i < len; i++) {
                    row = ref1[i];
                    item = {};
                    ref2 = table.columns;
                    for (index = j = 0, len1 = ref2.length; j < len1; index = ++j) {
                      col = ref2[index];
                      item[col.name] = row[index];
                    }
                    rows.push(item);
                  }
                  return mgr.insertRows(rows, done);
                });
              };
              if (table.create) {
                if (table.temporary) {
                  objectid = "tempdb..[" + table.name + "]";
                } else {
                  objectid = table.path;
                }
                if (_this.verbose) {
                  elapsed = Date.now() - started;
                  _this._log("  message: attempting to create table " + table.path + " if not exists");
                }
                return req = connection.queryRaw("if object_id('" + (objectid.replace(/'/g, '\'\'')) + "') is null " + (table.declare()), function(err) {
                  if (err) {
                    return done(err);
                  }
                  return go();
                });
              } else {
                return go();
              }
            }
          };
        })(this));
      };

      MsnodesqlRequest.prototype.query = function(command, callback) {
        var chunksBuffer, columns, handleOutput, input, isChunkedRecordset, name, output, param, recordset, recordsets, row, sets, started;
        if (command.length === 0) {
          return process.nextTick(function() {
            var elapsed;
            if (this.verbose && !this.nested) {
              this._log("---------- response -----------");
              elapsed = Date.now() - started;
              this._log(" duration: " + elapsed + "ms");
              this._log("---------- completed ----------");
            }
            return typeof callback === "function" ? callback(null, this.multiple || this.nested ? [] : null) : void 0;
          });
        }
        row = null;
        columns = null;
        recordset = null;
        recordsets = [];
        started = Date.now();
        handleOutput = false;
        isChunkedRecordset = false;
        chunksBuffer = null;
        if (!this.nested) {
          input = (function() {
            var ref1, results;
            ref1 = this.parameters;
            results = [];
            for (name in ref1) {
              param = ref1[name];
              results.push("@" + param.name + " " + (declare(param.type, param)));
            }
            return results;
          }).call(this);
          sets = (function() {
            var ref1, results;
            ref1 = this.parameters;
            results = [];
            for (name in ref1) {
              param = ref1[name];
              if (param.io === 1) {
                results.push("set @" + param.name + "=?");
              }
            }
            return results;
          }).call(this);
          output = (function() {
            var ref1, results;
            ref1 = this.parameters;
            results = [];
            for (name in ref1) {
              param = ref1[name];
              if (param.io === 2) {
                results.push("@" + param.name + " as '" + param.name + "'");
              }
            }
            return results;
          }).call(this);
          if (input.length) {
            command = "declare " + (input.join(',')) + ";" + (sets.join(';')) + ";" + command + ";";
          }
          if (output.length) {
            command += "select " + (output.join(',')) + ";";
            handleOutput = true;
          }
        }
        return this._acquire((function(_this) {
          return function(err, connection) {
            var req;
            if (!err) {
              if (_this.verbose && !_this.nested) {
                _this._log("---------- sql query ----------\n    query: " + command);
              }
              req = connection.queryRaw(command, (function() {
                var ref1, results;
                ref1 = this.parameters;
                results = [];
                for (name in ref1) {
                  param = ref1[name];
                  if (param.io === 1) {
                    results.push(castParameter(param.value, param.type));
                  }
                }
                return results;
              }).call(_this));
              if (_this.verbose && !_this.nested) {
                _this._log("---------- response -----------");
              }
              req.on('meta', function(metadata) {
                var error, ex, ref1;
                if (row) {
                  if (isChunkedRecordset) {
                    if (columns[0].name === JSON_COLUMN_ID && _this.connection.config.parseJSON === true) {
                      try {
                        row = JSON.parse(chunksBuffer.join(''));
                        if (!_this.stream) {
                          recordsets[recordsets.length - 1][0] = row;
                        }
                      } catch (error) {
                        ex = error;
                        row = null;
                        ex = RequestError(new Error("Failed to parse incoming JSON. " + ex.message), 'EJSON');
                        if (_this.stream) {
                          _this.emit('error', ex);
                        } else {
                          console.error(ex);
                        }
                      }
                    } else {
                      row[columns[0].name] = chunksBuffer.join('');
                    }
                    chunksBuffer = null;
                  }
                  if (_this.verbose) {
                    _this._log(util.inspect(row));
                    _this._log("---------- --------------------");
                  }
                  if (row["___return___"] == null) {
                    if (_this.stream) {
                      _this.emit('row', row);
                    }
                  }
                }
                row = null;
                columns = metadata;
                recordset = [];
                Object.defineProperty(recordset, 'columns', {
                  enumerable: false,
                  value: createColumns(metadata)
                });
                isChunkedRecordset = false;
                if (metadata.length === 1 && ((ref1 = metadata[0].name) === JSON_COLUMN_ID || ref1 === XML_COLUMN_ID)) {
                  isChunkedRecordset = true;
                  chunksBuffer = [];
                }
                if (_this.stream) {
                  if (recordset.columns["___return___"] == null) {
                    return _this.emit('recordset', recordset.columns);
                  }
                } else {
                  return recordsets.push(recordset);
                }
              });
              req.on('row', function(rownumber) {
                if (row) {
                  if (isChunkedRecordset) {
                    return;
                  }
                  if (_this.verbose) {
                    _this._log(util.inspect(row));
                    _this._log("---------- --------------------");
                  }
                  if (row["___return___"] == null) {
                    if (_this.stream) {
                      _this.emit('row', row);
                    }
                  }
                }
                row = {};
                if (!_this.stream) {
                  return recordset.push(row);
                }
              });
              req.on('column', function(idx, data, more) {
                var exi;
                if (isChunkedRecordset) {
                  return chunksBuffer.push(data);
                } else {
                  data = valueCorrection(data, columns[idx]);
                  exi = row[columns[idx].name];
                  if (exi != null) {
                    if (exi instanceof Array) {
                      return exi.push(data);
                    } else {
                      return row[columns[idx].name] = [exi, data];
                    }
                  } else {
                    return row[columns[idx].name] = data;
                  }
                }
              });
              req.on('rowcount', function(count) {
                if (count > 0) {
                  return _this.rowsAffected += count;
                }
              });
              req.once('error', function(err) {
                var e, elapsed;
                if ('string' === typeof err.sqlstate && err.sqlstate.toLowerCase() === '08s01') {
                  connection.hasError = true;
                }
                e = RequestError(err);
                if (/^\[Microsoft\]\[SQL Server Native Client 11\.0\](?:\[SQL Server\])?([\s\S]*)$/.exec(err.message)) {
                  e.message = RegExp.$1;
                }
                e.code = 'EREQUEST';
                if (_this.verbose && !_this.nested) {
                  elapsed = Date.now() - started;
                  _this._log("    error: " + err);
                  _this._log(" duration: " + elapsed + "ms");
                  _this._log("---------- completed ----------");
                }
                _this._release(connection);
                return typeof callback === "function" ? callback(e) : void 0;
              });
              return req.once('done', function() {
                var elapsed, error, ex, last, ref1, ref2;
                if (!_this.nested) {
                  if (row) {
                    if (isChunkedRecordset) {
                      if (columns[0].name === JSON_COLUMN_ID && _this.connection.config.parseJSON === true) {
                        try {
                          row = JSON.parse(chunksBuffer.join(''));
                          if (!_this.stream) {
                            recordsets[recordsets.length - 1][0] = row;
                          }
                        } catch (error) {
                          ex = error;
                          row = null;
                          ex = RequestError(new Error("Failed to parse incoming JSON. " + ex.message), 'EJSON');
                          if (_this.stream) {
                            _this.emit('error', ex);
                          } else {
                            console.error(ex);
                          }
                        }
                      } else {
                        row[columns[0].name] = chunksBuffer.join('');
                      }
                      chunksBuffer = null;
                    }
                    if (_this.verbose) {
                      _this._log(util.inspect(row));
                      _this._log("---------- --------------------");
                    }
                    if (row["___return___"] == null) {
                      if (_this.stream) {
                        _this.emit('row', row);
                      }
                    }
                  }
                  if (handleOutput) {
                    last = (ref1 = recordsets.pop()) != null ? ref1[0] : void 0;
                    ref2 = _this.parameters;
                    for (name in ref2) {
                      param = ref2[name];
                      if (!(param.io === 2)) {
                        continue;
                      }
                      param.value = last[param.name];
                      if (_this.verbose) {
                        _this._log("   output: @" + param.name + ", " + param.type.declaration + ", " + param.value);
                      }
                    }
                  }
                  if (_this.verbose) {
                    elapsed = Date.now() - started;
                    _this._log(" duration: " + elapsed + "ms");
                    _this._log("---------- completed ----------");
                  }
                }
                _this._release(connection);
                if (_this.stream) {
                  return callback(null, _this.nested ? row : null);
                } else {
                  return typeof callback === "function" ? callback(null, _this.multiple || _this.nested ? recordsets : recordsets[0]) : void 0;
                }
              });
            } else {
              if (connection) {
                _this._release(connection);
              }
              return typeof callback === "function" ? callback(err) : void 0;
            }
          };
        })(this));
      };

      MsnodesqlRequest.prototype.execute = function(procedure, callback) {
        var cmd, name, param, ref1, spp, started;
        if (this.verbose) {
          this._log("---------- sql execute --------\n     proc: " + procedure);
        }
        started = Date.now();
        cmd = "declare " + (['@___return___ int'].concat((function() {
          var ref1, results;
          ref1 = this.parameters;
          results = [];
          for (name in ref1) {
            param = ref1[name];
            if (param.io === 2) {
              results.push("@" + param.name + " " + (declare(param.type, param)));
            }
          }
          return results;
        }).call(this)).join(', ')) + ";";
        cmd += "exec @___return___ = " + procedure + " ";
        spp = [];
        ref1 = this.parameters;
        for (name in ref1) {
          param = ref1[name];
          if (this.verbose) {
            this._log("   " + (param.io === 1 ? " input" : "output") + ": @" + param.name + ", " + param.type.declaration + ", " + param.value);
          }
          if (param.io === 2) {
            spp.push("@" + param.name + "=@" + param.name + " output");
          } else {
            spp.push("@" + param.name + "=?");
          }
        }
        cmd += (spp.join(', ')) + ";";
        cmd += "select " + (['@___return___ as \'___return___\''].concat((function() {
          var ref2, results;
          ref2 = this.parameters;
          results = [];
          for (name in ref2) {
            param = ref2[name];
            if (param.io === 2) {
              results.push("@" + param.name + " as '" + param.name + "'");
            }
          }
          return results;
        }).call(this)).join(', ')) + ";";
        if (this.verbose) {
          this._log("---------- response -----------");
        }
        this.nested = true;
        return MsnodesqlRequest.prototype.query.call(this, cmd, (function(_this) {
          return function(err, recordsets) {
            var elapsed, last, ref2, ref3, returnValue;
            _this.nested = false;
            if (err) {
              if (_this.verbose) {
                elapsed = Date.now() - started;
                _this._log("    error: " + err);
                _this._log(" duration: " + elapsed + "ms");
                _this._log("---------- completed ----------");
              }
              return typeof callback === "function" ? callback(err) : void 0;
            } else {
              if (_this.stream) {
                last = recordsets;
              } else {
                last = (ref2 = recordsets.pop()) != null ? ref2[0] : void 0;
              }
              if (last && (last.___return___ != null)) {
                returnValue = last.___return___;
                ref3 = _this.parameters;
                for (name in ref3) {
                  param = ref3[name];
                  if (!(param.io === 2)) {
                    continue;
                  }
                  param.value = last[param.name];
                  if (_this.verbose) {
                    _this._log("   output: @" + param.name + ", " + param.type.declaration + ", " + param.value);
                  }
                }
              }
              if (_this.verbose) {
                elapsed = Date.now() - started;
                _this._log("   return: " + returnValue);
                _this._log(" duration: " + elapsed + "ms");
                _this._log("---------- completed ----------");
              }
              if (_this.stream) {
                return callback(null, null, returnValue);
              } else {
                recordsets.returnValue = returnValue;
                return typeof callback === "function" ? callback(null, recordsets, returnValue) : void 0;
              }
            }
          };
        })(this));
      };


      /*
      		Cancel currently executed request.
       */

      MsnodesqlRequest.prototype.cancel = function() {
        return false;
      };

      return MsnodesqlRequest;

    })(Request);
    return {
      Connection: MsnodesqlConnection,
      Transaction: MsnodesqlTransaction,
      Request: MsnodesqlRequest,
      fix: function() {}
    };
  };

}).call(this);
