// @ts-nocheck
import { CramMalformedError } from '../errors'
import D04 from './d04'
import D14 from './d14'
import Decoding from './decoding'
import { readStatsO0, readStatsO1 } from './frequencies'

// const /* int */ ORDER_BYTE_LENGTH = 1
// const /* int */ COMPRESSED_BYTE_LENGTH = 4
const /* int */ RAW_BYTE_LENGTH = 4
// const /* int */ PREFIX_BYTE_LENGTH =
//   ORDER_BYTE_LENGTH + COMPRESSED_BYTE_LENGTH + RAW_BYTE_LENGTH

// enum ORDER {
//     ZERO, ONE;

//     static ORDER fromInt(const /* int */ value) {
//         try {
//             return ORDER.values()[value];
//         } catch (const ArrayIndexOutOfBoundsException e) {
//             throw new RuntimeException("Unknown rANS order: " + value);
//         }
//     }
// }

// static ByteBuffer compress(const ByteBuffer input, const ORDER order, const ByteBuffer out) {
//     if (input.remaining() == 0)
//         return EMPTY_BUFFER;

//     if (input.remaining() < 4)
//         return encode_order0_way4(input, out);

//     switch (order) {
//         case ZERO:
//             return encode_order0_way4(input, out);
//         case ONE:
//             return encode_order1_way4(input, out);

//         default:
//             throw new RuntimeException("Unknown rANS order: " + order);
//     }
// }

// static /* ByteBuffer */ allocateIfNeeded(/* const int */ in_size,
//                                            /* const ByteBuffer */ out_buf) {
//     const /* int */ compressedSize = (/* int */) (1.05 * in_size + 257 * 257 * 3 + 4);
//     if (out_buf == null)
//         return ByteBuffer.allocate(compressedSize);
//     if (out_buf.remaining() < compressedSize)
//         throw new RuntimeException("Insufficient buffer size.");
//     out_buf.order(ByteOrder.LITTLE_ENDIAN);
//     return out_buf;
// }

// static ByteBuffer encode_order0_way4(const ByteBuffer input,
//                                              ByteBuffer out_buf) {
//     const /* int */ in_size = input.remaining();
//     out_buf = allocateIfNeeded(in_size, out_buf);
//     const /* int */ freqTableStart = PREFIX_BYTE_LENGTH;
//     out_buf.position(freqTableStart);

//     const /* int */[] F = Frequencies.calcFrequencies_o0(in);
//     const RansEncSymbol[] syms = Frequencies.buildSyms_o0(F);

//     const ByteBuffer cp = out_buf.slice();
//     const /* int */ frequencyTable_size = Frequencies.writeFrequencies_o0(cp, F);

//     input.rewind();
//     const /* int */ compressedBlob_size = E04.compress(input, syms, cp);

//     finalizeCompressed(0, out_buf, in_size, frequencyTable_size,
//             compressedBlob_size);
//     return out_buf;
// }

// static ByteBuffer encode_order1_way4(const ByteBuffer input,
//                                              ByteBuffer out_buf) {
//     const /* int */ in_size = input.remaining();
//     out_buf = allocateIfNeeded(in_size, out_buf);
//     const /* int */ freqTableStart = PREFIX_BYTE_LENGTH;
//     out_buf.position(freqTableStart);

//     const /* int */[][] F = Frequencies.calcFrequencies_o1(in);
//     const RansEncSymbol[][] syms = Frequencies.buildSyms_o1(F);

//     const ByteBuffer cp = out_buf.slice();
//     const /* int */ frequencyTable_size = Frequencies.writeFrequencies_o1(cp, F);

//     input.rewind();
//     const /* int */ compressedBlob_size = E14.compress(input, syms, cp);

//     finalizeCompressed(1, out_buf, in_size, frequencyTable_size,
//             compressedBlob_size);
//     return out_buf;
// }

