package org.jcon.ui.easylayout;

import java.awt.Container;
import java.awt.Component;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.Insets;
import java.awt.Label;
import java.util.Hashtable;
import java.util.Stack;

import javax.swing.JFrame;
import javax.swing.JWindow;

/**
* Provides an infinitely easier to use GridBagLayout. This
* is done by replacing the constraints abstraction with
* properties, using more appropriate, understandable and
* object oriented methods, providing named styles, and for
* the advanced developer, handling nested containers and
* library styles. The low usability of GridBagLayout is
* completely hidden.
*
* First call layout.setTopContainer(). After that the key
* method is add(). When a new row is desired use
* addEndRow(). Properties used when a component is
* added are easily set, and remain in effect until changed.
* Run and study class EasyLayoutExamples for usage examples.
*
* Named styles are supported, and are useful for groups
* of adds that should use the same style, such as a column.
* Styles are also useful for widespread reuse, for
* efficient coding and enforcement of consistency.
*
* A "Default" style is built in. It is very useful
* for returning to a known state, as are other styles,
* The Default style is immutable. It has property values
* identical to the GridBagConstraints defaults: <pre> <p> <p>
*
*   Property      Default     Constraint
*
* Alignment     "Center"      anchor
* Stretch       "None"        fill
*
* WidthWeight      0          weightx
* HeightWeight     0          weighty
* Row              0          gridy
* Column           0          gridx
*
* InsetTop         0          insets.top
* InsetLeft        0          insets.left
* InsetBottom      0          insets.bottom
* InsetRight       0          insets.right
*
* GridWidth        1          gridwidth
* GridHeight       1          gridheight
* ExtraSizeWidth   0          ipadx
* ExtraSizeHeight  0          ipady   </pre> <p> <p>
*
* This class is designed to be used by parameter driven
* classes or configured, which explains the use of strings
* instead of constants. The strings are case sensitive.
*
* @author Jack Harich
*/
public class EasyLayout implements java.io.Serializable {

//---------- Protected Fields ----------------------------
protected GridBagLayout      gbLayout = new GridBagLayout();
protected EasyLayoutStyle    currentStyle = new EasyLayoutStyle("Default");
protected EasyLayoutLibrary  library; // null if none

protected Container topContainer;
protected Container currentContainer;
protected Component lastComponentAdded;
protected Stack     nestedStack = new Stack();

// Key = style name, Object = EasyLayoutStyle
protected Hashtable styles = new Hashtable();

//---------- Initialization ------------------------------
public EasyLayout() {
    // Create Default style
    styles.put("Default", new EasyLayoutStyle("Default"));
    // Set initial style
    useStyle("Default");
}
//---------- Properties ----------------------------------
//----- topContainer
/**
 * Sets the topContainer that components will be added to
 * and clears all nested containers. This must be done
 * before adding components, or to start a new
 * topContainer. Thus this class can layout multiple
 * top containers.
 */

public void setTopContainer(Container topContainer) {
    this.topContainer = topContainer;
    nestedStack.removeAllElements(); // Precaution
    lastComponentAdded = topContainer;
    setCurrentContainer(topContainer);
}

public Container getTopContainer() {
    return topContainer;
}
//---------- Style properties
/**
 * See EasyLayoutStyle for documentation on style properties.
 */
 //----- Alignment
public void setAlignment(String alignment) {
    currentStyle.setAlignment(alignment);
}
public String getAlignment() {
    return currentStyle.getAlignment();
}
//----- Insets
public void setInsetTop(int insetTop) {
    currentStyle.setInsetTop(insetTop);
}
public void setInsetLeft(int insetLeft) {
    currentStyle.setInsetLeft(insetLeft);
}
public void setInsetBottom(int insetBottom) {
    currentStyle.setInsetBottom(insetBottom);
}
public void setInsetRight(int insetRight) {
    currentStyle.setInsetRight(insetRight);
}
/**
 * Sets the 4 Inset properties.
 */
public void setInsets(int top, int left, int bottom, int right) {
    setInsetTop(top);
    setInsetLeft(left);
    setInsetBottom(bottom);
    setInsetRight(right);
}
public Insets getInsets() {
    return currentStyle.getInsets();
}
//----- Stretch
public void setStretch(String stretch) {
    currentStyle.setStretch(stretch);
}
public String getStretch() {
    return currentStyle.getStretch();
}
//----- WidthWeight
public void setWidthWeight(double widthWeight) {
  currentStyle.setWidthWeight(widthWeight);
}
public double getWidthWeight() {
    return currentStyle.getWidthWeight();
}
//----- HeightWeight
public void setHeightWeight(double heightWeight) {
  currentStyle.setHeightWeight(heightWeight);
}
public double getHeightWeight() {
    return currentStyle.getHeightWeight();
}
//----- Column
public void setColumn(int column) {
    currentStyle.setColumn(column);
}
public int getColumn() {
    return currentStyle.getColumn();
}
//----- Row
public void setRow(int row) {
    currentStyle.setRow(row);
}
public int getRow() {
    return currentStyle.getRow();
}
//----- Column and Row
/**
 * A convenience method that sets the Row and Column.
 */
public void setRowColumn(int row, int column) {
    currentStyle.setRowColumn(row, column);
}
//----- GridWidth
public void setGridWidth(int gridWidth) {
    currentStyle.setGridWidth(gridWidth);
}
public int getGridWidth() {
    return currentStyle.getGridWidth();
}
//----- GridHeight
public void setGridHeight(int gridHeight) {
    currentStyle.setGridHeight(gridHeight);
}
public int getGridHeight() {
    return currentStyle.getGridHeight();
}
//----- GridWidth and GridHeight
public void setGridWidthHeight(int gridWidth, int gridHeight) {
    currentStyle.setGridWidthHeight(gridWidth, gridHeight);
}
//----- ExtraSizeWidth
public void setExtraSizeWidth(int extraSizeWidth) {
    currentStyle.setExtraSizeWidth(extraSizeWidth);
}
public int getExtraSizeWidth() {
    return currentStyle.getExtraSizeWidth();
}
//----- ExtraSizeHeight
public void setExtraSizeHeight(int extraSizeHeight) {
    currentStyle.setExtraSizeHeight(extraSizeHeight);
}
public int getExtraSizeHeight() {
    return currentStyle.getExtraSizeHeight();
}
//----- Other
/**
 * Returns the current container. If in a nested container
 * block this is that nested container. Otherwise it is
 * the top container
 */
public Container getCurrentContainer() {
    return currentContainer;
}
/**
 * Sets the library to be used for styles if not found in
 * this layout. If this property is null then no library is
 * used. The initial value is null.
 */
public void setLibrary(EasyLayoutLibrary library) {
    this.library = library;
}
public EasyLayoutLibrary getLibrary() {
    return library;
}
//---------- Public Methods ------------------------------
//----- Add Variations
/**
 * Adds the component using the current properties. This
 * convenience method is the same as add(component, null, null)
 */
public void add(Component component) {
    add(component, null, null);
}
/**
 * Adds the component using the named style. This
 * convenience method is the same as add(component, styleName, null)
 */
public void add(Component component, String styleName) {
    add(component, styleName, null);
}
/**
 * Adds the component and ends the row using the current
 * properties. This convenience method that is the same as
 * add(Component, null, "EndRow"). It restores the
 * GridWidth after the component is added.
 */
public void addEndRow(Component comp) {
    int previousGridWidth = currentStyle.getGridWidth();
    currentStyle.setGridWidth(GridBagConstraints.REMAINDER);
    addComponent(comp);
    currentStyle.setGridWidth(previousGridWidth);
}
/**
 * Adds the component and ends the column using the current
 * properties. This convenience method that is the same as
 * add(Component, null, "EndColumn"). It restores the
 * GridHeight after the component is added.
 */
public void addEndColumn(Component comp) {
    int previousGridHeight = currentStyle.getGridHeight();
    currentStyle.setGridHeight(GridBagConstraints.REMAINDER);
    addComponent(comp);
    currentStyle.setGridHeight(previousGridHeight);
}
/**
 * Adds the component using the named style and addType.
 * If styleName is null then the current properties are
 * used. Valid values for addType are null, "Normal",
 * "EndRow", "EndColumn" or "EndColumnRow". The GridWidth
 * and GridHeight are restored after the component is added.
 *
 * null or "Normal" - Adds the component to the next cell
 * or the cell specified with the Column and Row properties.
 *
 * "EndRow" - Adds the component and ends the row.
 *
 * "EndColumn" - Adds the component and ends the column.
 *
 * "EndColumnRow" - Adds the component and ends the column
 * and row. The author has never encountered needing this.
 */
public void add(Component component, String styleName,
        String addType) {

    int previousGridWidth = currentStyle.getGridWidth();
    int previousGridHeight = currentStyle.getGridHeight();

    if (styleName != null) useStyle(styleName);

    if (addType == null || addType.equals("Normal")) {
        // Do nothing, assume gridWidth, gridHeight okay

    } else if (addType.equals("EndRow")) {
        currentStyle.setGridWidth(GridBagConstraints.REMAINDER);

    } else if (addType.equals("EndColumn")) {
        currentStyle.setGridHeight(GridBagConstraints.REMAINDER);

    } else if (addType.equals("EndColumnRow")) {
        currentStyle.setGridWidth(GridBagConstraints.REMAINDER);
        currentStyle.setGridHeight(GridBagConstraints.REMAINDER);
    } else {
        throw new IllegalArgumentException("Unknown addType '" + addType + "'.");
    }
    addComponent(component);
    // Restore properties
    currentStyle.setGridWidth(previousGridWidth);
    currentStyle.setGridHeight(previousGridHeight);
}
/**
 * Adds an invisible placeholder component. Useful for
 * putting no components on the left but some on the right,
 * or none on the top but some on the bottom, without
 * having to use cell indexes. Currently done with a Label
 * with no text, which has the drawback of non-zero size.
 *
 * Currently the same as add(new Label()). ***
 */
public void addNothing() {
    add(new Label());
}
//----- Nested Container Feature
/**
 * Begins a nested container block. All subsequent adds
 * will use the last component added, which must be a
 * Container. Nested containers are intended to allow a
 * more concise and readable implementation, and should use
 * indentation to show nested components.
 *
 * Throws an IllegalStateException if the last Component
 * added was not a Container.
 */
public void beginNestedContainer() {
    if (! (lastComponentAdded instanceof Container)) {
        throw new IllegalStateException("Last Component" +
         " added was not a Container.\nIt was " + lastComponentAdded);
    }
    addToNestedStack();
    setCurrentContainer((Container)lastComponentAdded);
}
private void addToNestedStack() {
    Nested nested = new Nested();
    nested.container = currentContainer;
    nested.style = cloneCurrentStyle();
    //print(" - pushing " + currentContainer.getName() + ", " + nested.style.getName());
    nestedStack.push(nested);
}
/**
 * Ends a nested container block. All subsequent adds
 * will use the previous nested container and style, all
 * the way back up to the topContainer provided in
 * setTopContainer().
 *
 * Calling this method when there is no nested container
 * will throw an IllegalStateException, since this
 * invariably indicates faulty code.
 */
public void endNestedContainer() { // 99
    // topContainer is always the first in the stack
    if (nestedStack.isEmpty()) {
        throw new IllegalStateException("There is no" +
            " current nested container.\nYou have a faulty"
            + " nested block in your code.");
    }
    // Restore previous container and style
    Nested nested = (Nested)nestedStack.pop();
    setCurrentContainer(nested.container);
    useStyle(nested.style.getName());

    //print(" - restored " + currentContainer.getName() +
    //", " + currentStyle.getName() + " size = " + nestedStack.size());
}
//----- Style Related Methods
/**
 * Stores the current properties in a named style
 * for future use. Any existing style by the same
 * name is overwritten.
 */
public void storeCurrentStyle(String styleName) {
    validateStyleMutation(styleName);
    EasyLayoutStyle style = cloneCurrentStyle();
    style.setName(styleName);
    styles.put(styleName, style);
}
/**
 * Clones and stores the provided style for future use. Any
 * existing style by the same name is overwritten.
 */
public void storeStyle(String styleName, EasyLayoutStyle style) {
    validateStyleMutation(styleName);
    EasyLayoutStyle newStyle = style.createClone();
    newStyle.setName(styleName);
    styles.put(styleName, newStyle);
}
/**
 * Retreives, clones and uses the named style to set all
 * properties to the current style's. First the layout's
 * styles are searched.
 * If not found and library is not null then the
 ( EasyLayoutLibrary is used to get the style. Throws an
 * IllegalArgumentException if the styleName is not found.
 *
 * Note using the "Default" style has the effect of setting
 * all properties to original values.
 */
public void useStyle(String styleName) {
    // Retreive style
    EasyLayoutStyle style = (EasyLayoutStyle)styles.get(styleName);
    if (style == null && library != null) {
        style = library.getStyle(styleName);
    }
    if (style == null) {
        throw new IllegalArgumentException("Style '" +
            styleName + "' not found.");
    }
    currentStyle = style.createClone();
}
/**
 * Returns a clone of the current style. To create a new
 * style, set properties, call cloneCurrentStyle() and then
 * set the name. Or better yet, use createStyle().
 */
public EasyLayoutStyle cloneCurrentStyle() {
    return currentStyle.createClone();
}
/**
 * Returns a clone of the current style, with its name set
 * to styleName. This is the easiest to create new styles
 * using EasyLayout. One can also create them directly with
 * EasyLayoutStyle. This is a convenience method.
 */
public EasyLayoutStyle createStyle(String styleName) {
    EasyLayoutStyle style = cloneCurrentStyle();
    style.setName(styleName);
    return style;
}
public String currentStyleToString() {
    return currentStyle.toString();
}
//---------- Private Methods -----------------------------
private void addComponent(Component comp) {
    gbLayout.setConstraints(comp, currentStyle.getConstraints());
    
    // Handle Swing changes adroitly :-)
    if (currentContainer instanceof JFrame) {
        ((JFrame)currentContainer).getContentPane().add(comp);
        
    } else if (currentContainer instanceof JWindow) {
        ((JWindow)currentContainer).getContentPane().add(comp);
    
    } else {
        currentContainer.add(comp);
    }        
    lastComponentAdded = comp;
}
private void setCurrentContainer(Container container) {
    currentContainer = container;
    
    // Handle Swing changes adroitly :-)
    if (currentContainer instanceof JFrame) {
        ((JFrame)currentContainer).getContentPane().setLayout(gbLayout);
        
    } else if (currentContainer instanceof JWindow) {
        ((JWindow)currentContainer).getContentPane().setLayout(gbLayout);
        
    } else {
        currentContainer.setLayout(gbLayout);
    }
}
private void validateStyleMutation(String styleName) {
    if (styleName.equals("Default")) {
        throw new IllegalArgumentException(
            "Cannot overwrite or shadow the 'Default' style.");
    }
}
//--- Std
private static void print(String text) {
    System.out.println("EasyLayout" + text);
}
//========== Inner Classes ===============================
class Nested {
    Container       container;
    EasyLayoutStyle style;

} // End inner class

} // End outer class