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

import java.util.HashMap;
import java.util.Map;

import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.preference.PreferenceStore;
import org.eclipse.jface.text.Document;
import org.eclipse.jface.text.IDocument;
import org.eclipse.ui.IEditorInput;
import org.python.pydev.core.IGrammarVersionProvider;
import org.python.pydev.core.IIndentPrefs;
import org.python.pydev.core.IPyEdit;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.parser.IPyParser;
import org.python.pydev.shared_core.editor.IBaseEditor;
import org.python.pydev.shared_core.model.IModelListener;
import org.python.pydev.shared_core.model.ISimpleNode;

import junit.framework.TestCase;

public class PyParserEditorIntegrationTest extends TestCase {

    static class PyEditStub implements IPyEdit {
        public IDocument doc;
        public int parserChanged;
        private Map<String, Object> cache = new HashMap<String, Object>();
        private PydevFileEditorInputStub pydevFileEditorInputStub;

        public PyEditStub(IDocument doc, PydevFileEditorInputStub pydevFileEditorInputStub) {
            this.doc = doc;
            this.pydevFileEditorInputStub = pydevFileEditorInputStub;
        }

        @Override
        public IEditorInput getEditorInput() {
            return pydevFileEditorInputStub;
        }

        @Override
        public IPythonNature getPythonNature() {
            return new PythonNatureStub();
        }

