"use strict";

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.isPLPStream = isPLPStream;
exports.readPLPStream = readPLPStream;
exports.readValue = readValue;
var _metadataParser = require("./metadata-parser");
var _dataType = require("./data-type");
var _iconvLite = _interopRequireDefault(require("iconv-lite"));
var _sprintfJs = require("sprintf-js");
var _guidParser = require("./guid-parser");
var _helpers = require("./token/helpers");
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
const NULL = (1 << 16) - 1;
const MAX = (1 << 16) - 1;
const THREE_AND_A_THIRD = 3 + 1 / 3;
const MONEY_DIVISOR = 10000;
const PLP_NULL = 0xFFFFFFFFFFFFFFFFn;
const UNKNOWN_PLP_LEN = 0xFFFFFFFFFFFFFFFEn;
const DEFAULT_ENCODING = 'utf8';
function readTinyInt(buf, offset) {
  return (0, _helpers.readUInt8)(buf, offset);
}
function readSmallInt(buf, offset) {
  return (0, _helpers.readInt16LE)(buf, offset);
}
function readInt(buf, offset) {
  return (0, _helpers.readInt32LE)(buf, offset);
}
function readBigInt(buf, offset) {
  let value;
  ({
    offset,
    value
  } = (0, _helpers.readBigInt64LE)(buf, offset));
  return new _helpers.Result(value.toString(), offset);
}
function readReal(buf, offset) {
  return (0, _helpers.readFloatLE)(buf, offset);
}
function readFloat(buf, offset) {
  return (0, _helpers.readDoubleLE)(buf, offset);
}
function readSmallMoney(buf, offset) {
  let value;
  ({
    offset,
    value
  } = (0, _helpers.readInt32LE)(buf, offset));
  return new _helpers.Result(value / MONEY_DIVISOR, offset);
}
function readMoney(buf, offset) {
  let high;
  ({
    offset,
    value: high
  } = (0, _helpers.readInt32LE)(buf, offset));
  let low;
  ({
    offset,
    value: low
  } = (0, _helpers.readUInt32LE)(buf, offset));
  return new _helpers.Result((low + 0x100000000 * high) / MONEY_DIVISOR, offset);
}
function readBit(buf, offset) {
  let value;
  ({
    offset,
    value
  } = (0, _helpers.readUInt8)(buf, offset));
  return new _helpers.Result(!!value, offset);
}
function readValue(buf, offset, metadata, options) {
  const type = metadata.type;
  switch (type.name) {
    case 'Null':
      return new _helpers.Result(null, offset);
    case 'TinyInt':
      {
        return readTinyInt(buf, offset);
      }
    case 'SmallInt':
      {
        return readSmallInt(buf, offset);
      }
    case 'Int':
      {
        return readInt(buf, offset);
      }
    case 'BigInt':
      {
        return readBigInt(buf, offset);
      }
    case 'IntN':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        switch (dataLength) {
          case 0:
            return new _helpers.Result(null, offset);
          case 1:
            return readTinyInt(buf, offset);
          case 2:
            return readSmallInt(buf, offset);
          case 4:
            return readInt(buf, offset);
          case 8:
            return readBigInt(buf, offset);
          default:
            throw new Error('Unsupported dataLength ' + dataLength + ' for IntN');
        }
      }
    case 'Real':
      {
        return readReal(buf, offset);
      }
    case 'Float':
      {
        return readFloat(buf, offset);
      }
    case 'FloatN':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        switch (dataLength) {
          case 0:
            return new _helpers.Result(null, offset);
          case 4:
            return readReal(buf, offset);
          case 8:
            return readFloat(buf, offset);
          default:
            throw new Error('Unsupported dataLength ' + dataLength + ' for FloatN');
        }
      }
    case 'SmallMoney':
      {
        return readSmallMoney(buf, offset);
      }
    case 'Money':
      return readMoney(buf, offset);
    case 'MoneyN':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        switch (dataLength) {
          case 0:
            return new _helpers.Result(null, offset);
          case 4:
            return readSmallMoney(buf, offset);
          case 8:
            return readMoney(buf, offset);
          default:
            throw new Error('Unsupported dataLength ' + dataLength + ' for MoneyN');
        }
      }
    case 'Bit':
      {
        return readBit(buf, offset);
      }
    case 'BitN':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        switch (dataLength) {
          case 0:
            return new _helpers.Result(null, offset);
          case 1:
            return readBit(buf, offset);
          default:
            throw new Error('Unsupported dataLength ' + dataLength + ' for BitN');
        }
      }
    case 'VarChar':
    case 'Char':
      {
        const codepage = metadata.collation.codepage;
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt16LE)(buf, offset));
        if (dataLength === NULL) {
          return new _helpers.Result(null, offset);
        }
        return readChars(buf, offset, dataLength, codepage);
      }
    case 'NVarChar':
    case 'NChar':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt16LE)(buf, offset));
        if (dataLength === NULL) {
          return new _helpers.Result(null, offset);
        }
        return readNChars(buf, offset, dataLength);
      }
    case 'VarBinary':
    case 'Binary':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt16LE)(buf, offset));
        if (dataLength === NULL) {
          return new _helpers.Result(null, offset);
        }
        return readBinary(buf, offset, dataLength);
      }
    case 'Text':
      {
        let textPointerLength;
        ({
          offset,
          value: textPointerLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (textPointerLength === 0) {
          return new _helpers.Result(null, offset);
        }

        // Textpointer
        ({
          offset
        } = readBinary(buf, offset, textPointerLength));

        // Timestamp
        ({
          offset
        } = readBinary(buf, offset, 8));
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt32LE)(buf, offset));
        return readChars(buf, offset, dataLength, metadata.collation.codepage);
      }
    case 'NText':
      {
        let textPointerLength;
        ({
          offset,
          value: textPointerLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (textPointerLength === 0) {
          return new _helpers.Result(null, offset);
        }

        // Textpointer
        ({
          offset
        } = readBinary(buf, offset, textPointerLength));

        // Timestamp
        ({
          offset
        } = readBinary(buf, offset, 8));
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt32LE)(buf, offset));
        return readNChars(buf, offset, dataLength);
      }
    case 'Image':
      {
        let textPointerLength;
        ({
          offset,
          value: textPointerLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (textPointerLength === 0) {
          return new _helpers.Result(null, offset);
        }

        // Textpointer
        ({
          offset
        } = readBinary(buf, offset, textPointerLength));

        // Timestamp
        ({
          offset
        } = readBinary(buf, offset, 8));
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt32LE)(buf, offset));
        return readBinary(buf, offset, dataLength);
      }
    case 'SmallDateTime':
      {
        return readSmallDateTime(buf, offset, options.useUTC);
      }
    case 'DateTime':
      {
        return readDateTime(buf, offset, options.useUTC);
      }
    case 'DateTimeN':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        switch (dataLength) {
          case 0:
            return new _helpers.Result(null, offset);
          case 4:
            return readSmallDateTime(buf, offset, options.useUTC);
          case 8:
            return readDateTime(buf, offset, options.useUTC);
          default:
            throw new Error('Unsupported dataLength ' + dataLength + ' for DateTimeN');
        }
      }
    case 'Time':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (dataLength === 0) {
          return new _helpers.Result(null, offset);
        }
        return readTime(buf, offset, dataLength, metadata.scale, options.useUTC);
      }
    case 'Date':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (dataLength === 0) {
          return new _helpers.Result(null, offset);
        }
        return readDate(buf, offset, options.useUTC);
      }
    case 'DateTime2':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (dataLength === 0) {
          return new _helpers.Result(null, offset);
        }
        return readDateTime2(buf, offset, dataLength, metadata.scale, options.useUTC);
      }
    case 'DateTimeOffset':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (dataLength === 0) {
          return new _helpers.Result(null, offset);
        }
        return readDateTimeOffset(buf, offset, dataLength, metadata.scale);
      }
    case 'NumericN':
    case 'DecimalN':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        if (dataLength === 0) {
          return new _helpers.Result(null, offset);
        }
        return readNumeric(buf, offset, dataLength, metadata.precision, metadata.scale);
      }
    case 'UniqueIdentifier':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt8)(buf, offset));
        switch (dataLength) {
          case 0:
            return new _helpers.Result(null, offset);
          case 0x10:
            return readUniqueIdentifier(buf, offset, options);
          default:
            throw new Error((0, _sprintfJs.sprintf)('Unsupported guid size %d', dataLength - 1));
        }
      }
    case 'Variant':
      {
        let dataLength;
        ({
          offset,
          value: dataLength
        } = (0, _helpers.readUInt32LE)(buf, offset));
        if (dataLength === 0) {
          return new _helpers.Result(null, offset);
        }
        return readVariant(buf, offset, options, dataLength);
      }
    default:
      {
        throw new Error('Invalid type!');
      }
  }
}
function isPLPStream(metadata) {
  switch (metadata.type.name) {
    case 'VarChar':
    case 'NVarChar':
    case 'VarBinary':
      {
        return metadata.dataLength === MAX;
      }
    case 'Xml':
      {
        return true;
      }
    case 'UDT':
      {
        return true;
      }
  }
}
function readUniqueIdentifier(buf, offset, options) {
  let data;
  ({
    value: data,
    offset
  } = readBinary(buf, offset, 0x10));
  return new _helpers.Result(options.lowerCaseGuids ? (0, _guidParser.bufferToLowerCaseGuid)(data) : (0, _guidParser.bufferToUpperCaseGuid)(data), offset);
}
function readNumeric(buf, offset, dataLength, _precision, scale) {
  let sign;
  ({
    offset,
    value: sign
  } = (0, _helpers.readUInt8)(buf, offset));
  sign = sign === 1 ? 1 : -1;
  let value;
  if (dataLength === 5) {
    ({
      offset,
      value
    } = (0, _helpers.readUInt32LE)(buf, offset));
  } else if (dataLength === 9) {
    ({
      offset,
      value
    } = (0, _helpers.readUNumeric64LE)(buf, offset));
  } else if (dataLength === 13) {
    ({
      offset,
      value
    } = (0, _helpers.readUNumeric96LE)(buf, offset));
  } else if (dataLength === 17) {
    ({
      offset,
      value
    } = (0, _helpers.readUNumeric128LE)(buf, offset));
  } else {
    throw new Error((0, _sprintfJs.sprintf)('Unsupported numeric dataLength %d', dataLength));
  }
  return new _helpers.Result(value * sign / Math.pow(10, scale), offset);
}
function readVariant(buf, offset, options, dataLength) {
  let baseType;
  ({
    value: baseType,
    offset
  } = (0, _helpers.readUInt8)(buf, offset));
  const type = _dataType.TYPE[baseType];
  let propBytes;
  ({
    value: propBytes,
    offset
  } = (0, _helpers.readUInt8)(buf, offset));
  dataLength = dataLength - propBytes - 2;
  switch (type.name) {
    case 'UniqueIdentifier':
      return readUniqueIdentifier(buf, offset, options);
    case 'Bit':
      return readBit(buf, offset);
    case 'TinyInt':
      return readTinyInt(buf, offset);
    case 'SmallInt':
      return readSmallInt(buf, offset);
    case 'Int':
      return readInt(buf, offset);
    case 'BigInt':
      return readBigInt(buf, offset);
    case 'SmallDateTime':
      return readSmallDateTime(buf, offset, options.useUTC);
    case 'DateTime':
      return readDateTime(buf, offset, options.useUTC);
    case 'Real':
      return readReal(buf, offset);
    case 'Float':
      return readFloat(buf, offset);
    case 'SmallMoney':
      return readSmallMoney(buf, offset);
    case 'Money':
      return readMoney(buf, offset);
    case 'Date':
      return readDate(buf, offset, options.useUTC);
    case 'Time':
      {
        let scale;
        ({
          value: scale,
          offset
        } = (0, _helpers.readUInt8)(buf, offset));
        return readTime(buf, offset, dataLength, scale, options.useUTC);
      }
    case 'DateTime2':
      {
        let scale;
        ({
          value: scale,
          offset
        } = (0, _helpers.readUInt8)(buf, offset));
        return readDateTime2(buf, offset, dataLength, scale, options.useUTC);
      }
    case 'DateTimeOffset':
      {
        let scale;
        ({
          value: scale,
          offset
        } = (0, _helpers.readUInt8)(buf, offset));
        return readDateTimeOffset(buf, offset, dataLength, scale);
      }
    case 'VarBinary':
    case 'Binary':
      {
        // maxLength (unused?)
        ({
          offset
        } = (0, _helpers.readUInt16LE)(buf, offset));
        return readBinary(buf, offset, dataLength);
      }
    case 'NumericN':
    case 'DecimalN':
      {
        let precision;
        ({
          value: precision,
          offset
        } = (0, _helpers.readUInt8)(buf, offset));
        let scale;
        ({
          value: scale,
          offset
        } = (0, _helpers.readUInt8)(buf, offset));
        return readNumeric(buf, offset, dataLength, precision, scale);
      }
    case 'VarChar':
    case 'Char':
      {
        // maxLength (unused?)
        ({
          offset
        } = (0, _helpers.readUInt16LE)(buf, offset));
        let collation;
        ({
          value: collation,
          offset
        } = (0, _metadataParser.readCollation)(buf, offset));
        return readChars(buf, offset, dataLength, collation.codepage);
      }
    case 'NVarChar':
    case 'NChar':
      {
        // maxLength (unused?)
        ({
          offset
        } = (0, _helpers.readUInt16LE)(buf, offset));

        // collation (unused?)
        ({
          offset
        } = (0, _metadataParser.readCollation)(buf, offset));
        return readNChars(buf, offset, dataLength);
      }
    default:
      throw new Error('Invalid type!');
  }
}
function readBinary(buf, offset, dataLength) {
  if (buf.length < offset + dataLength) {
    throw new _helpers.NotEnoughDataError(offset + dataLength);
  }
  return new _helpers.Result(buf.slice(offset, offset + dataLength), offset + dataLength);
}
function readChars(buf, offset, dataLength, codepage) {
  if (buf.length < offset + dataLength) {
    throw new _helpers.NotEnoughDataError(offset + dataLength);
  }
  return new _helpers.Result(_iconvLite.default.decode(buf.slice(offset, offset + dataLength), codepage ?? DEFAULT_ENCODING), offset + dataLength);
}
function readNChars(buf, offset, dataLength) {
  if (buf.length < offset + dataLength) {
    throw new _helpers.NotEnoughDataError(offset + dataLength);
  }
  return new _helpers.Result(buf.toString('ucs2', offset, offset + dataLength), offset + dataLength);
}
async function readPLPStream(parser) {
  while (parser.buffer.length < parser.position + 8) {
    await parser.waitForChunk();
  }
  const expectedLength = parser.buffer.readBigUInt64LE(parser.position);
  parser.position += 8;
  if (expectedLength === PLP_NULL) {
    return null;
  }
  const chunks = [];
  let currentLength = 0;
  while (true) {
    while (parser.buffer.length < parser.position + 4) {
      await parser.waitForChunk();
    }
    const chunkLength = parser.buffer.readUInt32LE(parser.position);
    parser.position += 4;
    if (!chunkLength) {
      break;
    }
    while (parser.buffer.length < parser.position + chunkLength) {
      await parser.waitForChunk();
    }
    chunks.push(parser.buffer.slice(parser.position, parser.position + chunkLength));
    parser.position += chunkLength;
    currentLength += chunkLength;
  }
  if (expectedLength !== UNKNOWN_PLP_LEN) {
    if (currentLength !== Number(expectedLength)) {
      throw new Error('Partially Length-prefixed Bytes unmatched lengths : expected ' + expectedLength + ', but got ' + currentLength + ' bytes');
    }
  }
  return chunks;
}
function readSmallDateTime(buf, offset, useUTC) {
  let days;
  ({
    offset,
    value: days
  } = (0, _helpers.readUInt16LE)(buf, offset));
  let minutes;
  ({
    offset,
    value: minutes
  } = (0, _helpers.readUInt16LE)(buf, offset));
  let value;
  if (useUTC) {
    value = new Date(Date.UTC(1900, 0, 1 + days, 0, minutes));
  } else {
    value = new Date(1900, 0, 1 + days, 0, minutes);
  }
  return new _helpers.Result(value, offset);
}
function readDateTime(buf, offset, useUTC) {
  let days;
  ({
    offset,
    value: days
  } = (0, _helpers.readInt32LE)(buf, offset));
  let threeHundredthsOfSecond;
  ({
    offset,
    value: threeHundredthsOfSecond
  } = (0, _helpers.readInt32LE)(buf, offset));
  const milliseconds = Math.round(threeHundredthsOfSecond * THREE_AND_A_THIRD);
  let value;
  if (useUTC) {
    value = new Date(Date.UTC(1900, 0, 1 + days, 0, 0, 0, milliseconds));
  } else {
    value = new Date(1900, 0, 1 + days, 0, 0, 0, milliseconds);
  }
  return new _helpers.Result(value, offset);
}
function readTime(buf, offset, dataLength, scale, useUTC) {
  let value;
  switch (dataLength) {
    case 3:
      {
        ({
          value,
          offset
        } = (0, _helpers.readUInt24LE)(buf, offset));
        break;
      }
    case 4:
      {
        ({
          value,
          offset
        } = (0, _helpers.readUInt32LE)(buf, offset));
        break;
      }
    case 5:
      {
        ({
          value,
          offset
        } = (0, _helpers.readUInt40LE)(buf, offset));
        break;
      }
    default:
      {
        throw new Error('unreachable');
      }
  }
  if (scale < 7) {
    for (let i = scale; i < 7; i++) {
      value *= 10;
    }
  }
  let date;
  if (useUTC) {
    date = new Date(Date.UTC(1970, 0, 1, 0, 0, 0, value / 10000));
  } else {
    date = new Date(1970, 0, 1, 0, 0, 0, value / 10000);
  }
  Object.defineProperty(date, 'nanosecondsDelta', {
    enumerable: false,
    value: value % 10000 / Math.pow(10, 7)
  });
  return new _helpers.Result(date, offset);
}
function readDate(buf, offset, useUTC) {
  let days;
  ({
    offset,
    value: days
  } = (0, _helpers.readUInt24LE)(buf, offset));
  if (useUTC) {
    return new _helpers.Result(new Date(Date.UTC(2000, 0, days - 730118)), offset);
  } else {
    return new _helpers.Result(new Date(2000, 0, days - 730118), offset);
  }
}
function readDateTime2(buf, offset, dataLength, scale, useUTC) {
  let time;
  ({
    offset,
    value: time
  } = readTime(buf, offset, dataLength - 3, scale, useUTC));
  let days;
  ({
    offset,
    value: days
  } = (0, _helpers.readUInt24LE)(buf, offset));
  let date;
  if (useUTC) {
    date = new Date(Date.UTC(2000, 0, days - 730118, 0, 0, 0, +time));
  } else {
    date = new Date(2000, 0, days - 730118, time.getHours(), time.getMinutes(), time.getSeconds(), time.getMilliseconds());
  }
  Object.defineProperty(date, 'nanosecondsDelta', {
    enumerable: false,
    value: time.nanosecondsDelta
  });
  return new _helpers.Result(date, offset);
}
function readDateTimeOffset(buf, offset, dataLength, scale) {
  let time;
  ({
    offset,
    value: time
  } = readTime(buf, offset, dataLength - 5, scale, true));
  let days;
  ({
    offset,
    value: days
  } = (0, _helpers.readUInt24LE)(buf, offset));

  // time offset?
  ({
    offset
  } = (0, _helpers.readUInt16LE)(buf, offset));
  const date = new Date(Date.UTC(2000, 0, days - 730118, 0, 0, 0, +time));
  Object.defineProperty(date, 'nanosecondsDelta', {
    enumerable: false,
    value: time.nanosecondsDelta
  });
  return new _helpers.Result(date, offset);
}
module.exports.readValue = readValue;
module.exports.isPLPStream = isPLPStream;
module.exports.readPLPStream = readPLPStream;
//# sourceMappingURL=data:application/json;charset=utf-8;base64,[encoded data, 59168 characters]