/*
 * Copyright 2000-2009 JetBrains s.r.o.
 *
 * 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.
 */
package com.intellij.tools.build.bazel.uiDesigner.core;

import java.awt.*;
import java.util.ArrayList;
import java.util.Arrays;

/**
 * public for test purposes
 * @noinspection AbstractMethodCallInConstructor
 */
public abstract class DimensionInfo {
  private final int[] myCell;
  private final int[] mySpan;
  protected final LayoutState myLayoutState;
  private final int[] myStretches;
  private final int[] mySpansAfterElimination;
  private final int[] myCellSizePolicies;
  private final int myGap;

  public DimensionInfo(final LayoutState layoutState, final int gap){
    if (layoutState == null){
      throw new IllegalArgumentException("layoutState cannot be null");
    }
    if (gap < 0){
      throw new IllegalArgumentException("invalid gap: " + gap);
    }
    myLayoutState = layoutState;
    myGap = gap;

    myCell = new int[layoutState.getComponentCount()];
    mySpan = new int[layoutState.getComponentCount()];

    for (int i = 0; i < layoutState.getComponentCount(); i++) {
      final GridConstraints c = layoutState.getConstraints(i);
      myCell[i] = getOriginalCell(c);
      mySpan[i] = getOriginalSpan(c);
    }

    myStretches = new int[getCellCount()];
    Arrays.fill(myStretches, 1);
    //TODO[anton,vova] handle stretches

    final ArrayList<Integer> eliminated = new ArrayList<>();
    mySpansAfterElimination = mySpan.clone();
    Util.eliminate(myCell.clone(), mySpansAfterElimination, eliminated);

    myCellSizePolicies = new int[getCellCount()];
    for (int i = 0; i < myCellSizePolicies.length; i++) {
      myCellSizePolicies[i] = getCellSizePolicyImpl(i, eliminated);
    }
  }

  public final int getComponentCount(){
    return myLayoutState.getComponentCount();
  }

  public final Component getComponent(final int componentIndex){
    return myLayoutState.getComponent(componentIndex);
  }

  public final GridConstraints getConstraints(int componentIndex) {
    return myLayoutState.getConstraints(componentIndex);
  }

  public abstract int getCellCount();

  public abstract int getPreferredWidth(int componentIndex);
  public abstract int getMinimumWidth(int componentIndex);
  public abstract DimensionInfo getDimensionInfo(GridLayoutManager grid);

  public final int getCell(final int componentIndex){
    return myCell[componentIndex];
  }
  
  public final int getSpan(final int componentIndex){
    return mySpan[componentIndex];
  }

  public final int getStretch(final int cellIndex){
    return myStretches[cellIndex];
  }

  protected abstract int getOriginalCell(GridConstraints constraints);
  protected abstract int getOriginalSpan(GridConstraints constraints);
  
  abstract int getSizePolicy(int componentIndex);

  abstract int getChildLayoutCellCount(final GridLayoutManager childLayout);

  public final int getGap(){
    return myGap;
  }

  public boolean componentBelongsCell(final int componentIndex, final int cellIndex) {
    final int componentStartCell = getCell(componentIndex);
    final int span = getSpan(componentIndex);
    return componentStartCell <= cellIndex && cellIndex < componentStartCell + span;
  }
  
  public final int getCellSizePolicy(final int cellIndex){
    return myCellSizePolicies[cellIndex];
  }
  
  private int getCellSizePolicyImpl(final int cellIndex, final ArrayList<Integer> eliminatedCells){
    int policyFromChild = getCellSizePolicyFromInheriting(cellIndex);
    if (policyFromChild != -1) {
      return policyFromChild;
    }
    for (int i = eliminatedCells.size() - 1; i >= 0; i--) {
      if (cellIndex == eliminatedCells.get(i).intValue()) {
        return GridConstraints.SIZEPOLICY_CAN_SHRINK;
      }
    }

    return calcCellSizePolicy(cellIndex);
  }

