/*
 * 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.leftHighlighter;

import jetbrains.mps.nodeEditor.leftHighlighter.HighlighterBracket.BracketEdge;
import jetbrains.mps.openapi.editor.cells.CellInfo;
import jetbrains.mps.openapi.editor.cells.EditorCell;

import java.awt.Color;
import java.awt.Graphics;
import java.awt.Rectangle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Stack;

/**
 * User: Alexander Shatalin
 * Date: 02.03.2010
 */
public class BracketsPainter extends AbstractFoldingAreaPainter {
  private int myAreaWidth = -1;
  private HashMap<CellInfo, HighlighterBracket> myBrackets = new HashMap<CellInfo, HighlighterBracket>();
  private boolean myRightToLeft;

  public BracketsPainter(LeftEditorHighlighter leftEditorHighlighter, boolean rightToLeft) {
    super(leftEditorHighlighter);
    myRightToLeft = rightToLeft;
  }

  @Override
  public int getLeftAreaWidth() {
    return myRightToLeft ? super.getLeftAreaWidth() : getAreaWidth();
  }

  @Override
  public int getRightAreaWidth() {
    return myRightToLeft ? getAreaWidth() : super.getRightAreaWidth();
  }

  private int getAreaWidth() {
    if (myAreaWidth < 0) {
      myAreaWidth = relayoutBrackets();
    }
    return myAreaWidth;
  }

  @Override
  public void relayout() {
    myAreaWidth = -1;
  }

  private int relayoutBrackets() {
    for (Iterator<Entry<CellInfo, HighlighterBracket>> it = myBrackets.entrySet().iterator(); it.hasNext(); ) {
      Entry<CellInfo, HighlighterBracket> nextEntry = it.next();
      if (!nextEntry.getValue().relayout()) {
        // TODO: check if this code is useful 
        it.remove();
      }
    }

    List<BracketEdge> bracketEdges = new ArrayList<BracketEdge>();
    for (HighlighterBracket bracket : myBrackets.values()) {
      bracket.setLevel(1);
      bracketEdges.add(bracket.getBeginningEdge());
      bracketEdges.add(bracket.getEndingEdge());
    }
    Collections.sort(bracketEdges);

    int maxLevel = 0;
    Stack<HighlighterBracket> myBracketsLayoutStack = new Stack<HighlighterBracket>();
    for (int i = 0; i < bracketEdges.size(); i++) {
      BracketEdge edge = bracketEdges.get(i);
      HighlighterBracket bracket = edge.getBracket();
      if (edge.isBeggining()) {
        myBracketsLayoutStack.push(bracket);
      } else {
        HighlighterBracket bracketFromStack = null;
        while (bracketFromStack != bracket) {
          if (bracketFromStack != null) {
            // layout error: last popped bracket corresponds to another ending edge. Skipping this bracket.
            bracketEdges.remove(bracketFromStack.getEndingEdge());
            myBrackets.remove(bracketFromStack.getCell());
          }
          if (!myBracketsLayoutStack.isEmpty()) {
            HighlighterBracket newBracketFromStack = myBracketsLayoutStack.pop();
            if (bracketFromStack != null) {
              // last popped bracket was incorrect - see "layout error" comment above, so copying level informatin from there
              newBracketFromStack.setLevel(Math.max(newBracketFromStack.getLevel(), bracketFromStack.getLevel()));
            }
            bracketFromStack = newBracketFromStack;
          } else {
            break;
          }
        }

        if (bracketFromStack != bracket) {
          // layout error - no bracket corresponding to this endig edge was found in stack. Skipping this bracket.
          myBrackets.remove(bracket.getCell());
        } else {
          if (!myBracketsLayoutStack.isEmpty()) {
            // setting level for next bracket on stack
            HighlighterBracket onTopOfStack = myBracketsLayoutStack.peek();
            onTopOfStack.setLevel(Math.max(onTopOfStack.getLevel(), bracket.getLevel() + 1));
          }
          maxLevel = Math.max(bracket.getLevel(), maxLevel);
        }
      }
    }
    return maxLevel == 0 ? 0 : HighlighterBracket.getBracketWidth(maxLevel);
  }

  @Override
  public void paintInLocalCoordinates(Graphics g) {
    Rectangle clipBounds = g.getClipBounds();
    for (HighlighterBracket bracket : myBrackets.values()) {
      bracket.paint(g, clipBounds);
    }
  }

  @Override
  public int getWeight() {
    return 1;
  }

  public void removeBracket(EditorCell cell) {
    myBrackets.remove(cell.getCellInfo());
    myAreaWidth = -1;
  }

  public void addBracket(EditorCell cell, EditorCell secondCell, Color c) {
    CellInfo info1 = cell.getCellInfo();
    CellInfo info2 = secondCell.getCellInfo();
    myBrackets.put(info1, new HighlighterBracket(info1, info2, c, getEditorComponent(), myRightToLeft));
    myAreaWidth = -1;
  }
}
