/*
 * Copyright 2003-2011 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 jetbrains.mps.nodeEditor.cellLayout;

import jetbrains.mps.editor.runtime.TextBuilderImpl;
import jetbrains.mps.editor.runtime.impl.LayoutConstraints;
import jetbrains.mps.editor.runtime.style.StyleAttributes;
import jetbrains.mps.logging.Logger;
import jetbrains.mps.nodeEditor.EditorSettings;
import jetbrains.mps.nodeEditor.cells.APICellAdapter;
import jetbrains.mps.nodeEditor.cells.GeometryUtil;
import jetbrains.mps.openapi.editor.TextBuilder;
import jetbrains.mps.openapi.editor.cells.CellTraversalUtil;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCell_Collection;
import org.apache.log4j.LogManager;

import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

/**
 * User: Sergey Dmitriev
 * Date: Jan 19, 2005
 */
public class CellLayout_Flow extends AbstractCellLayout {

  private static Logger LOG = Logger.wrap(LogManager.getLogger(CellLayout_Flow.class));

  /*
         wStart
        <------>
       |--------|------------------|
       |--------| blah blah blah   | 
       |blah blah blah  |----------|
       |----------------|----------|
       <---- wEnd------>
  */

  private void init() {
    myWStart = 0;
    myWEnd = 0;
    myRowCount = 1;
    myMaxDescent = 0;
    myMaxAscent = 0;
    myFirstLineHeight = -1;
    myBossLayout = null;
    myCurrentLineLayouts = new HashSet<CellLayout_Flow>();
    myCurrentLine = new ArrayList<EditorCell>();
  }

  private int myWStart = 0;
  private int myWEnd = 0;
  private int myRowCount = 1;
  private int myMaxDescent = 0;
  private int myMaxAscent = 0;
  private int myFirstLineHeight = -1;

  private CellLayout_Flow myBossLayout = null;

  private Set<CellLayout_Flow> myCurrentLineLayouts = new HashSet<CellLayout_Flow>();
  private java.util.List<EditorCell> myCurrentLine = new ArrayList<EditorCell>();


  private void setWStart(int WStart) {
    myWStart = WStart;
  }

  public int getWStart() {
    return myWStart;
  }

  public int getWEnd() {
    return myWEnd;
  }

  private void setBossLayout(CellLayout_Flow bossLayout) {
    myBossLayout = bossLayout;
  }


  private int getMaxX() {
    return EditorSettings.getInstance().getVerticalBoundWidth();
  }


  @Override
  public void doLayout(EditorCell_Collection editorCells) {
    if (myBossLayout == null) init();
    new FlowLayouter(editorCells).doLayout();
  }

  private void setMaxDescent(int maxDescent) {
    this.myMaxDescent = maxDescent;
  }

  private void setMaxAscent(int maxAscent) {
    this.myMaxAscent = maxAscent;
  }

  private java.util.List<EditorCell> getCurrentLine() {
    return myCurrentLine;
  }

  private CellLayout_Flow getFlowLayout(EditorCell editorCell) {
    if (editorCell instanceof EditorCell_Collection) {
      EditorCell_Collection editorCell_collection = (EditorCell_Collection) editorCell;
      jetbrains.mps.openapi.editor.cells.CellLayout cellLayout = editorCell_collection.getCellLayout();
      if (cellLayout instanceof CellLayout_Flow) {
        return (CellLayout_Flow) cellLayout;
      }
    }
    return null;
  }

  private void setFirstLineHeight(int firstLineHeight) {
    this.myFirstLineHeight = firstLineHeight;
  }

  private Set<CellLayout_Flow> getCurrentLineLayouts() {
    return myCurrentLineLayouts;
  }

  //----------------
  //  FlowLayouter
  //----------------

  private class FlowLayouter {
    private int myX;
    private int myY;
    private int myMaxRightX;
    private EditorCell_Collection myEditorCells;
    private boolean myNextIsPunctuation;
    private boolean myToSkip;
    private int myMaxX;

