/*
 * Copyright 2000-2014 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.ui.components.panels;

import com.intellij.util.ui.JBInsets;

import javax.swing.SwingConstants;
import java.awt.Component;
import java.awt.Container;
import java.awt.Dimension;
import java.awt.Insets;
import java.awt.LayoutManager2;
import java.util.ArrayList;

/**
 * This class is intended to lay out added components horizontally.
 * It allows to add them into the LEFT, CENTER, or RIGHT group, which are aligned separately.
 * Every group can contain any amount of components. The specified gap is added between components,
 * and the double gap is added between groups of components.
 * <p><b>NB!: this class must be modified together with the <code>VerticalLayout</code> class accordingly</b></p>
 *
 * @author Sergey.Malenkov
 * @see VerticalLayout
 */
public final class HorizontalLayout implements LayoutManager2 {
  public static final String LEFT = "LEFT";
  public static final String RIGHT = "RIGHT";
  public static final String CENTER = "CENTER";

  private final ArrayList<Component> myLeft = new ArrayList<>();
  private final ArrayList<Component> myRight = new ArrayList<>();
  private final ArrayList<Component> myCenter = new ArrayList<>();
  private final int myAlignment;
  private final int myGap;

  /**
   * Creates a layout with the specified gap.
   * All components will have preferred widths,
   * but their heights will be set according to the container.
   *
   * @param gap horizontal gap between components
   */
  public HorizontalLayout(int gap) {
    myGap = gap;
    myAlignment = -1;
  }

  /**
   * Creates a layout with the specified gap and vertical alignment.
   * All components will have preferred sizes.
   *
   * @param gap       horizontal gap between components
   * @param alignment vertical alignment for components
   *
   * @see SwingConstants#TOP
   * @see SwingConstants#BOTTOM
   * @see SwingConstants#CENTER
   */
  public HorizontalLayout(int gap, int alignment) {
    myGap = gap;
    switch (alignment) {
      case SwingConstants.TOP:
      case SwingConstants.BOTTOM:
      case SwingConstants.CENTER:
        myAlignment = alignment;
        break;
      default:
        throw new IllegalArgumentException("unsupported alignment: " + alignment);
    }
  }

  @Override
  public void addLayoutComponent(Component component, Object constraints) {
    if ((constraints == null) || (constraints instanceof String)) {
      addLayoutComponent((String)constraints, component);
    }
    else {
      throw new IllegalArgumentException("unsupported constraints: " + constraints);
    }
  }

  @Override
  public Dimension maximumLayoutSize(Container target) {
    return new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE);
  }

  @Override
  public float getLayoutAlignmentX(Container target) {
    return .5f;
  }

  @Override
  public float getLayoutAlignmentY(Container target) {
    return .5f;
  }

  @Override
  public void invalidateLayout(Container target) {
  }

  @Override
  public void addLayoutComponent(String name, Component component) {
    synchronized (component.getTreeLock()) {
      if (name == null || LEFT.equalsIgnoreCase(name)) {
        myLeft.add(component);
      }
      else if (CENTER.equalsIgnoreCase(name)) {
        myCenter.add(component);
      }
      else if (RIGHT.equalsIgnoreCase(name)) {
        myRight.add(component);
      }
      else {
        throw new IllegalArgumentException("unsupported name: " + name);
      }
    }
  }

  @Override
  public void removeLayoutComponent(Component component) {
    myLeft.remove(component);
    myRight.remove(component);
    myCenter.remove(component);
  }

  @Override
  public Dimension preferredLayoutSize(Container container) {
    return getPreferredSize(container, true);
  }

  @Override
  public Dimension minimumLayoutSize(Container container) {
    return getPreferredSize(container, false);
  }

  @Override
  public void layoutContainer(Container container) {
    synchronized (container.getTreeLock()) {
      Dimension left = getPreferredSize(myLeft);
      Dimension right = getPreferredSize(myRight);
      Dimension center = getPreferredSize(myCenter);

      Insets insets = container.getInsets();
      int width = container.getWidth() - insets.left - insets.right;
      int height = container.getHeight() - insets.top - insets.bottom;

      int leftX = 0;
      if (left != null) {
        leftX = myGap + layout(myLeft, 0, height, insets);
      }
      int rightX = width;
      if (right != null) {
        rightX -= right.width;
      }
      if (rightX < leftX) {
        rightX = leftX;
      }
      if (center != null) {
        int centerX = (width - center.width) / 2;
        if (centerX > leftX) {
          int centerRightX = centerX + center.width + myGap + myGap;
          if (centerRightX > rightX) {
            centerX = rightX - center.width - myGap - myGap;
          }
        }
        if (centerX < leftX) {
          centerX = leftX;
        }
        centerX = myGap + layout(myCenter, centerX, height, insets);
        if (rightX < centerX) {
          rightX = centerX;
        }
      }
      if (right != null) {
        layout(myRight, rightX, height, insets);
      }
    }
  }

  private int layout(ArrayList<Component> list, int x, int height, Insets insets) {
    for (Component component : list) {
      if (component.isVisible()) {
        Dimension size = component.getPreferredSize();
        int y = 0;
        if (myAlignment == -1) {
          size.height = height;
        }
        else if (myAlignment != SwingConstants.TOP) {
          y = height - size.height;
          if (myAlignment == SwingConstants.CENTER) {
            y /= 2;
          }
        }
        component.setBounds(x + insets.left, y + insets.top, size.width, size.height);
        x += size.width + myGap;
      }
    }
    return x;
  }

  private static Dimension join(Dimension result, int gap, Dimension size) {
    if (size == null) {
      return result;
    }
    if (result == null) {
      return new Dimension(size);
    }
    result.width += gap + size.width;
    if (result.height < size.height) {
      result.height = size.height;
    }
    return result;
  }

  private Dimension getPreferredSize(ArrayList<Component> list) {
    Dimension result = null;
    for (Component component : list) {
      if (component.isVisible()) {
        result = join(result, myGap, component.getPreferredSize());
      }
    }
    return result;
  }

  private Dimension getPreferredSize(Container container, boolean aligned) {
    synchronized (container.getTreeLock()) {
      Dimension left = getPreferredSize(myLeft);
      Dimension right = getPreferredSize(myRight);
      Dimension center = getPreferredSize(myCenter);
      Dimension result = join(join(join(null, myGap + myGap, left), myGap + myGap, center), myGap + myGap, right);
      if (result == null) {
        result = new Dimension();
      }
      else if (aligned && center != null) {
        int leftWidth = left == null ? 0 : left.width;
        int rightWidth = right == null ? 0 : right.width;
        result.width += Math.abs(leftWidth - rightWidth);
      }
      JBInsets.addTo(result, container.getInsets());
      return result;
    }
  }
}
