// Generated by CoffeeScript 1.7.1
(function() {
  var CONNECTION_STRING_NAMED_INSTANCE, CONNECTION_STRING_PORT, DECLARATIONS, EMPTY_BUFFER, ISOLATION_LEVEL, Pool, TYPES, UDT, castParameter, createColumns, declare, isolationLevelDeclaration, msnodesql, util, valueCorrection, _ref,
    __hasProp = {}.hasOwnProperty,
    __extends = 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; };

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

  msnodesql = require('msnodesql');

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

  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, index, out, _i, _len;
    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(_super) {
      __extends(MsnodesqlConnection, _super);

      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, value, _ref1, _ref2;
        defaultConnectionString = CONNECTION_STRING_PORT;
        if (config.options.instanceName != null) {
          defaultConnectionString = CONNECTION_STRING_NAMED_INSTANCE;
        }
        cfg = {
          connectionString: (_ref1 = config.connectionString) != null ? _ref1 : defaultConnectionString
        };
        cfg.connectionString = cfg.connectionString.replace(new RegExp('#{([^}]*)}', 'g'), function(p) {
          var key, _ref2;
          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 (_ref2 = config[key]) != null ? _ref2 : '';
          }
        });
        cfg_pool = {
          name: 'mssql',
          max: 10,
          min: 0,
          idleTimeoutMillis: 30000,
          create: (function(_this) {
            return function(callback) {
              return msnodesql.open(cfg.connectionString, 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;
          },
          destroy: function(c) {
            return c != null ? c.close() : void 0;
          }
        };
        if (config.pool) {
          _ref2 = config.pool;
          for (key in _ref2) {
            value = _ref2[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);
            }
            _this.pool.release(connection);
            if (err) {
              _this.pool.destroyAllNow();
              _this.pool = null;
            }
            return callback(err);
          };
        })(this));
      };

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

      return MsnodesqlConnection;

    })(Connection);
    MsnodesqlTransaction = (function(_super) {
      __extends(MsnodesqlTransaction, _super);

      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().query("set transaction isolation level " + (isolationLevelDeclaration(_this.isolationLevel)) + ";begin tran;", callback);
          };
        })(this));
      };

      MsnodesqlTransaction.prototype.commit = function(callback) {
        return this.request().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().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(_super) {
      __extends(MsnodesqlRequest, _super);

      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) {
        return process.nextTick(function() {
          return callback(RequestError("Bulk insert is not supported in 'msnodesql' driver.", 'ENOTSUPP'));
        });
      };

      MsnodesqlRequest.prototype.query = function(command, callback) {
        var columns, handleOutput, input, name, output, param, recordset, recordsets, row, sets, started;
        if (this.verbose && !this.nested) {
          this._log("---------- sql query ----------\n    query: " + command);
        }
        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;
        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) {
              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) {
                if (row) {
                  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)
                });
                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 (_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;
                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.once('error', function(err) {
                var elapsed;
                if (_this.verbose && !_this.nested) {
                  elapsed = Date.now() - started;
                  _this._log("    error: " + err);
                  _this._log(" duration: " + elapsed + "ms");
                  _this._log("---------- completed ----------");
                }
                return typeof callback === "function" ? callback(RequestError(err)) : void 0;
              });
              return req.once('done', function() {
                var elapsed, last, _ref1, _ref2;
                if (!_this.nested) {
                  if (row) {
                    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, spp, started, _ref1;
        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, returnValue, _ref2, _ref3;
            _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);
