/*
 * 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.Objects;

public final class GridConstraints implements Cloneable {
  public static final GridConstraints[] EMPTY_ARRAY = new GridConstraints[0];
  public static final int FILL_NONE = 0;
  public static final int FILL_HORIZONTAL = 1;
  public static final int FILL_VERTICAL = 2;
  public static final int FILL_BOTH = FILL_HORIZONTAL | FILL_VERTICAL;

  /**
   * Put the component in the center of its display area.
   */
  public static final int ANCHOR_CENTER = 0;

  /**
   * Put the component at the top of its display area,
   * centered horizontally.
   */
  public static final int ANCHOR_NORTH = 1;

  /**
   * Put the component at the bottom of its display area, centered
   * horizontally.
   */
  public static final int ANCHOR_SOUTH = 2;

  /**
   * Put the component on the right side of its display area,
   * centered vertically.
   */
  public static final int ANCHOR_EAST = 4;

  /**
   * Put the component on the left side of its display area,
   * centered vertically.
   */
  public static final int ANCHOR_WEST = 8;

  /**
   * Put the component at the top-right corner of its display area.
   */
  public static final int ANCHOR_NORTHEAST = ANCHOR_NORTH | ANCHOR_EAST;

  /**
   * Put the component at the bottom-right corner of its display area.
   */
  public static final int ANCHOR_SOUTHEAST = ANCHOR_SOUTH | ANCHOR_EAST;

  /**
   * Put the component at the bottom-left corner of its display area.
   */
  public static final int ANCHOR_SOUTHWEST = ANCHOR_SOUTH | ANCHOR_WEST;


  /**
   * Put the component at the top-left corner of its display area.
   */
  public static final int ANCHOR_NORTHWEST = ANCHOR_NORTH | ANCHOR_WEST;

  /**
   * TODO[anton,vova] write javadoc
   */
  public static final int SIZEPOLICY_FIXED = 0;
  public static final int SIZEPOLICY_CAN_SHRINK = 1;
  public static final int SIZEPOLICY_CAN_GROW = 2;
  public static final int SIZEPOLICY_WANT_GROW = 4;

  public static final int ALIGN_LEFT   = 0;
  public static final int ALIGN_CENTER = 1;
  public static final int ALIGN_RIGHT  = 2;
  public static final int ALIGN_FILL   = 3;

  private int myRow;
  private int myColumn;
  private int myRowSpan;
  private int myColSpan;
  private int myVSizePolicy;
  private int myHSizePolicy;
  private int myFill;
  private int myAnchor;

  /**
   * @see #myPreferredSize
   */
  public final Dimension myMinimumSize;

  /**
   * overridden preferred size. Never <code>null</code>. Term "overridden" means that GridLayoutManager gets
   * preferred size from the constrains first. Moreover if one of the returned dimensions (x or y)
   * is negative then the corresponding dimension is also calculated by the component.
   * It means that it's possible to override any particular dimension without subclassing
   * of the component. Stantard Swing doesn't have such capability.
   */
  public final Dimension myPreferredSize;

  /**
   * @see #myPreferredSize
   */
  public final Dimension myMaximumSize;

  /**
   * Additonal offset of the component from the left part of its cell boundary, expressed in multiples
   * of the HIG-specified indent step.
   */
  private int myIndent;

  /**
   * If true, a component with grid layout embedded in a parent with grid layout uses the parent's
   * column sizes.
   */
  private boolean myUseParentLayout;

  public GridConstraints(){
    myRowSpan = 1;
    myColSpan = 1;
    myVSizePolicy = SIZEPOLICY_CAN_GROW | SIZEPOLICY_CAN_SHRINK;
    myHSizePolicy = SIZEPOLICY_CAN_GROW | SIZEPOLICY_CAN_SHRINK;
    myFill = FILL_NONE;
    myAnchor = ANCHOR_CENTER;
    myMinimumSize = new Dimension(-1, -1);
    myPreferredSize = new Dimension(-1, -1);
    myMaximumSize = new Dimension(-1, -1);
    myIndent = 0;
  }

  public GridConstraints(
    final int row,
    final int column,
    final int rowSpan,
    final int colSpan,
    final int anchor,
    final int fill,
    final int HSizePolicy,
    final int VSizePolicy,
    final Dimension minimumSize,
    final Dimension preferredSize,
    final Dimension maximumSize) {
    myRow = row;
    myColumn = column;
    myRowSpan = rowSpan;
    myColSpan = colSpan;
    myAnchor = anchor;
    myFill = fill;
    myHSizePolicy = HSizePolicy;
    myVSizePolicy = VSizePolicy;
    myMinimumSize = minimumSize != null ? new Dimension(minimumSize) : new Dimension(-1,-1);
    myPreferredSize = preferredSize != null ? new Dimension(preferredSize) : new Dimension(-1,-1);
    myMaximumSize = maximumSize != null ? new Dimension(maximumSize) : new Dimension(-1,-1);
    myIndent = 0;
  }

  public GridConstraints(
    final int row,
    final int column,
    final int rowSpan,
    final int colSpan,
    final int anchor,
    final int fill,
    final int HSizePolicy,
    final int VSizePolicy,
    final Dimension minimumSize,
    final Dimension preferredSize,
    final Dimension maximumSize,
    final int indent){
    this(row, column, rowSpan, colSpan, anchor, fill, HSizePolicy, VSizePolicy, minimumSize, preferredSize, maximumSize);
    myIndent = indent;
  }

  public GridConstraints(
    final int row,
    final int column,
    final int rowSpan,
    final int colSpan,
    final int anchor,
    final int fill,
    final int HSizePolicy,
    final int VSizePolicy,
    final Dimension minimumSize,
    final Dimension preferredSize,
    final Dimension maximumSize,
    final int indent,
    final boolean useParentLayout){
    this(row, column, rowSpan, colSpan, anchor, fill, HSizePolicy, VSizePolicy, minimumSize, preferredSize, maximumSize, indent);
    myUseParentLayout = useParentLayout;
  }

  /**
   * @return deep copy of the {@link GridConstraints}
   */
  @Override
  public Object clone() {
    return new GridConstraints(
      myRow, myColumn, myRowSpan, myColSpan, myAnchor, myFill, myHSizePolicy, myVSizePolicy,
      new Dimension(myMinimumSize), new Dimension(myPreferredSize), new Dimension(myMaximumSize), myIndent, myUseParentLayout);
  }

  public int getColumn(){
    return myColumn;
  }

  public void setColumn(final int column){
    if (column < 0) {
      throw new IllegalArgumentException("wrong column: " + column);
    }
    myColumn = column;
  }

  public int getRow(){
    return myRow;
  }

  public void setRow(final int row){
    if(row<0){
      throw new IllegalArgumentException("wrong row: "+row);
    }
    myRow = row;
  }

  public int getRowSpan(){
    return myRowSpan;
  }

  public void setRowSpan(final int rowSpan){
    if (rowSpan <= 0) {
      throw new IllegalArgumentException("wrong rowSpan: " + rowSpan);
    }
    myRowSpan = rowSpan;
  }

  public int getColSpan(){
    return myColSpan;
  }

  public void setColSpan(final int colSpan){
    if (colSpan <= 0) {
      throw new IllegalArgumentException("wrong colSpan: " + colSpan);
    }
    myColSpan = colSpan;
  }

  public int getHSizePolicy(){
    return myHSizePolicy;
  }

  public void setHSizePolicy(final int sizePolicy){
    if (sizePolicy < 0 || sizePolicy > 7) {
      throw new IllegalArgumentException("invalid sizePolicy: " + sizePolicy);
    }
    myHSizePolicy = sizePolicy;
  }

  public int getVSizePolicy(){
    return myVSizePolicy;
  }

  public void setVSizePolicy(final int sizePolicy){
    if (sizePolicy < 0 || sizePolicy > 7) {
      throw new IllegalArgumentException("invalid sizePolicy: " + sizePolicy);
    }
    myVSizePolicy = sizePolicy;
  }

  public int getAnchor(){
    return myAnchor;
  }

  public void setAnchor(final int anchor){
    if (anchor < 0 || anchor > 15){
      throw new IllegalArgumentException("invalid anchor: " + anchor);
    }
    myAnchor = anchor;
  }

  public int getFill(){
    return myFill;
  }

  public void setFill(final int fill){
    if (
      fill != FILL_NONE &&
      fill != FILL_HORIZONTAL &&
      fill != FILL_VERTICAL &&
      fill != FILL_BOTH
    ){
      throw new IllegalArgumentException("invalid fill: " + fill);
    }
    myFill = fill;
  }

  public int getIndent() {
    return myIndent;
  }

  public void setIndent(final int indent) {
    myIndent = indent;
  }

  public boolean isUseParentLayout() {
    return myUseParentLayout;
  }

  public void setUseParentLayout(final boolean useParentLayout) {
    myUseParentLayout = useParentLayout;
  }

  public GridConstraints store() {
    final GridConstraints copy = new GridConstraints();

    copy.setRow(myRow);
    copy.setColumn(myColumn);
    copy.setColSpan(myColSpan);
    copy.setRowSpan(myRowSpan);
    copy.setVSizePolicy(myVSizePolicy);
    copy.setHSizePolicy(myHSizePolicy);
    copy.setFill(myFill);
    copy.setAnchor(myAnchor);
    copy.setIndent(myIndent);
    copy.setUseParentLayout(myUseParentLayout);

    copy.myMinimumSize.setSize(myMinimumSize);
    copy.myPreferredSize.setSize(myPreferredSize);
    copy.myMaximumSize.setSize(myMaximumSize);

    return copy;
  }

  public void restore(final GridConstraints constraints) {
    myRow = constraints.myRow;
    myColumn = constraints.myColumn;
    myRowSpan = constraints.myRowSpan;
    myColSpan = constraints.myColSpan;
    myHSizePolicy = constraints.myHSizePolicy;
    myVSizePolicy = constraints.myVSizePolicy;
    myFill = constraints.myFill;
    myAnchor = constraints.myAnchor;
    myIndent = constraints.myIndent;
    myUseParentLayout = constraints.myUseParentLayout;

    // Restore sizes
    myMinimumSize.setSize(constraints.myMinimumSize);
    myPreferredSize.setSize(constraints.myPreferredSize);
    myMaximumSize.setSize(constraints.myMaximumSize);
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (!(o instanceof GridConstraints)) return false;

    final GridConstraints gridConstraints = (GridConstraints)o;

    if (myAnchor != gridConstraints.myAnchor) return false;
    if (myColSpan != gridConstraints.myColSpan) return false;
    if (myColumn != gridConstraints.myColumn) return false;
    if (myFill != gridConstraints.myFill) return false;
    if (myHSizePolicy != gridConstraints.myHSizePolicy) return false;
    if (myRow != gridConstraints.myRow) return false;
    if (myRowSpan != gridConstraints.myRowSpan) return false;
    if (myVSizePolicy != gridConstraints.myVSizePolicy) return false;
    if (!Objects.equals(myMaximumSize, gridConstraints.myMaximumSize)) return false;
    if (!Objects.equals(myMinimumSize, gridConstraints.myMinimumSize)) return false;
    if (!Objects.equals(myPreferredSize, gridConstraints.myPreferredSize)) return false;
    if (myIndent != gridConstraints.myIndent) return false;
    if (myUseParentLayout != gridConstraints.myUseParentLayout) return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result;
    result = myRow;
    result = 29 * result + myColumn;
    result = 29 * result + myRowSpan;
    result = 29 * result + myColSpan;
    result = 29 * result + myVSizePolicy;
    result = 29 * result + myHSizePolicy;
    result = 29 * result + myFill;
    result = 29 * result + myAnchor;
    result = 29 * result + (myMinimumSize != null ? myMinimumSize.hashCode() : 0);
    result = 29 * result + (myPreferredSize != null ? myPreferredSize.hashCode() : 0);
    result = 29 * result + (myMaximumSize != null ? myMaximumSize.hashCode() : 0);
    result = 29 * result + myIndent;
    result = 29 * result + (myUseParentLayout ? 1 : 0);
    return result;
  }

  public int getCell(final boolean isRow){
    return isRow ? getRow() : getColumn();
  }

  public void setCell(final boolean isRow, final int value) {
    if (isRow) {
      setRow(value);
    }
    else {
      setColumn(value);
    }
  }

  public int getSpan(final boolean isRow){
    return isRow ? getRowSpan() : getColSpan();
  }

  public void setSpan(final boolean isRow, final int value) {
    if (isRow) {
      setRowSpan(value);
    }
    else {
      setColSpan(value);
    }
  }

  public boolean contains(final boolean isRow, final int cell) {
    if (isRow) {
      return cell >= myRow && cell < myRow + myRowSpan;
    }
    return cell >= myColumn && cell < myColumn + myColSpan;
  }

  @Override
  public String toString() {
    //noinspection HardCodedStringLiteral
    return "GridConstraints (row=" + myRow + ", col=" + myColumn + ", rowspan=" + myRowSpan + ", colspan=" + myColSpan + ")";
  }
}