  private int calcCellSizePolicy(final int cellIndex) {
    boolean canShrink = true;
    boolean canGrow = false;
    boolean wantGrow = false;

    boolean weakCanGrow = true;
    boolean weakWantGrow = true;

    int countOfBelongingComponents = 0;

    for (int i = 0; i < getComponentCount(); i++) {
      if (!componentBelongsCell(i, cellIndex)){
        continue;
      }

      countOfBelongingComponents++;

      final int p = getSizePolicy(i);

      final boolean thisCanShrink = (p & GridConstraints.SIZEPOLICY_CAN_SHRINK) != 0;
      final boolean thisCanGrow = (p & GridConstraints.SIZEPOLICY_CAN_GROW) != 0;
      final boolean thisWantGrow = (p & GridConstraints.SIZEPOLICY_WANT_GROW) != 0;

      if (getCell(i) == cellIndex && mySpansAfterElimination[i] == 1) {
        canShrink &= thisCanShrink;
        canGrow |= thisCanGrow;
        wantGrow |= thisWantGrow;
      }

      if (!thisCanGrow) {
        weakCanGrow = false;
      }
      if (!thisWantGrow) {
        weakWantGrow = false;
      }
    }

    return
      (canShrink ? GridConstraints.SIZEPOLICY_CAN_SHRINK : 0) |
      (canGrow || (countOfBelongingComponents > 0 && weakCanGrow) ? GridConstraints.SIZEPOLICY_CAN_GROW : 0) |
      (wantGrow || (countOfBelongingComponents > 0 && weakWantGrow) ? GridConstraints.SIZEPOLICY_WANT_GROW : 0);
  }

  private int getCellSizePolicyFromInheriting(final int cellIndex) {
    int nonInheritingComponentsInCell = 0;
    int policyFromInheriting = -1;
    for(int i=getComponentCount() - 1; i >= 0; i--) {
      if (!componentBelongsCell(i, cellIndex)) {
        continue;
      }
      Component child = getComponent(i);
      GridConstraints c = getConstraints(i);
      Container container = findAlignedChild(child, c);
      if (container != null) {
        GridLayoutManager grid = (GridLayoutManager) container.getLayout();
        grid.validateInfos(container);
        DimensionInfo info = getDimensionInfo(grid);
        final int policy = info.calcCellSizePolicy(cellIndex - getOriginalCell(c));
        if (policyFromInheriting == -1) {
          policyFromInheriting = policy;
        }
        else {
          policyFromInheriting |= policy;
        }
      }
      else if (getOriginalCell(c) == cellIndex && getOriginalSpan(c) == 1 && !(child instanceof Spacer)) {
        nonInheritingComponentsInCell++;
      }
    }
    if (nonInheritingComponentsInCell > 0) {
      return -1;
    }
    return policyFromInheriting;
  }

  public static Container findAlignedChild(final Component child, final GridConstraints c) {
    if (c.isUseParentLayout() && child instanceof Container) {
      Container container = (Container) child;
      if (container.getLayout() instanceof GridLayoutManager) {
        return container;
      }
      else if (container.getComponentCount() == 1 && container.getComponent(0) instanceof Container) {
        // "use parent layout" also needs to work in cases where a grid is the only control in a non-grid panel
        // which is contained in a grid
        Container childContainer = (Container) container.getComponent(0);
        if (childContainer.getLayout() instanceof GridLayoutManager) {
          return childContainer;
        }
      }
    }
    return null;
  }

  protected final Dimension getPreferredSize(final int componentIndex){
    Dimension size = myLayoutState.myPreferredSizes[componentIndex];
    if (size == null) {
      size = Util.getPreferredSize(myLayoutState.getComponent(componentIndex), myLayoutState.getConstraints(componentIndex), true);
      myLayoutState.myPreferredSizes[componentIndex] = size;
    }
    return size;
  }

  protected final Dimension getMinimumSize(final int componentIndex){
    Dimension size = myLayoutState.myMinimumSizes[componentIndex];
    if (size == null) {
      size = Util.getMinimumSize(myLayoutState.getComponent(componentIndex), myLayoutState.getConstraints(componentIndex), true);
      myLayoutState.myMinimumSizes[componentIndex] = size;
    }
    return size;
  }
}
