/**
 * ===========================================
 * LibLayout : a free Java layouting library
 * ===========================================
 *
 * Project Info:  http://reporting.pentaho.org/liblayout/
 *
 * (C) Copyright 2006-2007, by Pentaho Corporation and Contributors.
 *
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ------------
 * $Id: LogicalPageBox.java 6653 2008-12-02 14:53:40Z tmorgner $
 * ------------
 * (C) Copyright 2006-2007, by Pentaho Corporation.
 */
package org.jfree.layouting.renderer.model.page;

import java.util.Arrays;

import org.jfree.layouting.renderer.model.BlockRenderBox;
import org.jfree.layouting.renderer.model.EmptyBoxDefinition;
import org.jfree.layouting.renderer.model.NormalFlowRenderBox;
import org.jfree.layouting.renderer.model.PageAreaRenderBox;
import org.jfree.layouting.renderer.model.RenderBox;
import org.jfree.layouting.renderer.model.RenderNode;
import org.jfree.layouting.renderer.model.NodeLayoutProperties;
import org.jfree.layouting.layouter.context.LayoutContext;
import org.jfree.layouting.output.OutputProcessorMetaData;
import org.jfree.layouting.namespace.Namespaces;

/**
 * The logical page box does not have a layout at all. It has collection of
 * flows, one for each <b>logical</b> page area.
 * <p/>
 * Each logical page has a header (for the repeating group headers, not to be
 * mistaken as PageHeader!), content and footer area. The footer area contains
 * the foot-notes at the very bottom, the repeatable footer area and finally the
 * flow:bottom area.
 * <p/>
 * The flow- and repeatable areas behave like stacks, and are filled from the
 * content area (so for headers, new content goes after any previous content,
 * and for footers, new content goes before any previous content.)
 * <p/>
 * The footNotes section is always filled in the normal-order, so new content
 * gets added at the bottom of that area.
 * <p/>
 * The logical page also holds the absolutely and static positioned elements.
 * These elements may overlap the repeating headers, but will never overlap the
 * physical page header or footer.
 * <p/>
 * The logical page is also the container for all physical pages. (The sizes of
 * the physical pages influence the available space on the logical pages.)
 * <p/>
 * Layout notes: The logical page, as it is implemented now, consists of three
 * areas. The header and footer areas are plain banded areas and correspond to
 * the @top and @bottom page-areas. These areas are layouted zero-based. The
 * header's y=0 corresponds to the beginning of the page, while the footer's y=0
 * depends on the total height of the footer and must fullfill the following
 * constraint: Page_bottom_edge = (footer_y + footer_height).
 * <p/>
 * The content window is positioned relative to the content-flow and is placed
 * between the header and the footer. The mapping of the content into the header
 * is defined by the pageOffset. The content-window's y=0 corresponds to the
 * normal-flow's y=pageOffset.
 * <p/>
 * Repeatable headers and footers are *not* inserted into the content-flow.
 *
 * @author Thomas Morgner
 */
public class LogicalPageBox extends BlockRenderBox
{
  //private ArrayList subFlows;
  private PageGrid pageGrid;

  private long[] pageWidths;
  private long[] pageHeights;
  private long[] horizontalBreaks;
  private long[] verticalBreaks;
  private long pageWidth;
  private long pageHeight;

  //private long offset;
  private Object contentAreaId;
  private PageAreaRenderBox footerArea;
  private PageAreaRenderBox headerArea;

  private long pageOffset;
  private boolean normalFlowActive;

  public LogicalPageBox(final PageGrid pageGrid)
  {
    super(EmptyBoxDefinition.getInstance());

    if (pageGrid == null)
    {
      throw new NullPointerException("PageGrid must not be null");
    }

    //this.subFlows = new ArrayList();
    final NormalFlowRenderBox contentArea =
        new NormalFlowRenderBox(EmptyBoxDefinition.getInstance());
    this.contentAreaId = contentArea.getInstanceId();
    this.headerArea = new PageAreaRenderBox(EmptyBoxDefinition.getInstance());
    this.headerArea.setParent(this);
    this.footerArea = new PageAreaRenderBox(EmptyBoxDefinition.getInstance());
    this.footerArea.setParent(this);

    updatePageArea(pageGrid);

    addChild(contentArea);

    setMajorAxis(VERTICAL_AXIS);
    setMinorAxis(HORIZONTAL_AXIS);

    final NodeLayoutProperties nodeLayoutProperties = getNodeLayoutProperties();
    nodeLayoutProperties.setNamespace(Namespaces.LIBLAYOUT_NAMESPACE);
    nodeLayoutProperties.setTagName("logical-page");
  }

