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

import java.util.Map.Entry;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;

import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.python.pydev.core.docutils.ParsingUtils;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.docutils.SyntaxErrorException;
import org.python.pydev.core.docutils.TabNannyDocIterator;
import org.python.pydev.core.log.Log;
import org.python.pydev.shared_core.parsing.IScopesParser;
import org.python.pydev.shared_core.parsing.Scopes;
import org.python.pydev.shared_core.string.FastStringBuffer;
import org.python.pydev.shared_core.structure.Tuple3;

/**
 * This parser is a bit different from the others, as its output is not an AST, but a structure defining the scopes
 * in a document (used for doing the scope selection action).
 *
 * @author fabioz
 */
public class ScopesParser implements IScopesParser {

    @Override
    public Scopes createScopes(IDocument doc) {
        this.scopes = new Scopes();
        this.doc = doc;
        this.lineOffsetToIndent = new TreeMap<Integer, Integer>();

        try {
            TabNannyDocIterator nannyDocIterator = new TabNannyDocIterator(doc, true, false);
            while (nannyDocIterator.hasNext()) {
                Tuple3<String, Integer, Boolean> next = nannyDocIterator.next();
                this.lineOffsetToIndent.put(next.o2, next.o1.length());
            }
        } catch (BadLocationException e1) {
            throw new RuntimeException(e1);
        }

        try {
            return this.createScopes();
        } catch (SyntaxErrorException e) {
            throw new RuntimeException(e);
        }
    }

    private Scopes scopes;
    private SortedMap<Integer, Integer> lineOffsetToIndent;
    private IDocument doc;

    public ScopesParser() {

    }

    private Scopes createScopes() throws SyntaxErrorException {
        int globalScope = this.scopes.startScope(0, Scopes.TYPE_MODULE);
        int offset = createInternalScopes(ParsingUtils.create(doc, true), 0);
        this.scopes.endScope(globalScope, offset, Scopes.TYPE_MODULE);

        return this.scopes;

    }

    private int createInternalScopes(ParsingUtils parsingUtils, int offsetDelta) {
        int docLen = parsingUtils.len();
        int offset = 0;
        FastStringBuffer lineMemo = new FastStringBuffer();
        int memoStart = 0;
        int id;

        for (; offset < docLen; offset++) {
            char ch = parsingUtils.charAt(offset);

            switch (ch) {

                case '#':
                    id = this.scopes.startScope(offsetDelta + offset, Scopes.TYPE_COMMENT);
                    offset = parsingUtils.eatComments(null, offset);
                    this.scopes.endScope(id, offsetDelta + offset, Scopes.TYPE_COMMENT);
                    break;

                case '{':
                case '[':
                case '(':
                    int baseOffset = offset;
                    try {
                        offset = parsingUtils.eatPar(offset, null, ch); //If a SyntaxError is raised here, we won't create a scope!
                        id = this.scopes.startScope(offsetDelta + baseOffset + 1, Scopes.TYPE_PEER);

                        try {
                            String cs = doc.get(offsetDelta + baseOffset + 1, offset - baseOffset - 1);
                            createInternalScopes(ParsingUtils.create(cs, true), offsetDelta + baseOffset + 1);
                        } catch (BadLocationException e1) {
                            Log.log(e1);
                        }

                        this.scopes.endScope(id, offsetDelta + offset, Scopes.TYPE_PEER);

                    } catch (SyntaxErrorException e2) {

                    }

                    break;

                case '\'':
                    //Fallthrough

                case '\"':
                    baseOffset = offset;

                    try {
                        offset = parsingUtils.eatLiterals(null, offset); //If a SyntaxError is raised here, we won't create a scope!
                        id = this.scopes.startScope(offsetDelta + baseOffset, Scopes.TYPE_STRING);
                        this.scopes.endScope(id, offsetDelta + offset + 1, Scopes.TYPE_STRING);
                    } catch (SyntaxErrorException e1) {

                    }
                    break;

                case ':':
                    if (PySelection.startsWithIndentToken(lineMemo.toString().trim())) {
                        SortedMap<Integer, Integer> subMap = lineOffsetToIndent.tailMap(offsetDelta + memoStart + 1);
                        Integer level = lineOffsetToIndent.get(offsetDelta + memoStart);
                        if (level == null) {
                            //It's a ':' inside a parens
                            continue;
                        }

                        Set<Entry<Integer, Integer>> entrySet = subMap.entrySet();
                        boolean found = false;
                        id = this.scopes.startScope(memoStart + level, Scopes.TYPE_SUITE);

                        int id2 = -1;
                        for (int j = offset + 1; j < docLen; j++) {
                            char c = parsingUtils.charAt(j);
                            if (Character.isWhitespace(c)) {
                                continue;
                            }
                            if (c == '#') {
                                j = parsingUtils.eatComments(null, j);
                                continue;
                            }
                            id2 = this.scopes.startScope(offsetDelta + j, Scopes.TYPE_SUITE);
                            break;
                        }

                        for (Entry<Integer, Integer> entry : entrySet) {
                            if (level >= entry.getValue()) {
                                found = true;
                                Integer scopeEndOffset = entry.getKey();
                                try {
                                    int line = doc.getLineOfOffset(scopeEndOffset);
                                    if (line > 0) {
                                        //We want it to end at the end of the previous line (not at the start of the next scope)
                                        IRegion lineInformation = doc.getLineInformation(line - 1);
                                        scopeEndOffset = lineInformation.getOffset() + lineInformation.getLength();
                                    }
                                } catch (BadLocationException e) {
                                    Log.log(e);
                                }
                                this.scopes.endScope(id, scopeEndOffset, Scopes.TYPE_SUITE);
                                if (id2 > 0) {
                                    this.scopes.endScope(id2, scopeEndOffset, Scopes.TYPE_SUITE);
                                }
                                break;
                            }
                        }
                        if (!found) {
                            //Ends at the end of the document!
                            this.scopes.endScope(id, offsetDelta + parsingUtils.len(), Scopes.TYPE_SUITE);
                            if (id2 > 0) {
                                this.scopes.endScope(id2, offsetDelta + parsingUtils.len(), Scopes.TYPE_SUITE);
                            }
                        }
                    }
                    break;

                case '\r':
                    //Fallthrough

                case '\n':
                    //Note that we don't add the \r nor \n to the memo (but we clear it if the  line did not end with a \).
                    if (lineMemo.length() > 0 && lineMemo.lastChar() != '\\') {
                        lineMemo.clear();
                    }
                    break;

                default:
                    if (lineMemo.length() == 0) {
                        memoStart = offset;
                    }
                    lineMemo.append(ch);
                    break;
            }

        }
        return offset;
    }

}
