/**
 * 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.
 */
/*
 * Created on Feb 1, 2005
 *
 * @author Fabio Zadrozny
 */
package org.python.pydev.editor.codecompletion.revisited;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.eclipse.jface.text.Document;
import org.python.pydev.core.ExtensionHelper;
import org.python.pydev.core.ICodeCompletionASTManager;
import org.python.pydev.core.ICompletionState;
import org.python.pydev.core.IToken;
import org.python.pydev.core.TestDependent;
import org.python.pydev.core.structure.CompletionRecursionException;
import org.python.pydev.editor.codecompletion.IASTManagerObserver;
import org.python.pydev.editor.codecompletion.revisited.modules.CompiledModule;
import org.python.pydev.plugin.PydevPlugin;
import org.python.pydev.shared_core.string.FastStringBuffer;

/**
 * Tests here have no dependency on the pythonpath.
 * 
 * @author Fabio Zadrozny
 */
public class ASTManagerTest extends CodeCompletionTestsBase {

    public static void main(String[] args) {
        CompiledModule.COMPILED_MODULES_ENABLED = false;

        try {
            ASTManagerTest test = new ASTManagerTest();
            test.setUp();
            test.testCompletion();
            test.tearDown();

            junit.textui.TestRunner.run(ASTManagerTest.class);
        } catch (Throwable e) {
            e.printStackTrace();
        }
    }

    private ICompletionState state;
    private String token;
    private int line;
    private int col;
    private String sDoc;
    private Document doc;
    private IToken[] comps = null;

    /**
     * @return Returns the manager.
     */
    private ICodeCompletionASTManager getManager() {
        return (ICodeCompletionASTManager) nature.getAstManager();
    }

    /*
     * @see TestCase#setUp()
     */
    public void setUp() throws Exception {
        super.setUp();
        CompiledModule.COMPILED_MODULES_ENABLED = false;
        super.restorePythonPath(false);
    }

    /*
     * @see TestCase#tearDown()
     */
    public void tearDown() throws Exception {
        super.tearDown();
        CompiledModule.COMPILED_MODULES_ENABLED = true;
        PydevPlugin.setBundleInfo(null);
    }

    public void testCompletion() {
        token = "C";
        line = 6;
        col = 11;
        sDoc = "" +
                "class C:             \n" +
                "                     \n" +
                "    def makeit(self):\n"
                +
                "        pass         \n" +
                "                     \n" +
                "class D(C.:          \n"
                +
                "                     \n" +
                "    def a(self):     \n" +
                "        pass         \n";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(1);
        assertEquals("makeit", comps[0].getRepresentation());

        sDoc = "" +
                "import unittest       \n" +
                "                      \n" +
                "class Classe1:        \n"
                +
                "                      \n" +
                "    def makeit(self): \n" +
                "        self.makeit   \n"
                +
                "                      \n" +
                "                      \n" +
                "class Test(unit       \n";

        line = 8;
        col = 16;
        token = "";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();

        checkExpected(6);
        assertIsIn("__name__", comps);
        assertIsIn("__file__", comps);
        assertIsIn("__dict__", comps);
        assertIsIn("unittest", comps);
        assertIsIn("Classe1", comps);
        assertIsIn("Test", comps);

        sDoc = "" +
                "import unittest       \n" +
                "                      \n" +
                "class Classe1:        \n"
                +
                "                      \n" +
                "    def makeit(self): \n" +
                "        self.makeit   \n"
                +
                "                      \n" +
                "                      \n" +
                "class Test(unit       \n"
                +
                "                      \n" +
                "def meth1():          \n" +
                "    pass              \n";

        line = 8;
        col = 16;
        token = "";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(7);
        assertIsIn("__name__", comps);
        assertIsIn("__file__", comps);
        assertIsIn("__dict__", comps);
        assertIsIn("unittest", comps);
        assertIsIn("Classe1", comps);
        assertIsIn("Test", comps);
        assertIsIn("meth1", comps);

        sDoc = "" +
                "import unittest       \n" +
                "                      \n" +
                "class Classe1:        \n"
                +
                "                      \n" +
                "    def makeit(self): \n" +
                "        self.makeit   \n"
                +
                "                      \n" +
                "                      \n" +
                "class Test(unit       \n"
                +
                "                      \n" +
                "    def meth1():      \n" +
                "        pass          \n";

        line = 8;
        col = 16;
        token = "";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(6);
        assertIsIn("__name__", comps);
        assertIsIn("__file__", comps);
        assertIsIn("__dict__", comps);
        assertIsIn("unittest", comps);
        assertIsIn("Classe1", comps);
        assertIsIn("Test", comps);

        sDoc = "" +
                "class Classe1:       \n" +
                "                     \n" +
                "    def foo(self):   \n"
                +
                "        ignoreThis=0 \n" +
                "        self.a = 1   \n" +
                "        self.        \n"
                +
                "                     \n";

        line = 6;
        col = 13;
        token = "Classe1";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(2);
        assertIsIn("a", comps);
        assertIsIn("foo", comps);

        sDoc = "" +
                "class Classe1:       \n" +
                "                     \n" +
                "    def foo(self):   \n"
                +
                "        self.a = 2   \n" +
                "                     \n" +
                "    test = foo       \n"
                +
                "                     \n" +
                "Classe1.             \n";

        line = 8;
        col = 9;
        token = "Classe1";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(3);
        assertIsIn("foo", comps);
        assertIsIn("a", comps);
        assertIsIn("test", comps);

        sDoc = "" +
                "class LinkedList:                      \n" +
                "    def __init__(self,content='Null'): \n"
                +
                "        if not content:                \n" +
                "            self.first=content         \n"
                +
                "            self.last=content          \n" +
                "        else:                          \n"
                +
                "            self.first='Null'          \n" +
                "            self.last='Null'           \n"
                +
                "        self.content=content           \n" +
                "        self.                          \n";

        line = 9;
        col = 9;
        token = "LinkedList";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        assertIsIn("first", comps);
        assertIsIn("last", comps);
        assertIsIn("content", comps);

    }

