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

import java.io.File;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;

import org.eclipse.compare.CompareConfiguration;
import org.eclipse.compare.IResourceProvider;
import org.eclipse.compare.ITypedElement;
import org.eclipse.compare.contentmergeviewer.TextMergeViewer;
import org.eclipse.compare.structuremergeviewer.ICompareInput;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextViewer;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.jface.text.source.SourceViewerConfiguration;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.DisposeEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartSite;
import org.eclipse.ui.IWorkbenchWindow;
import org.python.pydev.core.IGrammarVersionProvider;
import org.python.pydev.core.IIndentPrefs;
import org.python.pydev.core.IPythonNature;
import org.python.pydev.core.IPythonPartitions;
import org.python.pydev.core.MisconfigurationException;
import org.python.pydev.core.docutils.PySelection;
import org.python.pydev.core.log.Log;
import org.python.pydev.core.partition.PyPartitionScanner;
import org.python.pydev.editor.IPySyntaxHighlightingAndCodeCompletionEditor;
import org.python.pydev.editor.PyEdit;
import org.python.pydev.editor.PyEditConfiguration;
import org.python.pydev.editor.PyEditConfigurationWithoutEditor;
import org.python.pydev.editor.actions.FirstCharAction;
import org.python.pydev.editor.actions.PyBackspace;
import org.python.pydev.editor.actions.PyPeerLinker;
import org.python.pydev.editor.autoedit.DefaultIndentPrefs;
import org.python.pydev.plugin.nature.PythonNature;
import org.python.pydev.plugin.preferences.PydevPrefs;
import org.python.pydev.shared_ui.EditorUtils;
import org.python.pydev.ui.ColorAndStyleCache;

/**
 * Provides a viewer that provides:
 * - syntax highlighting
 * - indent strategy
 * - proper backspace
 * - code completion
 */
public class PyMergeViewer extends TextMergeViewer {

    private List<ColorAndStyleCache> colorCache;
    private List<IPropertyChangeListener> prefChangeListeners;

    public PyMergeViewer(Composite parent, int style, CompareConfiguration configuration) {
        super(parent, style | SWT.LEFT_TO_RIGHT, configuration);
    }

    @Override
    protected IDocumentPartitioner getDocumentPartitioner() {
        return PyPartitionScanner.createPyPartitioner();
    }

    /**
     * Overridden to handle the partitioning (will only work on Eclipse 3.3)
     */
    @Override
    protected String getDocumentPartitioning() {
        return IPythonPartitions.PYTHON_PARTITION_TYPE;
    }

    private IPythonNature getPythonNature(Object compareInput) {
        IResource resource = getResource(compareInput);
        if (resource != null) {
            return PythonNature.getPythonNature(resource);
        }
        return null;

    }

    private IResource getResource(Object compareInput) {
        if (!(compareInput instanceof ICompareInput)) {
            return null;
        }
        ICompareInput input = (ICompareInput) compareInput;

        IResourceProvider rp = null;
        ITypedElement te = input.getLeft();

        if (te instanceof IResourceProvider) {
            rp = (IResourceProvider) te;
        }

        if (rp == null) {
            te = input.getRight();
            if (te instanceof IResourceProvider) {
                rp = (IResourceProvider) te;
            }
        }

        if (rp == null) {
            te = input.getAncestor();
            if (te instanceof IResourceProvider) {
                rp = (IResourceProvider) te;
            }
        }
        if (rp != null) {
            return rp.getResource();
        }
        return null;
    }

    /**
     * Overridden to handle backspace (will only be called on Eclipse 3.5)
     */
    @Override
    protected SourceViewer createSourceViewer(Composite parent, int textOrientation) {
        final SourceViewer viewer = super.createSourceViewer(parent, textOrientation);
        viewer.appendVerifyKeyListener(PyPeerLinker.createVerifyKeyListener(viewer));
        viewer.appendVerifyKeyListener(PyBackspace.createVerifyKeyListener(viewer, null));
        IWorkbenchPart workbenchPart = getCompareConfiguration().getContainer().getWorkbenchPart();

        //Note that any site should be OK as it's just to know if a keybinding is active. 
        IWorkbenchPartSite site = null;
        if (workbenchPart != null) {
            site = workbenchPart.getSite();
        } else {
            IWorkbenchWindow window = EditorUtils.getActiveWorkbenchWindow();
            if (window != null) {
                IWorkbenchPage activePage = window.getActivePage();
                if (activePage != null) {
                    IWorkbenchPart activePart = activePage.getActivePart();
                    if (activePart != null) {
                        site = activePart.getSite();
                    }
                }
            }
        }
        VerifyKeyListener createVerifyKeyListener = FirstCharAction.createVerifyKeyListener(viewer, site, true);
        if (createVerifyKeyListener != null) {
            viewer.appendVerifyKeyListener(createVerifyKeyListener);
        }
        return viewer;
    }

    private List<ColorAndStyleCache> getColorCache() {
        if (this.colorCache == null) {
            this.colorCache = new ArrayList<ColorAndStyleCache>();
        }
        return this.colorCache;
    }

    public List<IPropertyChangeListener> getPrefChangeListeners() {
        if (this.prefChangeListeners == null) {
            this.prefChangeListeners = new ArrayList<IPropertyChangeListener>();
        }
        return this.prefChangeListeners;
    }

