/*
 * Copyright (c) 2010, 2023 BSI Business Systems Integration AG
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 */
import {AbstractLayout, arrays, Dimension, graphics, HtmlComponent, HtmlCompPrefSizeOptions, MenuBarLayout, RowLayout, scout, scrollbars, Table} from '../index';

export class TableLayout extends AbstractLayout {
  table: Table;
  protected _dataHeightPositive: boolean;

  constructor(table: Table) {
    super();
    this.table = table;
    this._dataHeightPositive = false;
  }

  override layout($container: JQuery) {
    let menuBarHeight = 0,
      footerHeight = 0,
      headerHeight = 0,
      tileTableHeight = 0,
      controlContainerHeight = 0,
      tileAccordion = this.table.tableTileGridMediator ? this.table.tableTileGridMediator.tileAccordion : null,
      $data = this.table.$data,
      dataMargins = graphics.margins(scout.nvl($data, this.table.$container)),
      dataMarginsHeight = dataMargins.top + dataMargins.bottom,
      menuBar = this.table.menuBar,
      footer = this.table.footer,
      header = this.table.header,
      tileTableHeader = this.table.tileTableHeader,
      visibleColumns = this.table.visibleColumns(),
      lastColumn = visibleColumns[visibleColumns.length - 1],
      htmlContainer = this.table.htmlComp,
      containerSize = htmlContainer.availableSize({exact: true}).subtract(htmlContainer.insets());

    if (this.table.menuBarVisible && menuBar.visible) {
      let htmlMenuBar = HtmlComponent.get(menuBar.$container);
      let menuBarSize = MenuBarLayout.size(htmlMenuBar, containerSize);
      htmlMenuBar.setSize(menuBarSize);
      menuBarHeight = menuBarSize.height;
    }
    $container.css('--menubar-height', menuBarHeight + 'px');
    if (header) {
      headerHeight = graphics.size(header.$container).height;
      if (header.menuBar) {
        header.menuBar.validateLayout();
      }
    }
    if (footer) {
      // Layout table footer and add size of footer (including the control content) to 'height'
      footerHeight = graphics.size(footer.$container).height;
      controlContainerHeight = footer.computeControlContainerHeight(this.table, footer.selectedControl, !this._dataHeightPositive);
      let controlContainerInsets = graphics.insets(footer.$controlContainer);
      if (!footer.animating) { // closing or opening: height is about to be changed
        footer.$controlContainer.cssHeight(controlContainerHeight);
        footer.$controlContent.outerHeight(controlContainerHeight - controlContainerInsets.vertical());
        footer.revalidateLayout();
      }
    }
    if (tileTableHeader && tileTableHeader.visible) {
      let groupBoxSize = tileTableHeader.htmlComp.prefSize({
        widthHint: containerSize.width
      });
      groupBoxSize.width = containerSize.width;
      groupBoxSize = groupBoxSize.subtract(tileTableHeader.htmlComp.margins());
      tileTableHeader.htmlComp.setSize(groupBoxSize);
      tileTableHeight = groupBoxSize.height;
    }
    let controlsHeight = dataMarginsHeight + menuBarHeight + controlContainerHeight + footerHeight + headerHeight + tileTableHeight;
    let dataHeight = containerSize.height - controlsHeight;
    $container.css('--controls-height', controlsHeight + 'px');
    if ($data) {
      $data.css('height', 'calc(100% - ' + controlsHeight + 'px)');
      this._dataHeightPositive = $data.height() > 0;
    } else {
      if (tileAccordion && tileAccordion.htmlComp) {
        tileAccordion.htmlComp.setSize(new Dimension(containerSize.width, dataHeight));
        scrollbars.update(tileAccordion.$container);
        this._dataHeightPositive = dataHeight > 0;
      }
    }

    if (!this.table.tileMode) {
      this._layoutColumns();

      // Size of last column may have to be adjusted due to the header menu items
      if (header) {
        header.resizeHeaderItem(lastColumn);
      }

      this.table.setViewRangeSize(this.table.calculateViewRangeSize());

      if (!htmlContainer.layouted) {
        this.table._renderScrollTop();
      }

      // Always render viewport (not only when viewRangeSize changes), because view range depends on scroll position and data height
      this.table._renderViewport();

      // Render scroll top again to make sure the data is really at the correct position after rendering viewport.
      // Somehow table.$data[0].scrollTop changes during _renderViewport sometimes (e.g. when there are aggregate rows)
      if (!htmlContainer.layouted) {
        this.table._renderScrollTop();
      }

      if (this.table.cellEditorPopup && this.table.cellEditorPopup.rendered) {
        this.table.cellEditorPopup.position();
        this.table.cellEditorPopup.pack();
      }

      this.table.updateScrollbars();
    }
  }