    public FlowLayouter(EditorCell_Collection editorCells) {
      this.myEditorCells = editorCells;
      myX = editorCells.getX() + myWStart;
      myY = editorCells.getY();
      myMaxRightX = myX;
      if (editorCells.getStyle().isSpecified(StyleAttributes.MAX_WIDTH)) {
        myMaxX = editorCells.getX() + editorCells.getStyle().get(StyleAttributes.MAX_WIDTH);
      } else {
        myMaxX = getMaxX();
      }
    }

    public void doLayout() {
      if (myBossLayout == null) {
        getCurrentLine().clear();
        getCurrentLineLayouts().clear();
      }

      for (EditorCell cell : myEditorCells) {
        if (myToSkip) {
          myToSkip = false;
          myNextIsPunctuation = false;
          continue;
        }

        //testing the next cell
        EditorCell nextCell = CellTraversalUtil.getNextSibling(cell);
        myNextIsPunctuation = false;
        if (nextCell != null) {
          if (APICellAdapter.isPunctuationLayout(nextCell)) {
            myNextIsPunctuation = true;
          }
        }

        //if no flow
        if (LayoutConstraints.NOFLOW_LAYOUT_CONSTRAINT.getName().equals(cell.getStyle().get(StyleAttributes.LAYOUT_CONSTRAINT))) {
          if (!getCurrentLine().isEmpty()) {
            alignLine();
            nextLine();
          }
          cell.moveTo(myEditorCells.getX(), myY);
          cell.relayout();
          myY += cell.getHeight();
          myMaxRightX = Math.max(myMaxRightX, cell.getX() + cell.getWidth());
        } else
          //if flow layout
          if (getFlowLayout(cell) != null) {

            CellLayout_Flow cellLayout_flow = getFlowLayout(cell);
            cellLayout_flow.setMaxAscent(myMaxAscent);
            cellLayout_flow.setMaxDescent(myMaxDescent);
            cellLayout_flow.setWStart(myX - myEditorCells.getX());
            cellLayout_flow.getCurrentLine().clear();
            cellLayout_flow.getCurrentLine().addAll(CellLayout_Flow.this.getCurrentLine());
            cellLayout_flow.getCurrentLineLayouts().clear();
            cellLayout_flow.getCurrentLineLayouts().addAll(CellLayout_Flow.this.getCurrentLineLayouts());
            cell.setX(myEditorCells.getX());
            cell.setY(myY);

            cellLayout_flow.setBossLayout(CellLayout_Flow.this);
            cell.relayout();

            myMaxRightX = Math.max(myMaxRightX, cell.getX() + cell.getWidth());
            setMaxAscent(Math.max(myMaxAscent, cellLayout_flow.myMaxAscent));
            setMaxDescent(Math.max(myMaxDescent, cellLayout_flow.myMaxDescent));
            CellLayout_Flow.this.getCurrentLine().clear();
            CellLayout_Flow.this.getCurrentLine().addAll(cellLayout_flow.getCurrentLine());
            CellLayout_Flow.this.getCurrentLineLayouts().clear();
            CellLayout_Flow.this.getCurrentLineLayouts().addAll(cellLayout_flow.getCurrentLineLayouts());

            if (cellLayout_flow.myRowCount >= 2) {
              myX = cell.getX() + cellLayout_flow.myWEnd;
              myY = cell.getY() + cell.getHeight() - cellLayout_flow.myMaxAscent - cellLayout_flow.myMaxDescent;
              myRowCount += cellLayout_flow.myRowCount - 1;
            } else {
              myX += cell.getWidth();
            }

          } else {
            //punctuation must be at the same line as previous cell
            cell.relayout();
            int allocatedWidth = cell.getWidth();
            if (myNextIsPunctuation) {
              assert nextCell != null;
              nextCell.relayout();
              allocatedWidth += nextCell.getWidth();
            }
            //if end-of-line
            if (allocatedWidth + myX >= myMaxX) {
              alignLine();
              nextLine();
              addCell(cell);
              if (myNextIsPunctuation) {
                addCell(nextCell);
                myToSkip = true;
              }
            } else {//default
              addCell(cell);
            }
          }
      }
      boolean currentLineIsEmpty = CellLayout_Flow.this.getCurrentLine().isEmpty();
      if (!currentLineIsEmpty) alignLine();
      int lastLineHeight = myMaxAscent + myMaxDescent;
      myEditorCells.setHeight((myY + lastLineHeight) - myEditorCells.getY());
      myEditorCells.setWidth(myMaxRightX - myEditorCells.getX());
      myWEnd = myX - myEditorCells.getX();
    }


