// SPDX-FileCopyrightText: 2022 Gregor Kleen <gregor.kleen@ifi.lmu.de>,Sarah Vaupel <sarah.vaupel@ifi.lmu.de>
//
// SPDX-License-Identifier: AGPL-3.0-or-later

const DEBUG_MODE = /localhost/.test(window.location.href) ? 0 : 0;

import defer from 'lodash.defer';

class Overhang {
  colSpan;
  rowSpan;
  cell;

  constructor(colSpan, rowSpan, cell) {
    this.colSpan = colSpan;
    this.rowSpan = rowSpan;
    this.cell = cell;

    if (new.target === Overhang)
      Object.freeze(this);
  }

  nextLine() {
    return new Overhang(this.colSpan, Math.max(0, this.rowSpan - 1), this.cell);
  }

  reduceCol(n) {
    if (this.colSpan > n)
      return new Overhang(this.colSpan - n, this.rowSpan, this.cell);
    else
      return null;
  }

  isHole() {
    return this.rowSpan <= 0;
  }
}

const instanceCache = new Map();

export class TableIndices {
  _table;

  _cellToIndices = new Map();
  _indicesToCell = new Array();

  colSpan = cell => cell ? Math.max(1, cell.colSpan || 1) : 1;
  rowSpan = cell => cell ? Math.max(1, cell.rowSpan || 1) : 1;

  maxRow = 0;
  maxCol = 0;

  constructor(table, overrides) {
    const prev = instanceCache.get(table);
    if ( prev?.instance &&
         overrides?.colSpan === prev.overrides?.colSpan &&
         overrides?.rowSpan === prev.overrides?.rowSpan
       ) {
      if (DEBUG_MODE > 0)
        console.log('Reusing existing TableIndices', table, overrides, prev);
      
      return prev.instance;
    }
    
    if (overrides && overrides.colSpan)
      this.colSpan = overrides.colSpan;
    if (overrides && overrides.rowSpan)
      this.rowSpan = overrides.rowSpan;

    this._table = table;


    let currentOverhangs = new Array();
    let currentRow = 0;

    for (const rowParent of this._table.rows) {
      let newOverhangs = new Array();

      let cellBefore = 0;

      for (const cell of rowParent.cells) {
        let i;

        for (i = 0; i < currentOverhangs.length; i++) {
          const overhang = currentOverhangs[i];

          if (overhang.isHole())
            break;
          else
            newOverhangs.push(overhang.nextLine());
          
          if (DEBUG_MODE > 1)
            console.log('From overhang', overhang);

          cellBefore += overhang.colSpan;
        }

        currentOverhangs = currentOverhangs.slice(i);
        let remCols = this.colSpan(cell);
        while (remCols > 0 && currentOverhangs[0]) {
          let firstOverhang = currentOverhangs[0].reduceCol(this.colSpan(cell));
          if (firstOverhang) {
            if (DEBUG_MODE > 1)
              console.log('Replace first overhang', remCols, currentOverhangs[0], firstOverhang);
            currentOverhangs[0] = firstOverhang;
            break;
          } else {
            if (DEBUG_MODE > 1)
              console.log('Drop first overhang', remCols, currentOverhangs[0], firstOverhang);
            remCols -= currentOverhangs[0].colSpan;
            currentOverhangs.shift();
          }
        }

        this._cellToIndices.set(cell, { row: currentRow, col: cellBefore });

        let rows = range(currentRow, currentRow + this.rowSpan(cell));
        let columns = range(cellBefore, cellBefore + this.colSpan(cell));

        if (DEBUG_MODE > 1) {
          console.log('Result', rows, columns);
          
          cell.dataset.rows = JSON.stringify(rows);
          cell.dataset.columns = JSON.stringify(columns);
        }

        for (const row of rows) {
          for (const col of columns) {
            if (!this._indicesToCell[row])
              this._indicesToCell[row] = new Array();

            this._indicesToCell[row][col] = cell;

            this.maxRow = Math.max(row, this.maxRow);
            this.maxCol = Math.max(col, this.maxCol);
          }
        }

        newOverhangs.push(new Overhang(this.colSpan(cell), this.rowSpan(cell) - 1, cell));

        if (DEBUG_MODE > 1)
          console.log('From current cell', this.colSpan(cell));
        cellBefore += this.colSpan(cell);
      }

      currentOverhangs = Array.from(newOverhangs);
      currentRow++;
    }

    if (DEBUG_MODE > 1) {
      console.log(this._cellToIndices);
      console.table(this._indicesToCell);
    }

    instanceCache.set(table, { overrides: overrides, instance: this });
    defer(() => { instanceCache.delete(table); } );
  }

  colIndex(cell) {
    return this.getIndices(cell)?.col;
  }
  
  rowIndex(cell) {
    return this.getIndices(cell)?.row;
  }
  
  getIndices(cell) {
    const res = this._cellToIndices.get(cell);

    if (DEBUG_MODE > 2)
      console.log('getIndices', cell, res);

    return res;
  }

  getCell(row, col) {
    const cell = this._indicesToCell[row]?.[col];

    if (DEBUG_MODE > 2)
      console.log('getCell', row, col, cell);

    return cell;
  }
}

function range(from, to) {
  return [...Array(to - from).keys()].map(n => n + from);
}
