/*
 * 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, Dimension, graphics, Insets, Popup, Rectangle, scout} from '../index';

export class PopupLayout extends AbstractLayout {
  popup: Popup;
  /** enables popups with a height which depends on the width (= popups with wrapping content) */
  doubleCalcPrefSize: boolean;
  autoPosition: boolean;
  autoSize: boolean;
  resizeAnimationRunning: boolean;
  resizeAnimationDuration: JQuery.Duration;

  protected _autoPositionOrig: boolean;

  constructor(popup: Popup) {
    super();
    this.popup = popup;
    this.doubleCalcPrefSize = true;
    this.autoPosition = true;
    this.autoSize = true;
    this.resizeAnimationRunning = false;
    this.resizeAnimationDuration = null;
    this._autoPositionOrig = null;
  }

  override layout($container: JQuery) {
    if (this.popup.isOpeningAnimationRunning()) {
      this.popup.$container.oneAnimationEnd(this.layout.bind(this, $container));
      return;
    }
    if (this.popup.removalPending || this.popup.removing || !this.popup.rendered) {
      return;
    }
    if (!this.autoSize) {
      // Just layout the popup with the current size
      this._setSize(this.popup.htmlComp.size({exact: true}));
      return;
    }

    let htmlComp = this.popup.htmlComp;
    // Read current bounds before calling pref size, because pref size may change position (_calcMaxSize)
    let currentBounds = graphics.bounds(htmlComp.$comp);
    let prefSize = this.preferredLayoutSize($container, {
      exact: true,
      widthOnly: this.doubleCalcPrefSize
    });

    prefSize = this.adjustSize(prefSize);
    if (this.doubleCalcPrefSize) {
      prefSize = this.preferredLayoutSize($container, {
        exact: true,
        widthHint: prefSize.width - htmlComp.insets().horizontal()
      });
      prefSize = this.adjustSize(prefSize);
    }

    this._setSize(prefSize);

    if (htmlComp.layouted && this.popup.animateResize) {
      this._resizeAnimated(currentBounds, prefSize);
    }
  }

  protected _resizeAnimated(currentBounds: Rectangle, prefSize: Dimension) {
    this._position();
    let htmlComp = this.popup.htmlComp;
    let prefPosition = htmlComp.$comp.position();

    // Preferred size are exact, current bounds are rounded -> round preferred size up to make compare work
    let prefBounds = new Rectangle(prefPosition.left, prefPosition.top, Math.ceil(prefSize.width), Math.ceil(prefSize.height));
    if (currentBounds.equals(prefBounds)) {
      // Bounds did not change -> do nothing
      return;
    }
    this.resizeAnimationRunning = true;
    htmlComp.$comp
      .stop(true)
      .cssHeight(currentBounds.height)
      .cssWidth(currentBounds.width)
      .cssLeft(currentBounds.x)
      .cssTop(currentBounds.y)
      .animate({
        height: prefSize.height,
        width: prefSize.width,
        left: prefPosition.left,
        top: prefPosition.top
      }, {
        duration: this.resizeAnimationDuration,
        complete: () => {
          this.resizeAnimationRunning = false;
          if (!this.popup.rendered) {
            return;
          }
          // Ensure the arrow is at the correct position after the animation
          this._position();
        }
      });
  }

  protected _position(switchIfNecessary?: boolean) {
    if (this.autoPosition) {
      this.popup.position(switchIfNecessary);
    }
  }

  protected _setSize(prefSize: Dimension) {
    graphics.setSize(this.popup.htmlComp.$comp, prefSize);
  }

  adjustSize(prefSize: Dimension): Dimension {
    // Consider CSS min/max rules
    this.popup.htmlComp._adjustPrefSizeWithMinMaxSize(prefSize);

    // Consider window boundaries
    if (this.popup.boundToAnchor && (this.popup.anchorBounds || this.popup.$anchor)) {
      return this._adjustSizeWithAnchor(prefSize);
    }
    return this._adjustSize(prefSize);
  }

  protected _adjustSize(prefSize: Dimension): Dimension {
    let popupSize = new Dimension(),
      maxSize = this._calcMaxSize();

    // Ensure the popup is not larger than max size
    popupSize.width = Math.min(maxSize.width, prefSize.width);
    popupSize.height = Math.min(maxSize.height, prefSize.height);

    return popupSize;
  }

  /**
   * Considers window boundaries.
   *
   */
  protected _calcMaxSize(): Dimension {
    let maxWidth, maxHeight,
      htmlComp = this.popup.htmlComp,
      windowPaddingX = this.popup.windowPaddingX,
      windowPaddingY = this.popup.windowPaddingY,
      popupMargins = htmlComp.margins(),
      windowSize = this.popup.getWindowSize();

    maxWidth = (windowSize.width - popupMargins.horizontal() - windowPaddingX);
    maxHeight = (windowSize.height - popupMargins.vertical() - windowPaddingY);

    return new Dimension(maxWidth, maxHeight);
  }

  protected _adjustSizeWithAnchor(prefSize: Dimension): Dimension {
    let popupSize = new Dimension(),
      maxSize = this._calcMaxSizeAroundAnchor(),
      windowSize = this._calcMaxSize(),
      Alignment = Popup.Alignment,
      horizontalAlignment = this.popup.horizontalAlignment,
      verticalAlignment = this.popup.verticalAlignment;

    // Decide whether the prefSize can be used or the popup needs to be shrunken so that it fits into the viewport
    // The decision is based on the preferred opening direction
    // Example: The popup would like to be opened leftedge and bottom
    // If there is enough space on the right and on the bottom -> pref size is used
    // If there is not enough space on the right it checks whether there is enough space on the left
    // If there is enough space on the left -> use preferred width -> The opening direction will be switched using position() at the end
    // If there is not enough space on the left as well, the greater width is used -> Position() will either switch the direction or not, depending on the size of the popup
    // The same happens for y direction if there is not enough space on the bottom
    popupSize.width = prefSize.width;
    if (this.popup.trimWidth) {
      if (this.popup.horizontalSwitch) {
        if (prefSize.width > maxSize.right && prefSize.width > maxSize.left) {
          popupSize.width = Math.max(maxSize.right, maxSize.left);
        }
      } else {
        if (horizontalAlignment === Alignment.RIGHT) {
          popupSize.width = Math.min(popupSize.width, maxSize.right);
        } else if (horizontalAlignment === Alignment.LEFT) {
          popupSize.width = Math.min(popupSize.width, maxSize.left);
        } else {
          popupSize.width = Math.min(popupSize.width, windowSize.width);
        }
      }
    }
    popupSize.height = prefSize.height;
    if (this.popup.trimHeight) {
      if (this.popup.verticalSwitch) {
        if (prefSize.height > maxSize.bottom && prefSize.height > maxSize.top) {
          popupSize.height = Math.max(maxSize.bottom, maxSize.top);
        }
      } else {
        if (verticalAlignment === Alignment.BOTTOM) {
          popupSize.height = Math.min(popupSize.height, maxSize.bottom);
        } else if (verticalAlignment === Alignment.TOP) {
          popupSize.height = Math.min(popupSize.height, maxSize.top);
        } else {
          popupSize.height = Math.min(popupSize.height, windowSize.height);
        }
      }
    }

    // On CENTER alignment, the anchor must ne be considered. Instead make sure the popup does not exceed window boundaries (same as in adjustSize)
    if (verticalAlignment === Alignment.CENTER || horizontalAlignment === Alignment.CENTER) {
      if (horizontalAlignment === Alignment.CENTER) {
        popupSize.width = Math.min(windowSize.width, prefSize.width);
      }
      if (verticalAlignment === Alignment.CENTER) {
        popupSize.height = Math.min(windowSize.height, prefSize.height);
      }
    }

    return popupSize;
  }

  /**
   * Considers window boundaries.
   *
   */
  protected _calcMaxSizeAroundAnchor(): Insets {
    let maxWidthLeft, maxWidthRight, maxHeightDown, maxHeightUp,
      htmlComp = this.popup.htmlComp,
      windowPaddingX = this.popup.windowPaddingX,
      windowPaddingY = this.popup.windowPaddingY,
      popupMargins = htmlComp.margins(),
      anchorBounds = this.popup.getAnchorBounds(),
      windowSize = this.popup.getWindowSize(),
      horizontalAlignment = this.popup.horizontalAlignment,
      verticalAlignment = this.popup.verticalAlignment,
      Alignment = Popup.Alignment;

    if (scout.isOneOf(horizontalAlignment, Alignment.LEFTEDGE, Alignment.RIGHTEDGE)) {
      maxWidthRight = windowSize.width - anchorBounds.x - popupMargins.horizontal() - windowPaddingX;
      maxWidthLeft = anchorBounds.right() - popupMargins.horizontal() - windowPaddingX;
    } else { // LEFT or RIGHT
      maxWidthRight = windowSize.width - anchorBounds.right() - popupMargins.horizontal() - windowPaddingX;
      maxWidthLeft = anchorBounds.x - popupMargins.horizontal() - windowPaddingX;
    }

    if (scout.isOneOf(verticalAlignment, Alignment.BOTTOMEDGE, Alignment.TOPEDGE)) {
      maxHeightDown = windowSize.height - anchorBounds.y - popupMargins.vertical() - windowPaddingY;
      maxHeightUp = anchorBounds.bottom() - popupMargins.vertical() - windowPaddingY;
    } else { // BOTTOM or TOP
      maxHeightDown = windowSize.height - anchorBounds.bottom() - popupMargins.vertical() - windowPaddingY;
      maxHeightUp = anchorBounds.y - popupMargins.vertical() - windowPaddingY;
    }

    return new Insets(maxHeightUp, maxWidthRight, maxHeightDown, maxWidthLeft);
  }

  disableAutoPosition() {
    if (this._autoPositionOrig === null) {
      this._autoPositionOrig = this.autoPosition;
      this.autoPosition = false;
    }
  }

  resetAutoPosition() {
    this.autoPosition = this._autoPositionOrig;
    this._autoPositionOrig = null;
  }
}
