/**
 * Copyright (c) 2005-2013 by Appcelerator, Inc. All Rights Reserved.
 * Licensed under the terms of the Eclipse Public License (EPL).
 * Please see the license.txt included with this distribution for details.
 * Any modifications to this file must keep this entire header intact.
 */
package org.python.pydev.parser.prettyprinterv2;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.python.pydev.parser.jython.SimpleNode;
import org.python.pydev.parser.jython.ast.commentType;
import org.python.pydev.shared_core.string.FastStringBuffer;

/**
 * Defines a line in the document. The items are added based on the order in which items are added
 * and their actual columns.
 * 
 * So, if 2 items are added to the same column, the one added 1st has precedence over the other.
 *
 */
public class PrettyPrinterDocLineEntry {

    /**
     * Holds the line parts available.
     */
    private ArrayList<ILinePart> lineParts = new ArrayList<ILinePart>();

    /**
     * The difference from indents/dedents in the current line (e.g.: if there are 2 indents
     * and 1 dedent in this line, indentDiff = 1)
     */
    private int indentDiff;

    /**
     * The number of empty lines required after a dedent in this line.
     */
    private int emptyLinesRequiredAfterDedent;

    /**
     * The number of this line in the document.
     */
    public final int line;

    /**
     * Marks if the line is currently sorted or not. Whenever the line is changed, this
     * attribute has to be marked as false.
     */
    private boolean lineSorted = false;

    public PrettyPrinterDocLineEntry(int line) {
        this.line = line;
    }

    private void addPart(ILinePart linePart) {
        int before = -1;
        if (linePart instanceof LinePartRequireMark) {
            String token = ((LinePartRequireMark) linePart).getToken().trim();
            if (token.equals(":") || token.equals(",") || token.equals("(") || token.equals(")") || token.equals("[")
                    || token.equals("]") || token.equals("=") || token.equals("{") || token.equals("}")) {
                if (lineParts.size() > 0) {
                    for (int i = lineParts.size() - 1; i >= 0; i--) {
                        ILinePart existing = lineParts.get(i);
                        if (existing instanceof LinePartStatementMark || existing.getToken() instanceof commentType) {
                            before = i;
                        } else {
                            break;
                        }
                    }
                }
            }
        }
        if (before != -1) {
            lineParts.add(before, linePart);
        } else {
            lineParts.add(linePart);
        }
        lineSorted = false;
    }

    private void addPart(int i, ILinePart linePart) {
        lineParts.add(i, linePart);
        lineSorted = false;
    }

    private void sortLineParts() {
        if (!lineSorted) {
            Collections.sort(lineParts, new Comparator<ILinePart>() {

                public int compare(ILinePart o1, ILinePart o2) {
                    return (o1.getBeginCol() < o2.getBeginCol() ? -1 : (o1.getBeginCol() == o2.getBeginCol() ? 0 : 1));
                }
            });
            lineSorted = true;
        }
    }

    public ILinePart add(int beginCol, String string, Object token) {
        ILinePart linePart = new LinePart(beginCol, string, token, this);
        addPart(linePart);
        return linePart;
    }

    public ILinePart addBefore(int beginCol, String string, Object token) {
        ILinePart linePart = new LinePart(beginCol, string, token, this);

        //Now, on the start, we want to add it before any existing in the same column.
        for (int i = 0; i < this.lineParts.size(); i++) {
            if (beginCol == this.lineParts.get(i).getBeginCol()) {
                addPart(i, linePart);
                return linePart;
            }
        }
        addPart(linePart);
        return linePart;
    }

    @Override
    public String toString() {
        FastStringBuffer buf = new FastStringBuffer();
        for (ILinePart c : getSortedParts()) {
            if (c instanceof ILinePart2) {
                ILinePart2 iLinePart2 = (ILinePart2) c;
                buf.append(iLinePart2.getString());
            } else {
                buf.append(c.toString());
            }
            buf.append(c.isMarkedAsFound() ? "+" : "?");
            buf.append(" ");
        }
        return buf.toString();
    }

    public List<ILinePart> getSortedParts() {
        sortLineParts();
        return this.lineParts;
    }

    public void indent(SimpleNode node) {
        indent(node, false);
    }