        public void setParser(IPyParser parser) {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public void parserChanged(ISimpleNode root, IAdaptable file, IDocument doc, long docModificationStamp) {
            this.parserChanged += 1;
        }

        @Override
        public void parserError(Throwable error, IAdaptable file, IDocument doc) {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public Map<String, Object> getCache() {
            return this.cache;
        }

        @Override
        public <T> T getAdapter(Class<T> adapter) {
            return null;
        }

        @Override
        public boolean hasSameInput(IBaseEditor edit) {
            if (this == edit) {
                throw new RuntimeException(
                        "Cannot compare when it's the same... it should change the document in this case");
            }
            if (edit.getEditorInput() == getEditorInput()) {
                return true;
            }
            return false;
        }

        @Override
        public IDocument getDocument() {
            return doc;
        }

        public void setDocument(IDocument doc) {
            this.doc = doc;
        }

        public void setInput(PydevFileEditorInputStub input) {
            this.pydevFileEditorInputStub = input;
        }

        @Override
        public void setStatusLineErrorMessage(String msg) {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public IGrammarVersionProvider getGrammarVersionProvider() {
            return this.getPythonNature();
        }

        @Override
        public IIndentPrefs getIndentPrefs() {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public Object getFormatStd() {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public void addModelListener(IModelListener modelListener) {
            throw new RuntimeException("Not implemented");
        }

        @Override
        public void removeModelListener(IModelListener modelListener) {
            throw new RuntimeException("Not implemented");
        }
    }

    public static void main(String[] args) {
        try {
            PyParserEditorIntegrationTest test = new PyParserEditorIntegrationTest();
            test.setUp();
            test.testChangeInput();
            test.tearDown();
            System.out.println("Finished");
            junit.textui.TestRunner.run(PyParserEditorIntegrationTest.class);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Override
    protected void setUp() throws Exception {
        PyParserManager.setPyParserManager(null);
    }

    @Override
    protected void tearDown() throws Exception {
        PyParserManager.setPyParserManager(null);
    }

    public void testIntegration() throws Exception {
        PreferenceStore preferences = new PreferenceStore();
        PyParserManager pyParserManager = PyParserManager.getPyParserManager(preferences);

        Document doc = new Document();
        PyEditStub pyEdit = new PyEditStub(doc, new PydevFileEditorInputStub());
        pyParserManager.attachParserTo(pyEdit);
        checkParserChanged(pyEdit, 1);

        doc.replace(0, 0, "\r\ntest");
        checkParserChanged(pyEdit, 2);

        pyParserManager.attachParserTo(pyEdit);
        checkParserChanged(pyEdit, 3);

        doc.replace(0, 0, "\r\ntest"); //after this change, only 1 reparse should be asked, as the editor and doc is the same
        checkParserChanged(pyEdit, 4);

        pyParserManager.notifyEditorDisposed(pyEdit);
        doc.replace(0, 0, "\r\ntest"); //after this change, only 1 reparse should be asked, as the editor and doc is the same
        waitABit();
        assertEquals(4, pyEdit.parserChanged);

        assertEquals(0, pyParserManager.getParsers().size());
    }

    public void testDifferentEditorsSameInput() throws Exception {
        PreferenceStore preferences = new PreferenceStore();
        PyParserManager pyParserManager = PyParserManager.getPyParserManager(preferences);

        Document doc = new Document();
        PydevFileEditorInputStub input = new PydevFileEditorInputStub();
        //create them with the same input
        PyEditStub pyEdit1 = new PyEditStub(doc, input);
        PyEditStub pyEdit2 = new PyEditStub(doc, input);
        pyParserManager.attachParserTo(pyEdit1);
        checkParserChanged(pyEdit1, 1);

        doc.replace(0, 0, "\r\ntest");
        checkParserChanged(pyEdit1, 2);

        pyParserManager.attachParserTo(pyEdit2);
        checkParserChanged(pyEdit1, 3);
        checkParserChanged(pyEdit2, 1);

        IDocument doc2 = new Document();
        pyEdit2.setDocument(doc2);
        pyParserManager.notifyEditorDisposed(pyEdit1);

        checkParserChanged(pyEdit1, 3);
        checkParserChanged(pyEdit2, 2);

        assertNull(pyParserManager.getParser(pyEdit1));
        doc2.replace(0, 0, "\r\ntest");
        checkParserChanged(pyEdit1, 3);
        checkParserChanged(pyEdit2, 3);

        doc.replace(0, 0, "\r\ntest"); //no one's listening this one anymore
        waitABit();
        checkParserChanged(pyEdit1, 3);
        checkParserChanged(pyEdit2, 3);

        pyParserManager.notifyEditorDisposed(pyEdit2);
        assertNull(pyParserManager.getParser(pyEdit2));
        doc2.replace(0, 0, "\r\ntest"); //no one's listening this one anymore
        doc.replace(0, 0, "\r\ntest"); //no one's listening this one anymore
        waitABit();
        checkParserChanged(pyEdit1, 3);
        checkParserChanged(pyEdit2, 3);
        assertEquals(0, pyParserManager.getParsers().size());

    }

    public void testChangeInput() throws Exception {
        PreferenceStore preferences = new PreferenceStore();
        PyParserManager pyParserManager = PyParserManager.getPyParserManager(preferences);

        Document doc = new Document();
        PydevFileEditorInputStub input1 = new PydevFileEditorInputStub();
        PydevFileEditorInputStub input2 = new PydevFileEditorInputStub();
        //create them with the same input
        PyEditStub pyEdit1 = new PyEditStub(doc, input1);
        PyEditStub pyEdit2 = new PyEditStub(doc, input2);

        pyParserManager.attachParserTo(pyEdit1);
        pyParserManager.attachParserTo(pyEdit2);
        assertEquals(2, pyParserManager.getParsers().size());

        pyEdit2.setInput(input1);
        pyParserManager.attachParserTo(pyEdit2);
        assertEquals(1, pyParserManager.getParsers().size());

        pyEdit2.setInput(input2); //different input
        pyParserManager.attachParserTo(pyEdit2);
        assertEquals(2, pyParserManager.getParsers().size());

        pyParserManager.notifyEditorDisposed(pyEdit1);
        pyParserManager.notifyEditorDisposed(pyEdit2);
        assertEquals(0, pyParserManager.getParsers().size());
    }

    private void waitABit() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            synchronized (this) {
                this.wait(25);
            }
        }
    }

    Object lock = new Object();

    private void checkParserChanged(PyEditStub pyEdit, int expected) throws InterruptedException {
        for (int i = 0; i < 20 && pyEdit.parserChanged < expected; i++) {
            synchronized (lock) {
                lock.wait(250);
            }
        }
        assertEquals(expected, pyEdit.parserChanged);
    }
}
