// Generated by CoffeeScript 1.7.1
var DEFAULT_ENCODING, MAX, MONEY_DIVISOR, NULL, PLP_NULL, THREE_AND_A_THIRD, UNKNOWN_PLP_LEN, guidParser, iconv, parse, readBinary, readChars, readDate, readDateTime, readDateTime2, readDateTimeOffset, readMax, readMaxBinary, readMaxChars, readMaxNChars, readNChars, readSmallDateTime, readTime, sprintf;

iconv = require('iconv-lite');

sprintf = require('sprintf').sprintf;

guidParser = require('./guid-parser');

require('./buffertools');

NULL = (1 << 16) - 1;

MAX = (1 << 16) - 1;

THREE_AND_A_THIRD = 3 + (1 / 3);

MONEY_DIVISOR = 10000;

PLP_NULL = new Buffer([0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);

UNKNOWN_PLP_LEN = new Buffer([0xFE, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF]);

DEFAULT_ENCODING = 'utf8';

parse = function(buffer, metaData, options) {
  var codepage, dataLength, high, low, sign, textPointerLength, textPointerNull, type, value;
  value = void 0;
  dataLength = void 0;
  textPointerNull = void 0;
  type = metaData.type;
  if (type.hasTextPointerAndTimestamp) {
    textPointerLength = buffer.readUInt8();
    if (textPointerLength !== 0) {
      buffer.readBuffer(textPointerLength);
      buffer.readBuffer(8);
    } else {
      dataLength = 0;
      textPointerNull = true;
    }
  }
  if (!dataLength && dataLength !== 0) {
    switch (type.id & 0x30) {
      case 0x10:
        dataLength = 0;
        break;
      case 0x20:
        if (metaData.dataLength !== MAX) {
          switch (type.dataLengthLength) {
            case 0:
              dataLength = void 0;
              break;
            case 1:
              dataLength = buffer.readUInt8();
              break;
            case 2:
              dataLength = buffer.readUInt16LE();
              break;
            case 4:
              dataLength = buffer.readUInt32LE();
              break;
            default:
              throw Error("Unsupported dataLengthLength " + type.dataLengthLength + " for data type " + type.name);
          }
        }
        break;
      case 0x30:
        dataLength = 1 << ((type.id & 0x0C) >> 2);
    }
  }
  switch (type.name) {
    case 'Null':
      value = null;
      break;
    case 'TinyInt':
      value = buffer.readUInt8();
      break;
    case 'Int':
      value = buffer.readInt32LE();
      break;
    case 'SmallInt':
      value = buffer.readInt16LE();
      break;
    case 'BigInt':
      value = buffer.readAsStringInt64LE();
      break;
    case 'IntN':
      switch (dataLength) {
        case 0:
          value = null;
          break;
        case 1:
          value = buffer.readUInt8();
          break;
        case 2:
          value = buffer.readInt16LE();
          break;
        case 4:
          value = buffer.readInt32LE();
          break;
        case 8:
          value = buffer.readAsStringInt64LE();
          break;
        default:
          throw new Error("Unsupported dataLength " + dataLength + " for IntN");
      }
      break;
    case 'Real':
      value = buffer.readFloatLE();
      break;
    case 'Float':
      value = buffer.readDoubleLE();
      break;
    case 'FloatN':
      switch (dataLength) {
        case 0:
          value = null;
          break;
        case 4:
          value = buffer.readFloatLE();
          break;
        case 8:
          value = buffer.readDoubleLE();
          break;
        default:
          throw new Error("Unsupported dataLength " + dataLength + " for FloatN");
      }
      break;
    case 'Money':
    case 'SmallMoney':
    case 'MoneyN':
      switch (dataLength) {
        case 0:
          value = null;
          break;
        case 4:
          value = buffer.readInt32LE() / MONEY_DIVISOR;
          break;
        case 8:
          high = buffer.readInt32LE();
          low = buffer.readUInt32LE();
          value = low + (0x100000000 * high);
          value /= MONEY_DIVISOR;
          break;
        default:
          throw new Error("Unsupported dataLength " + dataLength + " for MoneyN");
      }
      break;
    case 'Bit':
      value = !!buffer.readUInt8();
      break;
    case 'BitN':
      switch (dataLength) {
        case 0:
          value = null;
          break;
        case 1:
          value = !!buffer.readUInt8();
      }
      break;
    case 'VarChar':
    case 'Char':
      codepage = metaData.collation.codepage;
      if (metaData.dataLength === MAX) {
        value = readMaxChars(buffer, codepage);
      } else {
        value = readChars(buffer, dataLength, codepage);
      }
      break;
    case 'NVarChar':
    case 'NChar':
      if (metaData.dataLength === MAX) {
        value = readMaxNChars(buffer);
      } else {
        value = readNChars(buffer, dataLength);
      }
      break;
    case 'VarBinary':
    case 'Binary':
      if (metaData.dataLength === MAX) {
        value = readMaxBinary(buffer);
      } else {
        value = readBinary(buffer, dataLength);
      }
      break;
    case 'Text':
      if (textPointerNull) {
        value = null;
      } else {
        value = readChars(buffer, dataLength, metaData.collation.codepage);
      }
      break;
    case 'NText':
      if (textPointerNull) {
        value = null;
      } else {
        value = readNChars(buffer, dataLength);
      }
      break;
    case 'Image':
      if (textPointerNull) {
        value = null;
      } else {
        value = readBinary(buffer, dataLength);
      }
      break;
    case 'Xml':
      value = readMaxNChars(buffer);
      break;
    case 'SmallDateTime':
      value = readSmallDateTime(buffer, options.useUTC);
      break;
    case 'DateTime':
      value = readDateTime(buffer, options.useUTC);
      break;
    case 'DateTimeN':
      switch (dataLength) {
        case 0:
          value = null;
          break;
        case 4:
          value = readSmallDateTime(buffer, options.useUTC);
          break;
        case 8:
          value = readDateTime(buffer, options.useUTC);
      }
      break;
    case 'TimeN':
      if ((dataLength = buffer.readUInt8()) === 0) {
        value = null;
      } else {
        value = readTime(buffer, dataLength, metaData.scale);
      }
      break;
    case 'DateN':
      if ((dataLength = buffer.readUInt8()) === 0) {
        value = null;
      } else {
        value = readDate(buffer);
      }
      break;
    case 'DateTime2N':
      if ((dataLength = buffer.readUInt8()) === 0) {
        value = null;
      } else {
        value = readDateTime2(buffer, dataLength, metaData.scale);
      }
      break;
    case 'DateTimeOffsetN':
      if ((dataLength = buffer.readUInt8()) === 0) {
        value = null;
      } else {
        value = readDateTimeOffset(buffer, dataLength, metaData.scale);
      }
      break;
    case 'NumericN':
    case 'DecimalN':
      if (dataLength === 0) {
        value = null;
      } else {
        sign = buffer.readUInt8() === 1 ? 1 : -1;
        switch (dataLength - 1) {
          case 4:
            value = buffer.readUInt32LE();
            break;
          case 8:
            value = buffer.readUNumeric64LE();
            break;
          case 12:
            value = buffer.readUNumeric96LE();
            break;
          case 16:
            value = buffer.readUNumeric128LE();
            break;
          default:
            throw new Error(sprintf('Unsupported numeric size %d at offset 0x%04X', dataLength - 1, buffer.position));
            break;
        }
        value *= sign;
        value /= Math.pow(10, metaData.scale);
      }
      break;
    case 'UniqueIdentifierN':
      switch (dataLength) {
        case 0:
          value = null;
          break;
        case 0x10:
          value = guidParser.arrayToGuid(buffer.readArray(0x10));
          break;
        default:
          throw new Error(sprintf('Unsupported guid size %d at offset 0x%04X', dataLength - 1, buffer.position));
      }
      break;
    case 'UDT':
      value = readMaxBinary(buffer);
      break;
    default:
      throw new Error(sprintf('Unrecognised type %s at offset 0x%04X', type.name, buffer.position));
      break;
  }
  return value;
};

readBinary = function(buffer, dataLength) {
  if (dataLength === NULL) {
    return null;
  } else {
    return buffer.readBuffer(dataLength);
  }
};

readChars = function(buffer, dataLength, codepage) {
  if (codepage == null) {
    codepage = DEFAULT_ENCODING;
  }
  if (dataLength === NULL) {
    return null;
  } else {
    return iconv.decode(buffer.readBuffer(dataLength), codepage);
  }
};

readNChars = function(buffer, dataLength) {
  if (dataLength === NULL) {
    return null;
  } else {
    return buffer.readString(dataLength, 'ucs2');
  }
};

readMaxBinary = function(buffer) {
  return readMax(buffer, function(valueBuffer) {
    return valueBuffer;
  });
};

readMaxChars = function(buffer, codepage) {
  if (codepage == null) {
    codepage = DEFAULT_ENCODING;
  }
  return readMax(buffer, function(valueBuffer) {
    return iconv.decode(valueBuffer, codepage);
  });
};

readMaxNChars = function(buffer) {
  return readMax(buffer, function(valueBuffer) {
    return valueBuffer.toString('ucs2');
  });
};

readMax = function(buffer, decodeFunction) {
  var chunk, chunkLength, chunks, expectedLength, length, position, type, valueBuffer, _i, _len;
  type = buffer.readBuffer(8);
  if (type.equals(PLP_NULL)) {
    return null;
  } else {
    if (type.equals(UNKNOWN_PLP_LEN)) {
      expectedLength = void 0;
    } else {
      buffer.rollback();
      expectedLength = buffer.readUInt64LE();
    }
    length = 0;
    chunks = [];
    chunkLength = buffer.readUInt32LE();
    while (chunkLength !== 0) {
      length += chunkLength;
      chunks.push(buffer.readBuffer(chunkLength));
      chunkLength = buffer.readUInt32LE();
    }
    if (expectedLength) {
      if (length !== expectedLength) {
        throw new Error("Partially Length-prefixed Bytes unmatched lengths : expected " + expectedLength + ", but got " + length + " bytes");
      }
    }
    valueBuffer = new Buffer(length);
    position = 0;
    for (_i = 0, _len = chunks.length; _i < _len; _i++) {
      chunk = chunks[_i];
      chunk.copy(valueBuffer, position, 0);
      position += chunk.length;
    }
    return decodeFunction(valueBuffer);
  }
};

readSmallDateTime = function(buffer, useUTC) {
  var days, minutes, value;
  days = buffer.readUInt16LE();
  minutes = buffer.readUInt16LE();
  if (useUTC) {
    value = new Date(Date.UTC(1900, 0, 1));
    value.setUTCDate(value.getUTCDate() + days);
    value.setUTCMinutes(value.getUTCMinutes() + minutes);
  } else {
    value = new Date(1900, 0, 1);
    value.setDate(value.getDate() + days);
    value.setMinutes(value.getMinutes() + minutes);
  }
  return value;
};

readDateTime = function(buffer, useUTC) {
  var days, milliseconds, threeHundredthsOfSecond, value;
  days = buffer.readInt32LE();
  threeHundredthsOfSecond = buffer.readUInt32LE();
  milliseconds = threeHundredthsOfSecond * THREE_AND_A_THIRD;
  if (useUTC) {
    value = new Date(Date.UTC(1900, 0, 1));
    value.setUTCDate(value.getUTCDate() + days);
    value.setUTCMilliseconds(value.getUTCMilliseconds() + milliseconds);
  } else {
    value = new Date(1900, 0, 1);
    value.setDate(value.getDate() + days);
    value.setMilliseconds(value.getMilliseconds() + milliseconds);
  }
  return value;
};

readTime = function(buffer, dataLength, scale) {
  var date, i, value, _i, _ref;
  switch (dataLength) {
    case 3:
      value = buffer.readUInt24LE();
      break;
    case 4:
      value = buffer.readUInt32LE();
      break;
    case 5:
      value = buffer.readUInt40LE();
  }
  if (scale < 7) {
    for (i = _i = _ref = scale + 1; _ref <= 7 ? _i <= 7 : _i >= 7; i = _ref <= 7 ? ++_i : --_i) {
      value *= 10;
    }
  }
  date = new Date(Date.UTC(1970, 0, 1, 0, 0, 0, value / 10000));
  Object.defineProperty(date, "nanosecondsDelta", {
    enumerable: false,
    value: (value % 10000) / Math.pow(10, 7)
  });
  return date;
};

readDate = function(buffer) {
  var days;
  days = buffer.readUInt24LE();
  return new Date(Date.UTC(2000, 0, days - 730118));
};

readDateTime2 = function(buffer, dataLength, scale) {
  var date, days, time;
  time = readTime(buffer, dataLength - 3, scale);
  days = buffer.readUInt24LE();
  date = new Date(Date.UTC(2000, 0, days - 730118, 0, 0, 0, +time));
  Object.defineProperty(date, "nanosecondsDelta", {
    enumerable: false,
    value: time.nanosecondsDelta
  });
  return date;
};

readDateTimeOffset = function(buffer, dataLength, scale) {
  var date, days, offset, time;
  time = readTime(buffer, dataLength - 5, scale);
  days = buffer.readUInt24LE();
  offset = buffer.readInt16LE();
  date = new Date(Date.UTC(2000, 0, days - 730118, 0, 0, 0, +time));
  Object.defineProperty(date, "nanosecondsDelta", {
    enumerable: false,
    value: time.nanosecondsDelta
  });
  return date;
};

module.exports = parse;