    private void checkExpected(String... expected) {
        for (String s : expected) {
            assertIsIn(s, comps);
        }
        List<String> asList = Arrays.asList(expected);
        for (IToken t : comps) {
            assertContains(asList, t.getRepresentation());
        }
        checkExpected(expected.length);

    }

    private void checkExpected(int expected) {
        FastStringBuffer buf = new FastStringBuffer(40 * comps.length);
        for (IToken t : comps) {
            buf.append(t.getRepresentation());
            buf.append(", ");
        }
        String msg = "Expected " + expected +
                ". Found: " + buf.toString();
        assertEquals(msg, expected, comps.length);
    }

    private IToken[] getComps() {
        try {
            IToken[] completionsForToken = getManager().getCompletionsForToken(doc, state);
            HashMap<String, IToken> map = new HashMap<String, IToken>();
            for (IToken iToken : completionsForToken) {
                map.put(iToken.getRepresentation(), iToken);
            }

            return map.values().toArray(new IToken[map.size()]);
        } catch (CompletionRecursionException e) {
            throw new RuntimeException(e);
        }
    }

    public void testRecursion() {
        token = "B";
        line = 0;
        col = 0;
        sDoc = "" +
                "class A(B):pass          \n" +
                "class B(A):pass          \n";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(0); //no tokens returned

    }

    public void testRelative() {
        super.restorePythonPath(false);
        token = "Test1";
        line = 1;
        col = 0;
        sDoc = "" +
                "from testlib.unittest.relative import Test1 \n" +
                "\n";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(1);
        assertIsIn("test1", comps);

    }

    public void testLocals() {
        token = "";
        line = 2;
        sDoc = "" +
                "contentsCopy = applicationDb.getContentsCopy()\n"
                +
                "database.Database.fromContentsCopy(self, cont)";
        col = sDoc.length() - 3;
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(4);
        assertIsIn("contentsCopy", comps);
        assertIsIn("__file__", comps);
        assertIsIn("__dict__", comps);
        assertIsIn("__name__", comps);
    }

    public void testLocals2() {
        token = "";
        line = 2;
        col = 10;
        sDoc = "" +
                "def met(par1, par2):          \n" +
                "    print                     \n";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(6);
        assertIsIn("__file__", comps);
        assertIsIn("__name__", comps);
        assertIsIn("__dict__", comps);
        assertIsIn("par1", comps);
        assertIsIn("par2", comps);
        assertIsIn("met", comps);

        token = "";
        line = 3;
        col = 13;
        sDoc = "" +
                "class C:                         \n" +
                "    def met(self, par1, par2):   \n"
                +
                "        print                    \n";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected(7);
        assertIsIn("__name__", comps);
        assertIsIn("__dict__", comps);
        assertIsIn("__file__", comps);
        assertIsIn("par1", comps);
        assertIsIn("par2", comps);
        assertIsIn("self", comps);
        assertIsIn("C", comps);

        token = "";
        line = 4;
        col = 13;
        sDoc = "" +
                "class C:                         \n" +
                "    def met(self, par1, par2):   \n"
                +
                "        loc1 = 10                \n" +
                "        print                    \n";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected("__name__", "__file__", "__dict__", "par1", "loc1", "par2", "self", "C");

        token = "";
        line = 4;
        col = 13;
        sDoc = "" +
                "class C:                         \n" +
                "    def met(self, par1, par2):   \n"
                +
                "        loc1 = 10                \n" +
                "        print                    \n"
                +
                "        ignoreLineAfter = 5      \n";
        doc = new Document(sDoc);
        state = new CompletionState(line, col, token, nature, "");
        comps = getComps();
        checkExpected("__name__", "__file__", "__dict__", "par1", "loc1", "par2", "self", "C");
    }

    private static class ManagerObserver implements IASTManagerObserver {

        boolean called;

        public void notifyASTManagerAttached(ICodeCompletionASTManager manager) {
            called = true;
        }

    }

    /**
     * Check that registered observers are called when ASTManager is 
     * associated with project.
     */
    public void testManagerObserver() {
        Map<String, List<Object>> oldExtensions = ExtensionHelper.testingParticipants;
        try {
            ManagerObserver trackingObserver = new ManagerObserver();
            Map<String, List<Object>> extensions = new HashMap<String, List<Object>>();
            extensions
                    .put(ExtensionHelper.PYDEV_MANAGER_OBSERVER, Collections.<Object> singletonList(trackingObserver));

            ExtensionHelper.testingParticipants = extensions;
            restoreProjectPythonPath(false, TestDependent.TEST_PYSRC_LOC, "TestProject");
            assertTrue(trackingObserver.called);
        } finally {
            ExtensionHelper.testingParticipants = oldExtensions;
        }
    }

    /**
     * @param string
     * @param comps
     */
    public static void assertIsIn(String string, IToken[] comps) {
        StringBuffer buffer = new StringBuffer("Available: \n");
        boolean found = false;
        for (int i = 0; i < comps.length; i++) {
            String rep = comps[i].getRepresentation();
            if (string.equals(rep)) {
                found = true;
            }
            buffer.append(rep);
            buffer.append("\n");
        }

        assertTrue("The searched token (" + string +
                ")was not found in the completions. " + buffer, found);
    }

}
