/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
//         www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////

package org.projectforge.web.wicket.bootstrap;

import java.util.HashSet;
import java.util.Set;

import org.apache.wicket.AttributeModifier;
import org.apache.wicket.MarkupContainer;
import org.apache.wicket.markup.repeater.RepeatingView;
import org.apache.wicket.model.IModel;
import org.apache.wicket.model.Model;
import org.projectforge.core.PropUtils;
import org.projectforge.web.HtmlHelper;
import org.projectforge.web.wicket.flowlayout.AbstractGridBuilder;
import org.projectforge.web.wicket.flowlayout.DivPanel;
import org.projectforge.web.wicket.flowlayout.DivTextPanel;
import org.projectforge.web.wicket.flowlayout.FieldProperties;
import org.projectforge.web.wicket.flowlayout.FieldsetPanel;
import org.projectforge.web.wicket.flowlayout.FormHeadingPanel;

/**
 * @author Kai Reinhard (k.reinhard@micromata.de)
 * 
 */
public class GridBuilder extends AbstractGridBuilder<FieldsetPanel>
{
  private static final long serialVersionUID = 4323077384391963834L;

  public static final int MAX_LEVEL = 2;

  private final DivPanel mainContainer;

  private final DivPanel[] rowPanel = new DivPanel[MAX_LEVEL + 1];

  private final DivPanel[] gridPanel = new DivPanel[MAX_LEVEL + 1];

  private int currentLevel = 0;

  private int splitDepth = 1;

  // Counts the length of grid panels of current row. After reaching full length, a new row will be created automatically.
  private final int lengthCounter[] = new int[MAX_LEVEL + 1];

  private Set<String> rowsPanelHelperSet;

  public GridBuilder(final MarkupContainer parent, final String id)
  {
    this(parent, id, true);
  }

  /**
   * @param parent
   * @param id
   * @param fluid Default is true.
   */
  public GridBuilder(final MarkupContainer parent, final String id, final boolean fluid)
  {
    super();
    this.parent = parent;
    mainContainer = new DivPanel(id, fluid == true ? GridType.CONTAINER_FLUID : GridType.CONTAINER);
    parent.add(mainContainer);
  }

  public GridBuilder newGridPanel(final GridType... gridTypes)
  {
    return newGridPanel(0, GridSize.SPAN12, gridTypes);
  }

  public GridBuilder newSplitPanel(final GridSize size, final GridType... gridTypes)
  {
    return newSplitPanel(size, false, gridTypes);
  }

  public GridBuilder newSplitPanel(final GridSize size, final boolean hasSubSplitPanel, GridType... gridTypes)
  {
    if (hasSubSplitPanel == true) {
      splitDepth = 2;
      if (gridTypes == null) {
        gridTypes = new GridType[] { GridType.HAS_CHILDS};
      } else {
        final GridType[] types = new GridType[gridTypes.length + 1];
        for (int i = 0; i < gridTypes.length; i++) {
          types[i] = gridTypes[i];
        }
        types[gridTypes.length] = GridType.HAS_CHILDS;
        gridTypes = types;
      }
    } else {
      splitDepth = 1;
    }
    newGridPanel(0, size, gridTypes);
    if (hasSubSplitPanel == true) {
      // Set the class attribute "row-has-childs":
      if (rowsPanelHelperSet == null) {
        rowsPanelHelperSet = new HashSet<String>();
        rowPanel[0].addCssClasses(GridType.ROW_HAS_CHILDS);
        rowsPanelHelperSet.add(rowPanel[0].getMarkupId());
      } else {
        if (rowsPanelHelperSet.contains(rowPanel[0].getMarkupId()) == false) {
          rowPanel[0].addCssClasses(GridType.ROW_HAS_CHILDS);
          rowsPanelHelperSet.add(rowPanel[0].getMarkupId());
        }
      }
    }
    return this;
  }

  public GridBuilder newSubSplitPanel(final GridSize size, final GridType... gridTypes)
  {
    if (splitDepth < 2) {
      throw new IllegalArgumentException("Dear developer: please call gridBuilder.newSplitPanel(GridSize, true, ...) first!");
    }
    return newGridPanel(1, size, gridTypes);
  }