  public void appyStyle(final LayoutContext context, final OutputProcessorMetaData metaData)
  {
    super.appyStyle(context, metaData);
    final NodeLayoutProperties nodeLayoutProperties = getNodeLayoutProperties();
    nodeLayoutProperties.setNamespace(Namespaces.LIBLAYOUT_NAMESPACE);
    nodeLayoutProperties.setTagName("logical-page");
  }

  public void updatePageArea(final PageGrid pageGrid)
  {
    if (pageGrid == null)
    {
      throw new NullPointerException();
    }

    this.pageGrid = pageGrid;
    this.pageHeights = new long[pageGrid.getColumnCount()];
    this.pageWidths = new long[pageGrid.getRowCount()];
    this.horizontalBreaks = new long[pageGrid.getColumnCount()];
    this.verticalBreaks = new long[pageGrid.getRowCount()];

    Arrays.fill(pageHeights, Long.MAX_VALUE);
    Arrays.fill(pageWidths, Long.MAX_VALUE);

    // todo: This is invalid right now. The page grids content area must be used

    for (int row = 0; row < pageGrid.getRowCount(); row++)
    {
      for (int col = 0; col < pageGrid.getColumnCount(); col++)
      {
        final PhysicalPageBox box = pageGrid.getPage(row, col);
        pageHeights[row] = Math.min(pageHeights[row], box.getImageableHeight());
        pageWidths[col] = Math.min(pageWidths[col], box.getImageableWidth());
      }
    }

    pageHeight = 0;
    for (int i = 0; i < pageHeights.length; i++)
    {
      pageHeight += pageHeights[i];
      verticalBreaks[i] = pageHeight;
    }

    pageWidth = 0;
    for (int i = 0; i < pageWidths.length; i++)
    {
      pageWidth += pageWidths[i];
      horizontalBreaks[i] = pageWidth;
    }
  }

  public NormalFlowRenderBox getContentArea()
  {
    return (NormalFlowRenderBox) findNodeById(contentAreaId);
  }

  public PageAreaRenderBox getFooterArea()
  {
    return footerArea;
  }

  public PageAreaRenderBox getHeaderArea()
  {
    return headerArea;
  }
//
//  public void addAbsoluteFlow(NormalFlowRenderBox flow)
//  {
//    subFlows.add(flow);
//  }
//
//  public NormalFlowRenderBox[] getAbsoluteFlows()
//  {
//    return (NormalFlowRenderBox[])
//        subFlows.toArray(new NormalFlowRenderBox[subFlows.size()]);
//  }
//
//  public NormalFlowRenderBox getAbsoluteFlow(int i)
//  {
//    return (NormalFlowRenderBox) subFlows.get(i);
//  }
//
//  public int getAbsoluteFlowCount()
//  {
//    return subFlows.size();
//  }

  public LogicalPageBox getLogicalPage()
  {
    return this;
  }

  public NormalFlowRenderBox getNormalFlow()
  {
    return getContentArea();
  }

  public PageGrid getPageGrid()
  {
    return pageGrid;
  }

  public RenderBox getInsertationPoint()
  {
    return getContentArea().getInsertationPoint();
  }

  public long[] getPhysicalBreaks(final int axis)
  {
    if (axis == HORIZONTAL_AXIS)
    {
      return (long[]) horizontalBreaks.clone();
    }
    return (long[]) verticalBreaks.clone();
  }

  public boolean isOverflow()
  {
    return false;
  }