  protected _layoutColumns(widthHint?: number) {
    this._autoOptimizeColumnsWidths();

    let htmlContainer = this.table.htmlComp;
    let columnLayoutDirty = this.table.columnLayoutDirty || !htmlContainer.sizeCached;
    if (!columnLayoutDirty) {
      let width = widthHint || htmlContainer.size().width;
      columnLayoutDirty = htmlContainer.sizeCached.width !== width;
    }
    // Auto resize only if table width or column structure has changed
    if (columnLayoutDirty) {
      if (this.table.autoResizeColumns) {
        this._autoResizeColumns(widthHint);
      }
      // This is already done in _renderRowsInRange, but it is necessary here as well if the zoom level changes dynamically (or autoResizeColumns toggles)
      this._updateRealColumnWidths();
      this.table.columnLayoutDirty = false;
    }
  }

  /**
   * Workaround for Chrome bug, see {@link Table._updateRealColumnWidths}
   */
  protected _updateRealColumnWidths() {
    if (this.table._updateRealColumnWidths()) {
      this.table._updateRowWidth();
      if (this.table.header && this.table.header.rendered) {
        this.table.header.resizeHeaderItems();
      }
      this.table.$rows(true)
        .css('width', this.table.rowWidth);
    }
  }

  /**
   * Resizes all visible columns with autoOptimizeWidth set to true, if necessary (means if autoOptimizeWidthRequired is true)
   */
  protected _autoOptimizeColumnsWidths() {
    this.table.visibleColumns().forEach(column => {
      if (column.autoOptimizeWidth && column.autoOptimizeWidthRequired) {
        this.table.resizeToFit(column, column.autoOptimizeMaxWidth);
      }
    });
  }

  /**
   * Resizes the visible columns to make them use all the available space.
   */
  protected _autoResizeColumns(widthHint?: number) {
    let newWidth: number, weight: number,
      relevantColumns = [],
      currentWidth = 0,
      totalInitialWidth = 0,
      tableWidth = widthHint || this.table.$data.width(),
      availableWidth = Math.floor(tableWidth - (this.table.rowBorders.horizontal() + this.table.rowMargins.horizontal()));

    // Don't resize fixed and auto optimize width columns
    this.table.visibleColumns().forEach(column => {
      if (column.fixedWidth || column.autoOptimizeWidth) {
        availableWidth -= column.width;
      } else {
        relevantColumns.push(column);
        currentWidth += column.width;
        totalInitialWidth += column.initialWidth;
      }
    });

    if (availableWidth === currentWidth) {
      // Columns already use the available space, no need to resize
      return;
    }

    let remainingWidth = availableWidth;

    // First, filter columns which would get smaller than their minimal size
    let minWidthColumns = relevantColumns.filter(column => {
      // Use initial width as preferred width for auto resize columns.
      // This makes sure the column doesn't get too small on small screens. The user can still make the column smaller though.
      let minWidth = Math.max(column.minWidth, column.initialWidth);
      if (totalInitialWidth === 0) {
        weight = 1 / relevantColumns.length;
      } else {
        weight = column.initialWidth / totalInitialWidth;
      }
      newWidth = Math.floor(weight * remainingWidth);
      if (newWidth < minWidth) {
        newWidth = minWidth;
        remainingWidth = Math.max(remainingWidth - newWidth, 0);
        return true;
      }
      return false;
    });

    // Resize them to their minimal width
    minWidthColumns.forEach((column, index) => {
      let minWidth = Math.max(column.minWidth, column.initialWidth);
      arrays.remove(relevantColumns, column);

      newWidth = minWidth;
      totalInitialWidth -= column.initialWidth;
      // If this is the last column, add remaining space (due to rounding) to this column
      if (index === minWidthColumns.length - 1 && remainingWidth > 0 && relevantColumns.length === 0) {
        newWidth += remainingWidth;
        remainingWidth = 0;
      }
      if (newWidth !== column.width) {
        this.table.resizeColumn(column, newWidth);
      }
    });

    // Then resize the others
    availableWidth = remainingWidth;
    relevantColumns.forEach((column, index) => {
      if (totalInitialWidth === 0) {
        weight = 1 / relevantColumns.length;
      } else {
        weight = column.initialWidth / totalInitialWidth;
      }
      newWidth = Math.floor(weight * availableWidth);
      remainingWidth -= newWidth;
      // If this is the last column, add remaining space (due to rounding) to this column
      if (index === relevantColumns.length - 1 && remainingWidth > 0) {
        newWidth += remainingWidth;
        remainingWidth = 0;
      }
      if (newWidth !== column.width) {
        this.table.resizeColumn(column, newWidth);
      }
    });
  }

  override preferredLayoutSize($container: JQuery, options?: HtmlCompPrefSizeOptions): Dimension {
    if (this.table.tileMode) {
      // Use RowLayout to calculate preferredLayoutSize of TileTableHeader, TileAccordion and Footer.
      return new RowLayout().preferredLayoutSize($container, options);
    }
    // If autoResizeColumns and text wrap is enabled, the height of the table depends on the width
    this._layoutColumns(options.widthHint);

    // If table was not visible during renderViewport, the rows are not rendered yet (see _renderViewport)
    // -> make sure rows are rendered otherwise preferred height cannot be determined
    this.table._renderViewport();
    return super.preferredLayoutSize($container, options);
  }
}