  /**
   * Sets the gridPanel of the given level as current level. You can only set a lower level than current level, otherwise an exception will
   * be thrown.
   * @param level the currentLevel to set
   * @return this for chaining.
   */
  public GridBuilder setCurrentLevel(final int level)
  {
    if (level > this.currentLevel) {
      throw new IllegalArgumentException("You can only set a lower level than current level, current level is "
          + this.currentLevel
          + ", desired level is "
          + level);
    }
    if (level < 0) {
      throw new IllegalArgumentException("Level must be a positive value: " + level);
    }
    this.currentLevel = level;
    setNullPanel(level, level + 1);
    return this;
  }

  public GridBuilder clear()
  {
    lengthCounter[currentLevel] = 1000;
    return this;
  }

  /**
   * If row panel of this level doesn't exist it will be created.
   * @param level
   * @param size
   * @param gridTypes
   * @return this for chaining.
   */
  private GridBuilder newGridPanel(final int level, final GridSize size, final GridType... gridTypes)
  {
    validateGridPanelLevel(level);
    currentLevel = level;
    if (rowPanel[level] == null) {
      newRowPanel(level);
    }
    boolean firstPanelOfRow = false;
    if (lengthCounter[level] == 0) {
      firstPanelOfRow = true;
    }
    lengthCounter[level] += size.getLength();
    if (lengthCounter[level] > 12) {
      newRowPanel(level);
      lengthCounter[level] = size.getLength();
      firstPanelOfRow = true;
    } else {
      if (firstPanelOfRow == false && gridPanel[level] != null) {
        gridPanel[level].addCssClasses(GridType.HAS_SIBLINGS);
      }
    }
    final DivPanel divPanel = new DivPanel(rowPanel[level].newChildId(), size, gridTypes);
    if (firstPanelOfRow == false) {
      divPanel.addCssClasses(GridType.NOT_FIRST);
    } else {
      divPanel.addCssClasses(GridType.FIRST);
    }
    return addGridPanel(level, divPanel);
  }

  /**
   * If you use this method, the lengthCounter won't be incremented and you use to call setCurrentLevel(level -1) manually.
   * @param level
   * @param divPanel
   * @return
   */
  private GridBuilder addGridPanel(final int level, final DivPanel divPanel)
  {
    validateGridPanelLevel(level);
    currentLevel = level;
    if (rowPanel[level] == null) {
      newRowPanel(level);
    }
    gridPanel[level] = divPanel;
    rowPanel[level].add(divPanel);
    setNullPanel(level + 1, level + 1);
    return this;
  }

  private String newRowPanelId(final int level)
  {
    validateRowPanelLevel(level);
    if (level > 0) {
      return gridPanel[level - 1].newChildId();
    } else {
      return mainContainer.newChildId();
    }
  }

  private GridBuilder newRowPanel(final int level, final GridType... gridTypes)
  {
    validateRowPanelLevel(level);
    final DivPanel rowPanel = new DivPanel(newRowPanelId(level), GridType.ROW);
    rowPanel.addCssClasses(gridTypes);
    return addRowPanel(level, rowPanel);
  }

  private GridBuilder addRowPanel(final int level, final DivPanel rowPanel)
  {
    validateRowPanelLevel(level);
    this.rowPanel[level] = rowPanel;
    lengthCounter[level] = 0;
    if (level > 0) {
      gridPanel[level - 1].add(rowPanel);
    } else {
      mainContainer.add(rowPanel);
    }
    setNullPanel(level + 1, level);
    return this;
  }

  /**
   * @return new child id (Wicket id) of the current grid panel.
   */
  public String newRowId()
  {
    return rowPanel[currentLevel].newChildId();
  }

  /**
   * @return new child id (Wicket id) of the current row panel.
   */
  public String newGridPanelId()
  {
    return gridPanel[currentLevel].newChildId();
  }

