/*************************************************************
 *
 *  Copyright (c) 2017-2026 The MathJax Consortium
 *
 *  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.
 */

/**
 * @file  Implements a bounding-box object and operations on it
 *
 * @author dpvc@mathjax.org (Davide Cervone)
 */

import { BIGDIMEN } from './lengths.js';

/**
 *  The data used to initialize a BBox
 */
export type BBoxData = {
  w?: number;
  h?: number;
  d?: number;
};

/*****************************************************************/
/**
 *  The BBox class
 */

export class BBox {
  /**
   * Constant for pwidth of full width box
   */
  public static fullWidth = '100%';

  /**
   * The side names, indices, and which dimension they affect
   */
  public static boxSides: [string, number, string][] = [
    ['Top', 0, 'h'],
    ['Right', 1, 'w'],
    ['Bottom', 2, 'd'],
    ['Left', 3, 'w'],
  ];

  /**
   *  These are the data stored for a bounding box
   */
  public w: number;
  public h: number;
  public d: number;
  public scale: number;
  public rscale: number; // scale relative to the parent's scale
  public L: number; // extra space on the left
  public R: number; // extra space on the right
  public pwidth: string; // percentage width (for tables)
  public ic: number; // italic correction
  public oc: number; // alternate italic correction for -tex-mit variant
  public sk: number; // skew
  public dx: number; // offset for combining characters as accents

  /**
   * @returns {BBox}  A BBox initialized to zeros
   */
  public static zero(): BBox {
    return new BBox({ h: 0, d: 0, w: 0 });
  }

  /**
   * @returns {BBox}  A BBox with height and depth not set
   */
  public static empty(): BBox {
    return new BBox();
  }

  /**
   * @param {BBoxData} def  The data with which to initialize the BBox
   *
   * @class
   */
  constructor(def: BBoxData = { w: 0, h: -BIGDIMEN, d: -BIGDIMEN }) {
    this.w = def.w || 0;
    this.h = 'h' in def ? def.h : -BIGDIMEN;
    this.d = 'd' in def ? def.d : -BIGDIMEN;
    this.L = this.R = this.ic = this.oc = this.sk = this.dx = 0;
    this.scale = this.rscale = 1;
    this.pwidth = '';
  }

  /**
   * Set up a bbox for append() and combine() operations
   *
   * @returns {BBox}  the bbox itself (for chaining calls)
   */
  public empty(): BBox {
    this.w = 0;
    this.h = this.d = -BIGDIMEN;
    return this;
  }

  /**
   * Convert any unspecified values into zeros
   */
  public clean() {
    if (this.w === -BIGDIMEN) this.w = 0;
    if (this.h === -BIGDIMEN) this.h = 0;
    if (this.d === -BIGDIMEN) this.d = 0;
  }

  /**
   * @param {number} scale  The scale to use to modify the bounding box size
   */
  public rescale(scale: number) {
    this.w *= scale;
    this.h *= scale;
    this.d *= scale;
  }

  /**
   * @param {BBox} cbox  A bounding to combine with this one
   * @param {number} x   An x-offest for the child bounding box
   * @param {number} y   A y-offset for the child bounding box
   */
  public combine(cbox: BBox, x: number = 0, y: number = 0) {
    const rscale = cbox.rscale;
    const w = x + rscale * (cbox.w + cbox.L + cbox.R);
    const h = y + rscale * cbox.h;
    const d = rscale * cbox.d - y;
    if (w > this.w) this.w = w;
    if (h > this.h) this.h = h;
    if (d > this.d) this.d = d;
  }

  /**
   * @param {BBox} cbox  A bounding box to be added to the right of this one
   */
  public append(cbox: BBox) {
    const scale = cbox.rscale;
    this.w += scale * (cbox.w + cbox.L + cbox.R);
    if (scale * cbox.h > this.h) {
      this.h = scale * cbox.h;
    }
    if (scale * cbox.d > this.d) {
      this.d = scale * cbox.d;
    }
  }

  /**
   * @param {BBox} cbox  The bounding box to use to overwrite this one
   */
  public updateFrom(cbox: BBox) {
    this.h = cbox.h;
    this.d = cbox.d;
    this.w = cbox.w;
    if (cbox.pwidth) {
      this.pwidth = cbox.pwidth;
    }
  }

  /**
   * @returns {BBox}   A copy of the current BBox
   */
  public copy(): BBox {
    const bbox = new BBox();
    Object.assign(bbox, this);
    return bbox;
  }
}