    private void nextLine() {
      myY += myMaxAscent + myMaxDescent;
      myX = myEditorCells.getX();
      setMaxAscent(0);
      setMaxDescent(0);
      getCurrentLine().clear();
      getCurrentLineLayouts().clear();
    }

    private void alignLine() {
      for (EditorCell cell : CellLayout_Flow.this.getCurrentLine()) {
        cell.setBaseline(myY + myMaxAscent);
      }
      for (CellLayout_Flow layout_flow : getCurrentLineLayouts()) {
        layout_flow.setMaxAscent(myMaxAscent);
        layout_flow.setMaxDescent(myMaxDescent);
        if (layout_flow.myFirstLineHeight == -1) layout_flow.setFirstLineHeight(myMaxAscent + myMaxDescent);
      }
      if (myFirstLineHeight == -1) myFirstLineHeight = myMaxAscent + myMaxDescent;
      myMaxRightX = Math.max(myX, myMaxRightX);
      myRowCount++;
    }

    private void addCell(EditorCell cell) {
      cell.setX(myX);
      cell.relayout();
      myX += cell.getWidth();
      if (myNextIsPunctuation) {
        myX -= cell.getRightInset();
      }
      setMaxAscent(Math.max(myMaxAscent, cell.getAscent()));
      setMaxDescent(Math.max(myMaxDescent, cell.getDescent()));
      CellLayout_Flow.this.getCurrentLine().add(cell);
    }

  } //--FlowLayouter

  @Override
  public List<? extends EditorCell> getSelectionCells(EditorCell_Collection editorCells) {
    LOG.assertLog(getFlowLayout(editorCells) == this, "Assertion failed.");
    List<EditorCell> result = new ArrayList<EditorCell>();
    for (EditorCell cell : editorCells) {
      result.add(cell);
    }
    return result;
  }

  @Override
  public List<Rectangle> getSelectionBounds(EditorCell_Collection editorCells) {
    LOG.assertLog(getFlowLayout(editorCells) == this, "Assertion failed.");
    List<Rectangle> result = new ArrayList<Rectangle>();
    for (EditorCell cell : editorCells) {
      result.add(GeometryUtil.getBounds(cell));
    }
    return result;
  }

  @Override
  public TextBuilder doLayoutText(Iterable<EditorCell> editorCells) {
    TextBuilder result = new TextBuilderImpl();
    Iterator<EditorCell> it = editorCells.iterator();
    while (it.hasNext()) {
      result.appendToTheBottom(doLayoutRow(it));
    }
    return result;
  }

  private TextBuilder doLayoutRow(Iterator<EditorCell> it) {
    TextBuilder result = new TextBuilderImpl();
    for (; it.hasNext(); ) {
      EditorCell editorCell = it.next();
      if (LayoutConstraints.NOFLOW_LAYOUT_CONSTRAINT.getName().equals(editorCell.getStyle().get(StyleAttributes.LAYOUT_CONSTRAINT))) {
        return result.appendToTheBottom(editorCell.renderText());
      }
      result.appendToTheRight(editorCell.renderText(), !APICellAdapter.isPunctuationLayout(editorCell));
    }
    return result;
  }

  public String toString() {
    return "Flow";
  }

}