// static void finalizeCompressed(const /* int */ order, const ByteBuffer out_buf,
//                                        const /* int */ in_size, const /* int */ frequencyTable_size, const /* int */ compressedBlob_size) {
//     out_buf.limit(PREFIX_BYTE_LENGTH + frequencyTable_size
//             + compressedBlob_size);
//     out_buf.put(0, (byte) order);
//     out_buf.order(ByteOrder.LITTLE_ENDIAN);
//     const /* int */ compressedSizeOffset = ORDER_BYTE_LENGTH;
//     out_buf.putInt(compressedSizeOffset, frequencyTable_size
//             + compressedBlob_size);
//     const /* int */ rawSizeOffset = ORDER_BYTE_LENGTH + COMPRESSED_BYTE_LENGTH;
//     out_buf.putInt(rawSizeOffset, in_size);
//     out_buf.rewind();
// }

function /* static ByteBuffer */ uncompressOrder0Way4(
  /* const ByteBuffer  */ input,
  /* const ByteBuffer  */ out,
) {
  // input.order(ByteOrder.LITTLE_ENDIAN);
  const D = new Decoding.AriDecoder()
  const syms = new Array(256)
  for (let i = 0; i < syms.length; i += 1) {
    syms[i] = new Decoding.DecodingSymbol()
  }

  readStatsO0(input, D, syms)

  D04(input, D, syms, out)

  return out
}

function /* static ByteBuffer */ uncompressOrder1Way4(
  /* const ByteBuffer */ input,
  /* const ByteBuffer */ output,
) {
  const D = new Array(256)
  for (let i = 0; i < D.length; i += 1) {
    D[i] = new Decoding.AriDecoder()
  }
  const /* Decoding.RansDecSymbol[][]  */ syms = new Array(256)
  for (let i = 0; i < syms.length; i += 1) {
    syms[i] = new Array(256)
    for (let j = 0; j < syms[i].length; j += 1) {
      syms[i][j] = new Decoding.DecodingSymbol()
    }
  }
  readStatsO1(input, D, syms)

  D14(input, output, D, syms)

  return output
}

/* compat layer to make a node buffer act like a java ByteBuffer */
class ByteBuffer {
  constructor(nodeBuffer, initialInputPosition = 0) {
    this._buffer = nodeBuffer
    this._dataView = new DataView(nodeBuffer.buffer)
    this._position = initialInputPosition
    this.length = nodeBuffer.length
  }

  get() {
    const b = this._buffer[this._position]
    this._position += 1
    return b
  }

  getByte() {
    return this.get()
  }

  getByteAt(position) {
    return this._buffer[position]
  }

  position() {
    return this._position
  }

  put(val) {
    this._buffer[this._position] = val
    this._position += 1
    return val
  }

  putAt(position, val) {
    this._buffer[position] = val
    return val
  }

  setPosition(pos) {
    this._position = pos
    return pos
  }

  getInt() {
    const i = this._dataView.getInt32(this._position, true)
    this._position += 4
    return i
  }

  remaining() {
    return this._buffer.length - this._position
  }
}

// static /* const */ ByteBuffer EMPTY_BUFFER = ByteBuffer.allocate(0);
export default function uncompress(
  inputBuffer,
  outputBuffer,
  initialInputPosition = 0,
) {
  if (inputBuffer.length === 0) {
    outputBuffer.fill(0)
    return outputBuffer
  }

  const input = new ByteBuffer(inputBuffer, initialInputPosition)
  // input.order(ByteOrder.LITTLE_ENDIAN);

  const order = input.get()
  if (order !== 0 && order !== 1) {
    throw new CramMalformedError(`Invalid rANS order ${order}`)
  }

  const /* int */ inputSize = input.getInt()
  if (inputSize !== input.remaining() - RAW_BYTE_LENGTH) {
    throw new CramMalformedError('Incorrect input length.')
  }

  const /* int */ outputSize = input.getInt()
  const output = new ByteBuffer(outputBuffer || new Uint8Array(outputSize))
  // TODO output.limit(outputSize)

  if (output.length < outputSize) {
    throw new CramMalformedError(
      `Output buffer too small to fit ${outputSize} bytes.`,
    )
  }

  switch (order) {
    case 0:
      return uncompressOrder0Way4(input, output)

    case 1:
      return uncompressOrder1Way4(input, output)

    default:
      throw new CramMalformedError(`Invalid rANS order: ${order}`)
  }
}