    public LinePartIndentMark indent(SimpleNode node, boolean requireNewLine) {
        this.indentDiff += 1;
        LinePartIndentMark linePartIndentMark = new LinePartIndentMark(node.beginColumn, node, true, this);
        linePartIndentMark.setRequireNewLine(requireNewLine);
        addPart(linePartIndentMark);
        return linePartIndentMark;
    }

    public LinePartIndentMark dedent(int emptyLinesRequiredAfterDedent) {
        if (this.emptyLinesRequiredAfterDedent < emptyLinesRequiredAfterDedent) {
            this.emptyLinesRequiredAfterDedent = emptyLinesRequiredAfterDedent;
        }
        this.indentDiff -= 1;
        List<ILinePart> sortedParts = this.getSortedParts();
        LinePartIndentMark dedentMark;
        if (sortedParts.size() > 0) {
            dedentMark = new LinePartIndentMark(sortedParts.get(sortedParts.size() - 1).getBeginCol(), "", false, this);
            sortedParts.add(dedentMark);
        } else {
            dedentMark = new LinePartIndentMark(0, "", false, this);
            addPart(dedentMark);
        }
        return dedentMark;
    }

    public LinePartIndentMark indentAfter(ILinePart after, boolean requireNewLine) {
        this.indentDiff += 1;
        LinePartIndentMark linePartIndentMark = new LinePartIndentMark(after.getBeginCol(), after.getToken(), true,
                this);
        linePartIndentMark.setRequireNewLine(requireNewLine);
        addPart(lineParts.indexOf(after) + 1, linePartIndentMark);
        return linePartIndentMark;
    }

    public int getIndentDiff() {
        return this.indentDiff;
    }

    public int getFirstCol() {
        sortLineParts();
        if (this.lineParts.size() > 0) {
            ILinePart iLinePart0 = this.lineParts.get(0);
            if (!(iLinePart0 instanceof LinePartRequireAdded)) {
                return iLinePart0.getBeginCol();
            }
        }
        return -1;
    }

    public void addStartStatementMark(ILinePart foundWithLowerLocation, SimpleNode node) {
        sortLineParts();

        //Now, on the start, we want to add it before any existing in the same column.
        for (int i = 0; i < this.lineParts.size(); i++) {
            if (foundWithLowerLocation == this.lineParts.get(i)) {
                addPart(i, new LinePartStatementMark(foundWithLowerLocation.getBeginCol(), node, true, this));
                return;
            }
        }
        addPart(new LinePartStatementMark(foundWithLowerLocation.getBeginCol(), node, true, this));
    }

    public void addEndStatementMark(ILinePart foundWithHigherLocation, SimpleNode node) {
        addPart(new LinePartStatementMark(foundWithHigherLocation.getBeginCol(), node, false, this));
    }

    public int getNewLinesRequired() {
        return this.emptyLinesRequiredAfterDedent;
    }

    public LinePartRequireMark addRequireMark(int beginColumn, String string) {
        LinePartRequireMark mark = new LinePartRequireMark(beginColumn, string, this);
        addPart(mark);
        return mark;
    }

    public LinePartRequireMark addRequireMark(int beginColumn, String... string) {
        LinePartRequireMark mark = new LinePartRequireMark(beginColumn, this, string);
        addPart(mark);
        return mark;
    }

    public LinePartRequireIndentMark addRequireIndentMark(int beginColumn, String string) {
        LinePartRequireIndentMark ret = new LinePartRequireIndentMark(beginColumn, string, this);
        addPart(ret);
        return ret;
    }

    public LinePartRequireMark addRequireMarkBefore(ILinePart o1, String string) {
        LinePartRequireMark linePart = new LinePartRequireMark(o1.getBeginCol(), string, this);
        for (int i = 0; i < this.lineParts.size(); i++) {
            if (o1 == this.lineParts.get(i)) {
                addPart(i, linePart);
                return linePart;
            }
        }
        addPart(linePart);
        return linePart;
    }

    public LinePartRequireMark addRequireMarkAfterBefore(ILinePart o1, String string) {
        LinePartRequireMark linePart = new LinePartRequireMark(o1.getBeginCol(), string, this);
        for (int i = 0; i < this.lineParts.size(); i++) {
            if (o1 == this.lineParts.get(i)) {
                addPart(i + 1, linePart);
                return linePart;
            }
        }
        addPart(linePart);
        return linePart;
    }

}