  /**
   * Derive creates a disconnected node that shares all the properties of the
   * original node. The derived node will no longer have any parent, silbling,
   * child or any other relationships with other nodes.
   *
   * @return
   */
  public RenderNode deriveFrozen(final boolean deepDerive)
  {
    final LogicalPageBox box = (LogicalPageBox) super.deriveFrozen(deepDerive);
    box.headerArea = (PageAreaRenderBox) headerArea.deriveFrozen(deepDerive);
    box.footerArea = (PageAreaRenderBox) footerArea.deriveFrozen(deepDerive);
//
//    box.subFlows.clear();
//
//    for (int i = 0; i < subFlows.size(); i++)
//    {
//      NormalFlowRenderBox flowRenderBox = (NormalFlowRenderBox) subFlows.get(i);
//      box.subFlows.add(flowRenderBox.deriveFrozen(deepDerive));
//    }

    return box;
  }

  /**
   * Derive creates a disconnected node that shares all the properties of the
   * original node. The derived node will no longer have any parent, silbling,
   * child or any other relationships with other nodes.
   *
   * @return
   */
  public RenderNode derive(final boolean deepDerive)
  {
    final LogicalPageBox box = (LogicalPageBox) super.derive(deepDerive);
    box.headerArea = (PageAreaRenderBox) headerArea.derive(deepDerive);
    box.footerArea = (PageAreaRenderBox) footerArea.derive(deepDerive);
//    box.subFlows.clear();
//
//    for (int i = 0; i < subFlows.size(); i++)
//    {
//      NormalFlowRenderBox flowRenderBox = (NormalFlowRenderBox) subFlows.get(i);
//      box.subFlows.add(flowRenderBox.derive(deepDerive));
//    }

    return box;
  }

  /**
   * Derives an hibernation copy. The resulting object should get stripped of
   * all unnecessary caching information and all objects, which will be
   * regenerated when the layouting restarts. Size does matter here.
   *
   * @return
   */
  public RenderNode hibernate()
  {
    final LogicalPageBox box = (LogicalPageBox) super.hibernate();
    box.headerArea = (PageAreaRenderBox) headerArea.hibernate();
    box.footerArea = (PageAreaRenderBox) footerArea.hibernate();

//    box.subFlows.clear();
//
//    for (int i = 0; i < subFlows.size(); i++)
//    {
//      NormalFlowRenderBox flowRenderBox = (NormalFlowRenderBox) subFlows.get(i);
//      box.subFlows.add(flowRenderBox.hibernate());
//    }

    return box;
  }

  /**
   * Clones this node. Be aware that cloning can get you into deep trouble, as
   * the relations this node has may no longer be valid.
   *
   * @return
   */
  public Object clone()
  {
    try
    {
      final LogicalPageBox o = (LogicalPageBox) super.clone();
      o.pageHeights = (long[]) pageHeights.clone();
      o.pageWidths = (long[]) pageWidths.clone();
      o.pageGrid = (PageGrid) pageGrid.clone();
//      o.subFlows = (ArrayList) subFlows.clone();
      return o;
    }
    catch (CloneNotSupportedException e)
    {
      throw new IllegalStateException("Cloning *must* be supported.");
    }
  }

  public boolean isNormalFlowActive()
  {
    return normalFlowActive;
  }

  public long getPageHeight()
  {
    return pageHeight;
  }

  public long getPageWidth()
  {
    return pageWidth;
  }

  public long getPageOffset()
  {
    return pageOffset;
  }

  public void setPageOffset(final long pageOffset)
  {
    this.pageOffset = pageOffset;
  }

  public void setNormalFlowActive(final boolean normalFlowActive)
  {
    this.normalFlowActive = normalFlowActive;
  }

  public void insertFirst(final RenderNode node)
  {
    insertBefore(getFirstChild(), node);
  }

  public void insertLast(final RenderNode node)
  {
    insertAfter(getLastChild(), node);
  }

  public RenderNode findNodeById(final Object instanceId)
  {
    // quick check
    final RenderNode footerNode = footerArea.findNodeById(instanceId);
    if (footerNode != null)
    {
      return footerNode;
    }

    final RenderNode headerNode = headerArea.findNodeById(instanceId);
    if (headerNode != null)
    {
      return headerNode;
    }
    // then depth-first for ordinary content.
    return super.findNodeById(instanceId);
  }
}
