/*
 * Copyright 2003-2015 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.style.DefaultBaseLine;
import jetbrains.mps.editor.runtime.style.StyleAttributes;
import jetbrains.mps.editor.runtime.style.TableComponent;
import jetbrains.mps.openapi.editor.TextBuilder;
import jetbrains.mps.openapi.editor.cells.EditorCell;
import jetbrains.mps.openapi.editor.cells.EditorCell_Collection;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;

/**
 * Sergey.Sinchuk, Oct 30, 2009
 */
public class CellLayout_Table extends AbstractCellLayout {

  private CellLayout_Table myParentLayout = null;
  private int[] myColumnAscents = null;
  private int[] myColumnDescents = null;

  @Override
  public boolean canBeFolded() {
    return true;
  }

  private EditorCell_Collection findCollection(EditorCell cell, TableComponent tc) {
    if (cell instanceof EditorCell_Collection) {
      EditorCell_Collection col = (EditorCell_Collection) cell;
      jetbrains.mps.openapi.editor.cells.CellLayout l = col.getCellLayout();
      if (l instanceof CellLayout_Table) {
        TableComponent comp = col.getStyle().get(StyleAttributes.TABLE_COMPONENT);
        if (comp == tc) {
          return col;
        }
      }
      for (EditorCell c : col) {
        EditorCell_Collection result = findCollection(c, tc);
        if (result != null) {
          return result;
        }
      }
    }
    return null;
  }

  private Iterable<EditorCell_Collection> getRowsCollection(EditorCell_Collection editorCells) {
    TableComponent tc = editorCells.getStyle().get(StyleAttributes.TABLE_COMPONENT);
    List<EditorCell_Collection> result = new LinkedList<EditorCell_Collection>();
    for (EditorCell childCell : editorCells) {
      EditorCell_Collection col = findCollection(childCell, tc.transpose());
      result.add(col);
      if (col != null) {
        ((CellLayout_Table) col.getCellLayout()).myParentLayout = this;
      }
    }
    return result;
  }

  @Override
  public void doLayout(EditorCell_Collection editorCells) {
    if (myParentLayout != null) {
      doLayoutInner(editorCells);
    } else {
      doLayoutOuter(editorCells);
    }
  }

  private void doLayoutOuter(EditorCell_Collection editorCells) {
    assert myParentLayout == null;

    final int x = editorCells.getX();
    final int y = editorCells.getY();
    TableComponent tc = editorCells.getStyle().get(StyleAttributes.TABLE_COMPONENT);

    Iterable<EditorCell_Collection> rowsCollection = getRowsCollection(editorCells);
    fillAscentsDescents(rowsCollection, tc);

    for (EditorCell c : editorCells) {
      c.relayout();
    }

    int leftMargin = 0;
    int rightMargin = 0;
    int gridWidth = 0;

    Iterator<EditorCell_Collection> rowsIterator = rowsCollection.iterator();
    for (EditorCell childCell : editorCells) {
      EditorCell innerCollection = rowsIterator.next();
      if (innerCollection == null) {
        gridWidth = Math.max(gridWidth, tc.getWidth(childCell));
      } else {
        gridWidth = Math.max(gridWidth, tc.getWidth(innerCollection));
        leftMargin = Math.max(leftMargin, tc.getX(innerCollection) - tc.getX(childCell));
        rightMargin = Math.max(rightMargin, (tc.getX(childCell) + tc.getWidth(childCell)) - (tc.getX(innerCollection) + tc.getWidth(innerCollection)));
      }
    }

    int currentWidth = leftMargin + gridWidth + rightMargin;
    int currentHeight = 0;

    rowsIterator = rowsCollection.iterator();
    for (EditorCell childCell : editorCells) {
      EditorCell innerCollection = rowsIterator.next();

      int deltaX;
      if (innerCollection == null) {
        deltaX = leftMargin + (gridWidth - tc.getWidth(childCell)) / 2;
      } else {
        deltaX = leftMargin - (tc.getX(innerCollection) - tc.getX(childCell));
      }
      int deltaY = currentHeight;

      childCell.moveTo(x + tc.getX(deltaX, deltaY), y + tc.getY(deltaX, deltaY));
      childCell.relayout();
      currentHeight += tc.getX(childCell.getHeight(), childCell.getWidth());
    }

    editorCells.setWidth(tc.getX(currentWidth, currentHeight));
    editorCells.setHeight(tc.getY(currentWidth, currentHeight));
  }

