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

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.python.pydev.parser.jython.FastCharStream;
import org.python.pydev.parser.jython.Token;
import org.python.pydev.shared_core.utils.Reflection;

/**
 * 
 * Note that this class actually has a tight coupling with subclasses, searching directly for some attributes
 * (e.g.: curLexState so that we don't need to override a get for each token manager class)
 *
 *
 * @author Fabio
 *
 */
public abstract class AbstractTokenManager extends AbstractTokenManagerWithConstants implements ITreeConstants,
        ITokenManager {

    /**
     * A stack with the indentations... No sure why it's not a stack (indentation+level)
     */
    protected final IndentLevel indentation = new IndentLevel();

    /**
     * When we find an EOF, we create artificially all the dedents. So, if we need to go back, we might
     * need to get back to the point we were previously.
     */
    protected int levelBeforeEof = -1;

    /**
     * If > 0, we're in a (, [ or {
     */
    protected int parens = 0;

    /**
     * This is the indentation in the current line (number of chars found after a \n)
     */
    protected int indent;

    /**
     * The special tokens available.
     */
    public final List<Object> specialTokens = new ArrayList<Object>();

    /**
     * The input stream.
     */
    private FastCharStream inputStream;

    @Override
    public final FastCharStream getInputStream() {
        if (this.inputStream == null) {
            this.inputStream = (FastCharStream) Reflection.getAttrObj(this, "input_stream", true);
        }
        return inputStream;
    }

    //must be calculated
    protected final int getCurLexState() {
        return (Integer) Reflection.getAttrObj(this, "curLexState", true);
    }

    /**
     * Gets the next token. Note that indents and dedents can be skipped if we're not currently
     * in a new line (that's the reason for a number of tricks while trying to recover from errors)
     */
    @Override
    public abstract Token getNextToken();

    protected AbstractTokenManager() {
    }

    /**
     * Creates a new token of the given kind (with the given image) in the same position as the 
     * previous token passed (note that even endline and endColumn are in the same position...
     * even if that'd be strange, that's what we want because those positions can be used
     * later and we need that specific point in the grammar)
     */
    protected final Token createFrom(Token previous, int kind, String image) {
        Token t = new Token();
        t.kind = kind;
        t.beginLine = previous.beginLine;
        t.endLine = previous.endLine;
        t.beginColumn = previous.beginColumn;
        t.endColumn = previous.endColumn;
        t.image = image;
        t.specialToken = null;
        t.next = null;
        return t;
    }

    /**
     * Creates a new token based on the coordinates on the previous and sets it as the next from the previous
     */
    protected final Token createFromAndSetAsNext(Token previous, int kind, String image) {
        Token t = createFrom(previous, kind, image);
        Token oldNext = previous.next;
        previous.next = t;
        t.next = oldNext;
        return t;
    }

    /**
     * Adds and returns a dedent token
     */
    protected final Token addDedent(Token previous) {
        return createFromAndSetAsNext(previous, getDedentId(), "<DEDENT>");
    }

    /**
     * Map pointing a token representation (, [, {, :, etc. to its id in the grammar.
     */
    private Map<String, Integer> tokenToIdCache;

    /**
     * @return a map pointing a token representation (, [, {, :, etc. to its id in the grammar.
     */
    private Map<String, Integer> getTokenToId() {
        if (tokenToIdCache == null) {
            tokenToIdCache = new HashMap<String, Integer>();

            tokenToIdCache.put(")", getRparenId());
            tokenToIdCache.put("]", getRbracketId());
            tokenToIdCache.put("}", getRbraceId());

            tokenToIdCache.put(":", getColonId());
            tokenToIdCache.put("(", getLparenId());
            tokenToIdCache.put(",", getCommaId());
        }
        return tokenToIdCache;
    }

    /**
     * Identifies that a custom token could not be created.
     */
    public static int CUSTOM_NOT_CREATED = 0;

    /**
     * Identifies that a custom token was created and it wasn't a parens
     * (meaning that we didn't change the parens level by creating it)
     */
    public static int CUSTOM_CREATED_NOT_PARENS = 1;

    /**
     * Identifies that a custom token was created and it wasn a parens
     * (meaning that we did change the parens level by creating it -- which
     * might requiring getting back in the input source to recreate possibly skipped
     * indents and dedents)
     */
    public static int CUSTOM_CREATED_WAS_PARENS = 2;

    /**
     * Creates a custom token for the given token representation (if possible)
     * 
     * @see #CUSTOM_NOT_CREATED
     * @see #CUSTOM_CREATED_NOT_PARENS
     * @see #CUSTOM_CREATED_WAS_PARENS
     */
    public int addCustom(Token curr, String token) {
        Integer id = getTokenToId().get(token);
        if (id != null) {
            createFromAndSetAsNext(curr, id, token);
            int ret = CUSTOM_CREATED_NOT_PARENS;
            if (id == getRparenId() || id == getRbracketId() || id == getRbraceId()) {
                parens--;
                ret = CUSTOM_CREATED_WAS_PARENS;
            } else if (id == getLparenId()) {
                parens++;
                ret = CUSTOM_CREATED_WAS_PARENS;
            }
            return ret;
        }
        return CUSTOM_NOT_CREATED;
    }

    /**
     * Called right after the creation of any token.
     * 
     * We may need to add the special tokens found while parsing to it and
     * add dedents (otherwise the grammar won't finish successfully if we don't have
     * a new line in the end).
     */
    protected final void CommonTokenAction(final Token initial) {
        Token t = initial;

        int i = specialTokens.size();
        while (t.specialToken != null) {
            this.specialTokens.add(i, t.specialToken);
            t = t.specialToken;
        }

        //Now, we must check the actual token here for EOF. 
        t = initial;

        //This is the place we check if we have to add dedents so that the parsing ends 'gracefully' when
        //we find and EOF.
        if (t.kind == getEofId()) {
            //Store it because if we backtrack we have to restore it!!
            this.levelBeforeEof = indentation.level;
            if (getCurLexState() == getLexerDefaultId()) {
                t.kind = getNewlineId();
            } else {
                t.kind = getDedentId();
                if (indentation.level >= 0) {
                    indentation.level -= 1;
                }
            }
            while (indentation.level >= 0) {
                indentation.level--;
                t = addDedent(t);
            }
            t.kind = getEofId();
            t.image = "<EOF>";
        }
    }

    /**
     * Must be called right after a new line with 0 as a parameter. Identifies the number of whitespaces in the current line.
     */
    @Override
    public abstract void indenting(int i);

    /**
     * @return The current level of the indentation in the current line.
     */
    public abstract int getCurrentLineIndentation();

    /**
     * @return The current level of the indentation.
     */
    public abstract int getLastIndentation();

}
