/**
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
export class DataArray {
  data: Uint8Array;
  readonly shape: number[];

  constructor(data: Uint8Array, shape: Array<number>) {
    this.data = data;
    this.shape = shape;
  }
}

export type RLEObject = {
  size: [h: number, w: number];
  counts: string;
};

type RLE = {
  h: number;
  w: number;
  m: number;
  cnts: number[];
};

type BB = number[];

function rleInit(R: RLE, h: number, w: number, m: number, cnts: number[]) {
  R.h = h;
  R.w = w;
  R.m = m;
  R.cnts = m === 0 ? [0] : cnts;
}

function rlesInit(R: RLE[], n: number) {
  let i;
  for (i = 0; i < n; i++) {
    R[i] = {h: 0, w: 0, m: 0, cnts: [0]};
    rleInit(R[i], 0, 0, 0, [0]);
  }
}

class RLEs {
  _R: RLE[];
  _n: number;

  constructor(n: number) {
    this._R = [];
    rlesInit(this._R, n);
    this._n = n;
  }
}

export class Masks {
  _mask: Uint8Array;
  _h: number;
  _w: number;
  _n: number;

  constructor(h: number, w: number, n: number) {
    this._mask = new Uint8Array(h * w * n);
    this._h = h;
    this._w = w;
    this._n = n;
  }

  toDataArray(): DataArray {
    return new DataArray(this._mask, [this._h, this._w, this._n]);
  }
}

// encode mask to RLEs objects
// list of RLE string can be generated by RLEs member function
export function encode(mask: DataArray): RLEObject[] {
  const h = mask.shape[0];
  const w = mask.shape[1];
  const n = mask.shape[2];
  const Rs = new RLEs(n);
  rleEncode(Rs._R, mask.data, h, w, n);
  const objs = _toString(Rs);
  return objs;
}

// decode mask from compressed list of RLE string or RLEs object
export function decode(rleObjs: RLEObject[]): DataArray {
  const Rs = _frString(rleObjs);
  const h = Rs._R[0].h;
  const w = Rs._R[0].w;
  const n = Rs._n;
  const masks = new Masks(h, w, n);
  rleDecode(Rs._R, masks._mask, n);
  return masks.toDataArray();
}

export function toBbox(rleObjs: RLEObject[]): BB {
  const Rs = _frString(rleObjs);
  const n = Rs._n;
  const bb: BB = [];
  rleToBbox(Rs._R, bb, n);
  return bb;
}

function rleEncode(R: RLE[], M: Uint8Array, h: number, w: number, n: number) {
  let i;
  let j;
  let k;
  const a = w * h;
  let c;
  const cnts: number[] = [];
  let p;
  for (i = 0; i < n; i++) {
    const from = a * i;
    const to = a * (i + 1);
    // Slice data for current RLE object
    const T = M.slice(from, to);
    k = 0;
    p = 0;
    c = 0;
    for (j = 0; j < a; j++) {
      if (T[j] !== p) {
        cnts[k++] = c;
        c = 0;
        p = T[j];
      }
      c++;
    }
    cnts[k++] = c;
    rleInit(R[i], h, w, k, [...cnts]);
  }
}

function rleDecode(R: RLE[], M: Uint8Array, n: number): void {
  let i;
  let j;
  let k;
  let p = 0;
  for (i = 0; i < n; i++) {
    let v = false;
    for (j = 0; j < R[i].m; j++) {
      for (k = 0; k < R[i].cnts[j]; k++) {
        M[p++] = v === false ? 0 : 1;
      }
      v = !v;
    }
  }
}

function rleToString(R: RLE): string {
  /* Similar to LEB128 but using 6 bits/char and ascii chars 48-111. */
  let i;
  const m = R.m;
  let p = 0;
  let x: number;
  let more;
  const s: string[] = [];
  for (i = 0; i < m; i++) {
    x = R.cnts[i];
    if (i > 2) {
      x -= R.cnts[i - 2];
    }
    more = true; // 1;
    while (more) {
      let c = x & 0x1f;
      x >>= 5;
      more = c & 0x10 ? x != -1 : x != 0;
      if (more) {
        c |= 0x20;
      }
      c += 48;
      s[p++] = String.fromCharCode(c);
    }
  }
  return s.join('');
}

// internal conversion from Python RLEs object to compressed RLE format
function _toString(Rs: RLEs): RLEObject[] {
  const n = Rs._n;
  let py_string;
  let c_string;
  const objs: RLEObject[] = [];
  for (let i = 0; i < n; i++) {
    c_string = rleToString(Rs._R[i]);
    py_string = c_string;
    objs.push({
      size: [Rs._R[i].h, Rs._R[i].w],
      counts: py_string,
    });
  }
  return objs;
}

// internal conversion from compressed RLE format to Python RLEs object
function _frString(rleObjs: RLEObject[]): RLEs {
  const n = rleObjs.length;
  const Rs = new RLEs(n);
  let py_string;
  let c_string;
  for (let i = 0; i < rleObjs.length; i++) {
    const obj = rleObjs[i];
    py_string = obj.counts;
    c_string = py_string;
    rleFrString(Rs._R[i], c_string, obj.size[0], obj.size[1]);
  }
  return Rs;
}

function rleToBbox(R: RLE[], bb: BB, n: number) {
  for (let i = 0; i < n; i++) {
    const h = R[i].h;
    const w = R[i].w;
    let m = R[i].m;
    // The RLE structure likely contains run-length encoded data where each
    // element represents a count of consecutive pixels with the same value in
    // a binary image (black or white). Since the counts represent both black
    // and white pixels, this operation ((siz)(m/2)) * 2 is used to ensure that
    // m is always an even number. By doing so, the code can later check
    // whether the current pixel is black or white based on whether the index j
    // is even or odd.
    m = Math.floor(m / 2) * 2;
    let xs = w;
    let ys = h;
    let xe = 0;
    let ye = 0;
    let cc = 0;
    let t;
    let y;
    let x;
    let xp = 0;
    if (m === 0) {
      bb[4 * i] = bb[4 * i + 1] = bb[4 * i + 2] = bb[4 * i + 3] = 0;
      continue;
    }
    for (let j = 0; j < m; j++) {
      cc += R[i].cnts[j];
      t = cc - (j % 2);
      y = t % h;
      x = Math.floor((t - y) / h);
      if (j % 2 === 0) {
        xp = x;
      } else if (xp < x) {
        ys = 0;
        ye = h - 1;
      }
      xs = Math.min(xs, x);
      xe = Math.max(xe, x);
      ys = Math.min(ys, y);
      ye = Math.max(ye, y);
    }
    bb[4 * i] = xs;
    bb[4 * i + 2] = xe - xs + 1;
    bb[4 * i + 1] = ys;
    bb[4 * i + 3] = ye - ys + 1;
  }
}

function rleFrString(R: RLE, s: string, h: number, w: number): void {
  let m = 0;
  let p = 0;
  let k;
  let x;
  let more;
  let cnts = [];
  while (s[m]) {
    m++;
  }
  cnts = [];
  m = 0;
  while (s[p]) {
    x = 0;
    k = 0;
    more = 1;
    while (more) {
      const c = s.charCodeAt(p) - 48;
      x |= (c & 0x1f) << (5 * k);
      more = c & 0x20;
      p++;
      k++;
      if (!more && c & 0x10) {
        x |= -1 << (5 * k);
      }
    }
    if (m > 2) {
      x += cnts[m - 2];
    }
    cnts[m++] = x;
  }
  rleInit(R, h, w, m, cnts);
}