  private void fillAscentsDescents(Iterable<EditorCell_Collection> rowCollections, TableComponent tc) {
    List<Integer> columnAscents = new LinkedList<Integer>();
    List<Integer> columnDescents = new LinkedList<Integer>();
    for (EditorCell_Collection row : rowCollections) {
      if (row == null) {
        continue;
      }
      ListIterator<Integer> columnAscentsIterator = columnAscents.listIterator();
      ListIterator<Integer> columnDescentsIterator = columnDescents.listIterator();
      for (EditorCell column : row) {
        column.relayout();
        if (columnAscentsIterator.hasNext()) {
          columnAscentsIterator.set(Math.max(columnAscentsIterator.next(), tc.transpose().getAccent(column)));
        } else {
          columnAscentsIterator.add(tc.transpose().getAccent(column));
        }
        if (columnDescentsIterator.hasNext()) {
          columnDescentsIterator.set(Math.max(columnDescentsIterator.next(), tc.transpose().getDescent(column)));
        } else {
          columnDescentsIterator.add(tc.transpose().getDescent(column));
        }
      }
    }
    myColumnAscents = new int[columnAscents.size()];
    myColumnDescents = new int[columnDescents.size()];
    assert columnAscents.size() == columnDescents.size();
    Iterator<Integer> columnAscentsIterator = columnAscents.iterator();
    Iterator<Integer> columnDescentsIterator = columnAscents.iterator();
    for (int i = 0; i < columnAscents.size(); i++) {
      myColumnDescents[i] = columnAscentsIterator.next();
      myColumnAscents[i] = columnDescentsIterator.next();
    }
  }

  private void doLayoutInner(EditorCell_Collection editorCells) {
    assert myParentLayout != null;

    final int x = editorCells.getX();
    final int y = editorCells.getY();
    TableComponent tc = editorCells.getStyle().get(StyleAttributes.TABLE_COMPONENT);

    for (EditorCell c : editorCells) {
      c.relayout();
    }

    int maxWidth = 0;
    for (EditorCell childCell : editorCells) {
      maxWidth = Math.max(maxWidth, tc.getWidth(childCell));
    }

    int currentHeight = 0;
    int counter = 0;
    for (EditorCell childCell : editorCells) {
      int deltaX = (maxWidth - tc.getWidth(childCell)) / 2;

      int ascent = myParentLayout.myColumnAscents[counter];
      int descent = myParentLayout.myColumnDescents[counter];
      int cellAscent = tc.getAccent(childCell);

      int deltaY = currentHeight + (ascent - cellAscent);
      currentHeight += ascent + descent;

      childCell.setX(x + tc.getX(deltaX, deltaY));
      childCell.setY(y + tc.getY(deltaX, deltaY));
      counter++;
    }

    // TODO: looks like we don't need it here for "inner" table cells (rows/columns)
    for (EditorCell c : editorCells) {
      c.relayout();
    }

    editorCells.setWidth(tc.getX(maxWidth, currentHeight));
    editorCells.setHeight(tc.getY(maxWidth, currentHeight));
  }

  @Override
  public TextBuilder doLayoutText(Iterable<EditorCell> editorCells) {
    TextBuilder result = new TextBuilderImpl();
    for (EditorCell editorCell : editorCells) {
      result.appendToTheBottom(editorCell.renderText());
    }
    return result;
  }

  @Override
  public int getAscent(EditorCell_Collection editorCells) {
    for (EditorCell cell : editorCells) {
      if (cell.getStyle().get(StyleAttributes.BASE_LINE_CELL)) {
        return cell.getY() - editorCells.getY() + cell.getAscent();
      }
    }

    DefaultBaseLine bL = editorCells.getStyle().get(StyleAttributes.DEFAULT_BASE_LINE);

    int result = 0;
    for (EditorCell cell : editorCells) {
      result = cell.getAscent();
      if (result > 0) {
        break;
      }
    }

    switch (bL) {
      case FIRST: // default behavior
        return result;
      case CENTER:
        return Math.max(result, editorCells.getHeight() / 2);
      case LAST:
        if (!editorCells.isEmpty()) {
          EditorCell lastCell = editorCells.lastCell();
          return lastCell.getY() - editorCells.getY() + lastCell.getAscent();
        }
    }

    return 0;
  }

  @Override
  public int getRightInternalInset(EditorCell_Collection editorCell_collection) {
    return editorCell_collection.isEmpty() ? 0 : editorCell_collection.firstCell().getRightInset();
  }

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

}
