//  SPDX-License-Identifier: LGPL-2.1-or-later
//  Copyright (c) 2015-2024 MariaDB Corporation Ab

'use strict';

const FieldType = require('../../const/field-type');
const Errors = require('../../misc/errors');

module.exports.parser = function (col, opts) {
  // set reader function read(col, packet, index, nullBitmap, opts, throwUnexpectedError)
  // this permit for multi-row result-set to avoid resolving type parsing each data.

  // return constant parser (function not depending on column info other than type)
  const defaultParser = DEFAULT_PARSER_TYPE[col.columnType];
  if (defaultParser) return defaultParser;

  // parser depending on column info
  switch (col.columnType) {
    case FieldType.DECIMAL:
    case FieldType.NEWDECIMAL:
      return col.scale === 0 ? readDecimalAsIntLengthCoded : readDecimalLengthCoded;

    case FieldType.BIGINT:
      if (opts.bigIntAsNumber || opts.supportBigNumbers) return readBigIntAsNumberLengthCoded;
      return readBigIntLengthCoded;

    case FieldType.GEOMETRY:
      let defaultVal = col.__getDefaultGeomVal();
      return readGeometry.bind(null, defaultVal);

    case FieldType.BIT:
      if (col.columnLength === 1 && opts.bitOneIsBoolean) {
        return readBitAsBoolean;
      }
      return readBufferLengthEncoded;

    default:
      if (col.dataTypeFormat && col.dataTypeFormat === 'json' && opts.autoJsonMap) {
        return readJson;
      }
      if (col.collation.index === 63) {
        return readBufferLengthEncoded;
      }
      if (col.isSet()) {
        return readSet;
      }
      return readStringLengthEncoded;
  }
};

module.exports.castWrapper = function (column, packet, opts, nullBitmap, index) {
  column.string = () => packet.readStringLengthEncoded();
  column.buffer = () => packet.readBufferLengthEncoded();
  column.float = () => packet.readFloatLengthCoded();
  column.tiny = () => packet.readIntLengthEncoded();
  column.short = () => packet.readIntLengthEncoded();
  column.int = () => packet.readIntLengthEncoded();
  column.long = () => packet.readBigIntLengthEncoded();
  column.decimal = () => packet.readDecimalLengthEncoded();
  column.date = () => packet.readDate(opts);
  column.datetime = () => packet.readDateTime();

  column.geometry = () => {
    let defaultVal = null;
    if (column.dataTypeName) {
      switch (column.dataTypeName) {
        case 'point':
          defaultVal = { type: 'Point' };
          break;
        case 'linestring':
          defaultVal = { type: 'LineString' };
          break;
        case 'polygon':
          defaultVal = { type: 'Polygon' };
          break;
        case 'multipoint':
          defaultVal = { type: 'MultiPoint' };
          break;
        case 'multilinestring':
          defaultVal = { type: 'MultiLineString' };
          break;
        case 'multipolygon':
          defaultVal = { type: 'MultiPolygon' };
          break;
        default:
          defaultVal = { type: column.dataTypeName };
          break;
      }
    }

    return packet.readGeometry(defaultVal);
  };
};
const readGeometry = (defaultVal, packet, opts, throwUnexpectedError) => packet.readGeometry(defaultVal);

const readIntLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readIntLengthEncoded();
const readStringLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readStringLengthEncoded();
const readFloatLengthCoded = (packet, opts, throwUnexpectedError) => packet.readFloatLengthCoded();
const readBigIntLengthCoded = (packet, opts, throwUnexpectedError) => packet.readBigIntLengthEncoded();

const readBigIntAsNumberLengthCoded = (packet, opts, throwUnexpectedError) => {
  const len = packet.readUnsignedLength();
  if (len === null) return null;
  if (len < 16) {
    const val = packet._atoi(len);
    if (opts.supportBigNumbers && opts.bigNumberStrings) {
      return `${val}`;
    }
    return val;
  }

  const val = packet.readBigIntFromLen(len);
  if (opts.bigIntAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(val))) {
    return throwUnexpectedError(
      `value ${val} can't safely be converted to number`,
      false,
      null,
      '42000',
      Errors.ER_PARSING_PRECISION
    );
  }
  if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(val)))) {
    return val.toString();
  }
  return Number(val);
};

const readDecimalAsIntLengthCoded = (packet, opts, throwUnexpectedError) => {
  const valDec = packet.readDecimalLengthEncoded();
  if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) {
    if (opts.decimalAsNumber && opts.checkNumberRange && !Number.isSafeInteger(Number(valDec))) {
      return throwUnexpectedError(
        `value ${valDec} can't safely be converted to number`,
        false,
        null,
        '42000',
        Errors.ER_PARSING_PRECISION
      );
    }
    if (opts.supportBigNumbers && (opts.bigNumberStrings || !Number.isSafeInteger(Number(valDec)))) {
      return valDec;
    }
    return Number(valDec);
  }
  return valDec;
};
const readDecimalLengthCoded = (packet, opts, throwUnexpectedError) => {
  const valDec = packet.readDecimalLengthEncoded();
  if (valDec != null && (opts.decimalAsNumber || opts.supportBigNumbers)) {
    const numberValue = Number(valDec);
    if (
      opts.supportBigNumbers &&
      (opts.bigNumberStrings || (Number.isInteger(numberValue) && !Number.isSafeInteger(numberValue)))
    ) {
      return valDec;
    }
    return numberValue;
  }
  return valDec;
};
const readDate = (packet, opts, throwUnexpectedError) => {
  if (opts.dateStrings) {
    return packet.readAsciiStringLengthEncoded();
  }
  return packet.readDate();
};
const readTimestamp = (packet, opts, throwUnexpectedError) => {
  if (opts.dateStrings) {
    return packet.readAsciiStringLengthEncoded();
  }
  return packet.readDateTime();
};
const readAsciiStringLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readAsciiStringLengthEncoded();
const readBitAsBoolean = (packet, opts, throwUnexpectedError) => {
  const val = packet.readBufferLengthEncoded();
  return val == null ? null : val[0] === 1;
};
const readBufferLengthEncoded = (packet, opts, throwUnexpectedError) => packet.readBufferLengthEncoded();
const readJson = (packet, opts, throwUnexpectedError) => JSON.parse(packet.readStringLengthEncoded());
const readSet = (packet, opts, throwUnexpectedError) => {
  const string = packet.readStringLengthEncoded();
  return string == null ? null : string === '' ? [] : string.split(',');
};

const DEFAULT_PARSER_TYPE = Array(256);
DEFAULT_PARSER_TYPE[FieldType.TINY] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.SHORT] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.INT] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.INT24] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.YEAR] = readIntLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.FLOAT] = readFloatLengthCoded;
DEFAULT_PARSER_TYPE[FieldType.DOUBLE] = readFloatLengthCoded;
DEFAULT_PARSER_TYPE[FieldType.DATE] = readDate;
DEFAULT_PARSER_TYPE[FieldType.DATETIME] = readTimestamp;
DEFAULT_PARSER_TYPE[FieldType.TIMESTAMP] = readTimestamp;
DEFAULT_PARSER_TYPE[FieldType.TIME] = readAsciiStringLengthEncoded;
DEFAULT_PARSER_TYPE[FieldType.JSON] = readJson;
