// Generated by CoffeeScript 1.10.0
(function() {
  var FIXED, ISOLATION_LEVEL, Pool, TYPES, castParameter, createColumns, createParameterHeader, declare, formatHex, isolationLevelDeclaration, parseGuid, ref, tds, util,
    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;

  tds = require('tds');

  util = require('util');

  FIXED = false;

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

  ISOLATION_LEVEL = require('./isolationlevel');


  /*
  @ignore
   */

  castParameter = function(value, type) {
    if (value == null) {
      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
   */

  createParameterHeader = function(param) {
    var header, ref1, ref2, ref3;
    header = {
      type: param.type.declaration
    };
    switch (param.type) {
      case TYPES.VarChar:
      case TYPES.NVarChar:
      case TYPES.VarBinary:
        header.size = "MAX";
        break;
      case TYPES.Char:
      case TYPES.NChar:
      case TYPES.Binary:
        header.size = (ref1 = (ref2 = param.length) != null ? ref2 : (ref3 = param.value) != null ? ref3.length : void 0) != null ? ref1 : 1;
    }
    return header;
  };


  /*
  @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.length,
        type: TYPES[column.type.sqlType]
      };
    }
    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.");
    }
  };


  /*
  Taken from Tedious.
  
  @private
   */

  formatHex = function(number) {
    var hex;
    hex = number.toString(16);
    if (hex.length === 1) {
      hex = '0' + hex;
    }
    return hex;
  };


  /*
  Taken from Tedious.
  
  @private
   */

  parseGuid = function(buffer) {
    var guid;
    guid = formatHex(buffer[3]) + formatHex(buffer[2]) + formatHex(buffer[1]) + formatHex(buffer[0]) + '-' + formatHex(buffer[5]) + formatHex(buffer[4]) + '-' + formatHex(buffer[7]) + formatHex(buffer[6]) + '-' + formatHex(buffer[8]) + formatHex(buffer[9]) + '-' + formatHex(buffer[10]) + formatHex(buffer[11]) + formatHex(buffer[12]) + formatHex(buffer[13]) + formatHex(buffer[14]) + formatHex(buffer[15]);
    return guid.toUpperCase();
  };


  /*
  @ignore
   */

  module.exports = function(Connection, Transaction, Request, ConnectionError, TransactionError, RequestError) {
    var TDSConnection, TDSRequest, TDSTransaction;
    TDSConnection = (function(superClass) {
      extend(TDSConnection, superClass);

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

      TDSConnection.prototype.pool = null;

      TDSConnection.prototype.connect = function(config, callback) {
        var cfg, cfg_pool, key, ref1, value;
        cfg = {
          userName: config.user,
          password: config.password,
          host: config.server,
          port: config.port,
          database: config.database
        };
        cfg_pool = {
          name: 'mssql',
          max: 10,
          min: 0,
          idleTimeoutMillis: 30000,
          create: (function(_this) {
            return function(callback) {
              var c, ref1, timeouted, tmr;
              c = new tds.Connection(cfg);
              c.on('error', function(err) {
                if (err.code === 'ECONNRESET') {
                  c.hasError = true;
                  return;
                }
                return _this.emit('error', err);
              });
              timeouted = false;
              tmr = setTimeout(function() {
                timeouted = true;
                c._client._socket.destroy();
                return callback(new ConnectionError("Connection timeout.", 'ETIMEOUT'), null);
              }, (ref1 = config.timeout) != null ? ref1 : 15000);
              return c.connect(function(err) {
                clearTimeout(tmr);
                if (timeouted) {
                  return;
                }
                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.end() : void 0;
          }
        };
        if (config.pool) {
          ref1 = config.pool;
          for (key in ref1) {
            value = ref1[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 ref2;
                if ((ref2 = _this.pool) != null) {
                  ref2.destroyAllNow();
                }
                return _this.pool = null;
              });
            } else {
              _this.pool.release(connection);
            }
            return callback(err);
          };
        })(this));
      };

      TDSConnection.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 TDSConnection;

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

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

      TDSTransaction.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)), function(err) {
              if (err) {
                return TransactionError(err);
              }
              return connection.setAutoCommit(false, callback);
            });
          };
        })(this));
      };

      TDSTransaction.prototype.commit = function(callback) {
        return this._pooledConnection.commit((function(_this) {
          return function(err) {
            if (err) {
              err = TransactionError(err);
            }
            _this.connection.pool.release(_this._pooledConnection);
            _this._pooledConnection = null;
            return callback(err);
          };
        })(this));
      };

      TDSTransaction.prototype.rollback = function(callback) {
        return this._pooledConnection.rollback((function(_this) {
          return function(err) {
            if (err) {
              err = TransactionError(err);
            }
            _this.connection.pool.release(_this._pooledConnection);
            _this._pooledConnection = null;
            return callback(err);
          };
        })(this));
      };

      return TDSTransaction;

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

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

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

      TDSRequest.prototype.bulk = function(table, callback) {
        return process.nextTick(function() {
          return callback(RequestError("Bulk insert is not supported in 'msnodesql' driver.", 'ENOTSUPP'));
        });
      };

      TDSRequest.prototype.query = function(command, callback) {
        var errors, handleOutput, input, lastrow, name, output, param, paramHeaders, paramValues, recordset, recordsets, ref1, 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;
          });
        }
        recordset = null;
        recordsets = [];
        started = Date.now();
        handleOutput = false;
        errors = [];
        lastrow = null;
        paramHeaders = {};
        paramValues = {};
        ref1 = this.parameters;
        for (name in ref1) {
          param = ref1[name];
          if (!(param.io === 1)) {
            continue;
          }
          paramHeaders[name] = createParameterHeader(param);
          paramValues[name] = castParameter(param.value, param.type);
        }
        if (!this.nested) {
          input = (function() {
            var ref2, results;
            ref2 = this.parameters;
            results = [];
            for (name in ref2) {
              param = ref2[name];
              if (param.io === 2) {
                results.push("@" + param.name + " " + (declare(param.type, param)));
              }
            }
            return results;
          }).call(this);
          output = (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);
          if (input.length) {
            command = "declare " + (input.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.canceled) {
                if (_this.verbose) {
                  _this._log("---------- canceling ----------");
                }
                _this._release(connection);
                return typeof callback === "function" ? callback(new RequestError("Canceled.", 'ECANCEL')) : void 0;
              }
              _this._cancel = function() {
                if (_this.verbose) {
                  _this._log("---------- canceling ----------");
                }
                return req.cancel();
              };
              req = connection.createStatement(command, paramHeaders);
              req.on('row', function(tdsrow) {
                var col, exi, i, len, ref2, row, value;
                row = {};
                ref2 = tdsrow.metadata.columns;
                for (i = 0, len = ref2.length; i < len; i++) {
                  col = ref2[i];
                  value = tdsrow.getValue(col.name);
                  if (value != null) {
                    if (col.type.name === 'GUIDTYPE') {
                      value = parseGuid(value);
                    }
                  }
                  exi = row[col.name];
                  if (exi != null) {
                    if (exi instanceof Array) {
                      exi.push(col.value);
                    } else {
                      row[col.name] = [exi, value];
                    }
                  } else {
                    row[col.name] = value;
                  }
                }
                if (_this.verbose) {
                  _this._log(util.inspect(row));
                  _this._log("---------- --------------------");
                }
                if (row["___return___"] == null) {
                  if (_this.stream) {
                    _this.emit('row', row);
                  }
                } else {
                  lastrow = row;
                }
                if (!_this.stream) {
                  return recordset.push(row);
                }
              });
              req.on('metadata', function(metadata) {
                recordset = [];
                Object.defineProperty(recordset, 'columns', {
                  enumerable: false,
                  value: createColumns(metadata.columns)
                }, _this.nested);
                if (_this.stream) {
                  if (recordset.columns["___return___"] == null) {
                    return _this.emit('recordset', recordset.columns);
                  }
                } else {
                  return recordsets.push(recordset);
                }
              });
              req.on('done', function(res) {
                var e, elapsed, error, i, last, len, ref2, ref3;
                if (_this.canceled) {
                  e = new RequestError("Canceled.", 'ECANCEL');
                  if (_this.stream) {
                    _this.emit('error', e);
                  } else {
                    errors.push(e);
                  }
                }
                if (!_this.nested) {
                  if (handleOutput) {
                    last = (ref2 = recordsets.pop()) != null ? ref2[0] : void 0;
                    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) {
                    if (errors.length) {
                      for (i = 0, len = errors.length; i < len; i++) {
                        error = errors[i];
                        _this._log("    error: " + error);
                      }
                    }
                    elapsed = Date.now() - started;
                    _this._log(" duration: " + elapsed + "ms");
                    _this._log("---------- completed ----------");
                  }
                }
                if (errors.length && !_this.stream) {
                  error = errors.pop();
                  error.precedingErrors = errors;
                }
                _this._release(connection);
                if (_this.stream) {
                  return callback(null, _this.nested ? lastrow : null);
                } else {
                  return typeof callback === "function" ? callback(error, _this.multiple || _this.nested ? recordsets : recordsets[0]) : void 0;
                }
              });
              req.on('message', function(msg) {
                return _this.emit('info', {
                  message: msg.text,
                  number: msg.number,
                  state: msg.state,
                  "class": msg.severity,
                  lineNumber: msg.lineNumber,
                  serverName: msg.serverName,
                  procName: msg.procName
                });
              });
              req.on('error', function(err) {
                var e;
                e = RequestError(err, 'EREQUEST');
                if (_this.stream) {
                  return _this.emit('error', e);
                } else {
                  return errors.push(e);
                }
              });
              return req.execute(paramValues);
            } else {
              if (connection) {
                _this._release(connection);
              }
              return typeof callback === "function" ? callback(err) : void 0;
            }
          };
        })(this));
      };

      TDSRequest.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 + "=@" + 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 TDSRequest.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.
       */

      TDSRequest.prototype.cancel = function() {
        if (this._cancel) {
          return this._cancel();
        }
        return true;
      };

      return TDSRequest;

    })(Request);
    return {
      Connection: TDSConnection,
      Transaction: TDSTransaction,
      Request: TDSRequest,
      fix: function() {
        if (!FIXED) {
          require('./tds-fix');
          return FIXED = true;
        }
      }
    };
  };

}).call(this);