  /**
   * @see org.projectforge.web.wicket.flowlayout.GridBuilderInterface#getPanel()
   */
  public DivPanel getPanel()
  {
    if (currentLevel == 0 && gridPanel[currentLevel] == null) {
      newGridPanel(0, GridSize.SPAN12);
    }
    return gridPanel[currentLevel];
  }

  public DivPanel getRowPanel()
  {
    return rowPanel[currentLevel];
  }

  /**
   * @see org.projectforge.web.wicket.flowlayout.GridBuilderInterface#newFormHeading(java.lang.String)
   */
  public FormHeadingPanel newFormHeading(final String label)
  {
    final FormHeadingPanel formHeading = new FormHeadingPanel(getPanel().newChildId(), label);
    getPanel().add(formHeading);
    return formHeading;
  }

  @SuppressWarnings("serial")
  public DivTextPanel newSecurityAdviceBox(final IModel<String> content)
  {
    final DivTextPanel hintBox = new DivTextPanel(getPanel().newChildId(), new Model<String>() {
      @Override
      public String getObject()
      {
        return "<h4>" + getString("securityAdvice") + "</h4>" + HtmlHelper.escapeHtml(content.getObject(), true);
      }
    });
    hintBox.getDiv().add(AttributeModifier.append("class", "alert alert-danger"));
    hintBox.getLabel().setEscapeModelStrings(false);
    getPanel().add(hintBox);
    return hintBox;
  }

  public RepeatingView newRepeatingView()
  {
    final RepeatingView repeater = new RepeatingView(getPanel().newChildId());
    getPanel().add(repeater);
    return repeater;
  }

  /**
   * @see org.projectforge.web.wicket.flowlayout.AbstractGridBuilder#newFieldset(org.projectforge.web.wicket.flowlayout.FieldProperties)
   */
  @Override
  public FieldsetPanel newFieldset(final FieldProperties< ? > fieldProperties)
  {
    return new FieldsetPanel(getPanel(), fieldProperties);
  }

  /**
   * @see org.projectforge.web.wicket.flowlayout.GridBuilderInterface#newFieldset(java.lang.String)
   */
  @Override
  public FieldsetPanel newFieldset(final Class< ? > clazz, final String property)
  {
    return new FieldsetPanel(getPanel(), getString(PropUtils.getI18nKey(clazz, property)));
  }

  /**
   * @see org.projectforge.web.wicket.flowlayout.GridBuilderInterface#newFieldset(java.lang.String)
   */
  @Override
  public FieldsetPanel newFieldset(final String label)
  {
    return new FieldsetPanel(getPanel(), label);
  }

  /**
   * @see org.projectforge.web.wicket.flowlayout.GridBuilderInterface#newFieldset(java.lang.String, java.lang.String)
   */
  @Override
  public FieldsetPanel newFieldset(final String labelText, final String labelDescription)
  {
    return new FieldsetPanel(getPanel(), labelText, labelDescription);
  }

  private void validateRowPanelLevel(final int level)
  {
    if (level < 0 || level > MAX_LEVEL) {
      throw new IllegalArgumentException("Level '" + "' not supported. Value must be between 1 and " + MAX_LEVEL);
    }
    if (level > 0 && gridPanel[level - 1] == null) {
      throw new IllegalArgumentException("Can't add row panel of level '"
          + level
          + "'. Grid panel of level "
          + (level - 1)
          + " doesn't exist!");
    }
  }

  private void validateGridPanelLevel(final int level)
  {
    if (level < 0 || level > MAX_LEVEL) {
      throw new IllegalArgumentException("Level '" + level + "' not supported. Value must be between 0 and " + MAX_LEVEL);
    }
  }

  private void setNullPanel(final int rowFromLevel, final int gridFromLevel)
  {
    for (int i = rowFromLevel; i <= MAX_LEVEL; i++) {
      rowPanel[i] = null;
      lengthCounter[i] = 0;
    }
    for (int i = gridFromLevel; i <= MAX_LEVEL; i++) {
      gridPanel[i] = null;
    }
  }
}