    @SuppressWarnings("unchecked")
    @Override
    protected void configureTextViewer(TextViewer textViewer) {
        if (!(textViewer instanceof SourceViewer)) {
            return;
        }
        final SourceViewer sourceViewer = (SourceViewer) textViewer;

        IAdaptable adaptable;
        if (sourceViewer instanceof IAdaptable) {
            adaptable = (IAdaptable) sourceViewer;
        } else {
            adaptable = new IAdaptable() {

                @Override
                public Object getAdapter(Class adapter) {
                    return null;
                }
            };
        }

        final IIndentPrefs indentPrefs = new DefaultIndentPrefs(adaptable);

        //Hack to provide the source viewer configuration that'll only be created later (there's a cycle there).
        final WeakReference<PyEditConfigurationWithoutEditor>[] sourceViewerConfigurationObj = new WeakReference[1];

        IPreferenceStore chainedPrefStore = PydevPrefs.getChainedPrefStore();
        final ColorAndStyleCache c = new ColorAndStyleCache(chainedPrefStore);
        this.getColorCache().add(c); //add for it to be disposed later.

        IPySyntaxHighlightingAndCodeCompletionEditor editor = new IPySyntaxHighlightingAndCodeCompletionEditor() {

            @Override
            public void resetForceTabs() {

            }

            @Override
            public void resetIndentPrefixes() {
                SourceViewerConfiguration configuration = getEditConfiguration();
                String[] types = configuration.getConfiguredContentTypes(sourceViewer);
                for (int i = 0; i < types.length; i++) {
                    String[] prefixes = configuration.getIndentPrefixes(sourceViewer, types[i]);
                    if (prefixes != null && prefixes.length > 0) {
                        sourceViewer.setIndentPrefixes(prefixes, types[i]);
                    }
                }
            }

            @Override
            public IIndentPrefs getIndentPrefs() {
                return indentPrefs;
            }

            @Override
            public ISourceViewer getEditorSourceViewer() {
                return sourceViewer;
            }

            @Override
            public PyEditConfigurationWithoutEditor getEditConfiguration() {
                return sourceViewerConfigurationObj[0].get();
            }

            @Override
            public ColorAndStyleCache getColorCache() {
                return c;
            }

            @Override
            public PySelection createPySelection() {
                ISelection selection = sourceViewer.getSelection();
                if (selection instanceof ITextSelection) {
                    return new PySelection(sourceViewer.getDocument(), (ITextSelection) selection);
                } else {
                    return null;
                }
            }

            @Override
            public File getEditorFile() {
                IResource file = PyMergeViewer.this.getResource(PyMergeViewer.this.getInput());
                if (file != null && file instanceof IFile) {
                    IPath path = file.getLocation().makeAbsolute();
                    return path.toFile();
                }
                return null;
            }

            @Override
            public IPythonNature getPythonNature() throws MisconfigurationException {
                return PyMergeViewer.this.getPythonNature(PyMergeViewer.this.getInput());
            }

            @Override
            public int getGrammarVersion() throws MisconfigurationException {
                IPythonNature pythonNature = this.getPythonNature();
                if (pythonNature == null) {
                    Log.logInfo("Expected to get the PythonNature at this point...");
                    return IGrammarVersionProvider.LATEST_GRAMMAR_VERSION;
                }
                return pythonNature.getGrammarVersion();
            }

            @Override
            public AdditionalGrammarVersionsToCheck getAdditionalGrammarVersions() throws MisconfigurationException {
                IPythonNature pythonNature = this.getPythonNature();
                if (pythonNature == null) {
                    Log.logInfo("Expected to get the PythonNature at this point...");
                    return null;
                }
                return pythonNature.getAdditionalGrammarVersions();
            }

            @Override
            public Object getAdapter(Class adapter) {
                if (adapter == IResource.class) {
                    return PyMergeViewer.this.getResource(PyMergeViewer.this.getInput());
                }
                if (adapter == IFile.class) {
                    IResource resource = PyMergeViewer.this.getResource(PyMergeViewer.this.getInput());
                    if (resource instanceof IFile) {
                        return resource;
                    }
                }
                if (adapter == IProject.class) {
                    IResource resource = PyMergeViewer.this.getResource(PyMergeViewer.this.getInput());
                    if (resource instanceof IFile) {
                        return resource.getProject();
                    }
                }
                return null;
            }

        };

        final PyEditConfiguration sourceViewerConfiguration = new PyEditConfiguration(c, editor, chainedPrefStore);
        sourceViewerConfiguration.getPyAutoIndentStrategy(editor); // Force its initialization
        sourceViewerConfigurationObj[0] = new WeakReference<PyEditConfigurationWithoutEditor>(
                sourceViewerConfiguration);
        sourceViewer.configure(sourceViewerConfiguration);

        IPropertyChangeListener prefChangeListener = PyEdit.createPrefChangeListener(editor);
        getPrefChangeListeners().add(prefChangeListener);
        chainedPrefStore.addPropertyChangeListener(prefChangeListener);
    }

    @Override
    protected void handleDispose(DisposeEvent event) {
        super.handleDispose(event);

        List<ColorAndStyleCache> colorCache = getColorCache();
        for (ColorAndStyleCache c : colorCache) {
            c.dispose();
        }
        colorCache.clear();

        List<IPropertyChangeListener> prefChangeListeners = getPrefChangeListeners();
        for (IPropertyChangeListener l : prefChangeListeners) {
            PydevPrefs.getChainedPrefStore().removePropertyChangeListener(l);
        }
        prefChangeListeners.clear();
    }

}
