/*******************************************************************************
 * Copyright (c) 2017 the TeXlipse team and others.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     The TeXlipse team - initial API and implementation
 *******************************************************************************/
package org.eclipse.texlipse.editor;

import java.util.ArrayList;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextViewerExtension;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.IVerticalRuler;
import org.eclipse.jface.text.source.projection.ProjectionSupport;
import org.eclipse.jface.text.source.projection.ProjectionViewer;
import org.eclipse.jface.viewers.IPostSelectionProvider;
import org.eclipse.jface.viewers.ISelectionChangedListener;
import org.eclipse.jface.viewers.SelectionChangedEvent;
import org.eclipse.swt.custom.VerifyKeyListener;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.texlipse.TexlipsePlugin;
import org.eclipse.texlipse.model.TexDocumentModel;
import org.eclipse.texlipse.outline.TexOutlinePage;
import org.eclipse.texlipse.properties.TexlipseProperties;
import org.eclipse.texlipse.treeview.views.TexOutlineTreeView;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.editors.text.TextEditor;
import org.eclipse.ui.texteditor.ITextEditorActionDefinitionIds;
import org.eclipse.ui.texteditor.SourceViewerDecorationSupport;
import org.eclipse.ui.texteditor.TextOperationAction;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;


/**
 * The Latex editor.
 * 
 * @author Oskar Ojala
 * @author Taavi Hupponen
 * @author Boris von Loesch
 */
public class TexEditor extends TextEditor {

    public final static String TEX_PARTITIONING= "__tex_partitioning";

    /** The editor's bracket matcher */
    private TexPairMatcher fBracketMatcher = new TexPairMatcher("()[]{}");
    
    private TexOutlinePage outlinePage;
    private TexOutlineTreeView fullOutline;
    private TexDocumentModel documentModel;
    private TexCodeFolder folder;
    private ProjectionSupport fProjectionSupport;
    private BracketInserter fBracketInserter;
    private TexlipseAnnotationUpdater fAnnotationUpdater;
    
    
    /**
     * Constructs a new editor.
     */
    public TexEditor() {
        super();
        //setRangeIndicator(new DefaultRangeIndicator());
        folder = new TexCodeFolder(this);
    }

    /** 
     * Create the part control.
     * 
     * @see org.eclipse.ui.IWorkbenchPart#createPartControl(org.eclipse.swt.widgets.Composite)
     */
    public void createPartControl(Composite parent) {
        super.createPartControl(parent);
        // enable projection support (for code folder)
        ProjectionViewer projectionViewer = (ProjectionViewer) getSourceViewer();
        fProjectionSupport = new ProjectionSupport(projectionViewer,
                getAnnotationAccess(), getSharedColors());
        fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.error");
        fProjectionSupport.addSummarizableAnnotationType("org.eclipse.ui.workbench.texteditor.warning");
        fProjectionSupport.install();
    
        if (TexlipsePlugin.getDefault().getPreferenceStore().getBoolean(TexlipseProperties.CODE_FOLDING)) {
        	projectionViewer.doOperation(ProjectionViewer.TOGGLE);
        }
        
        fAnnotationUpdater = new TexlipseAnnotationUpdater(this);
        
        ((IPostSelectionProvider) getSelectionProvider()).addPostSelectionChangedListener(
                new ISelectionChangedListener(){
                    public void selectionChanged(SelectionChangedEvent event) {
                        //Delete all StatuslineErrors after selection changes
                        documentModel.removeStatusLineErrorMessage();
                    }
                });

        // register documentModel as documentListener
        // in initializeEditor this would cause NPE
        this.getDocumentProvider().getDocument(getEditorInput()).addDocumentListener(this.documentModel);
        this.documentModel.initializeModel();
        this.documentModel.updateNow();

        ISourceViewer sourceViewer = getSourceViewer();
        if (sourceViewer instanceof ITextViewerExtension) {
            if (fBracketInserter == null)
                fBracketInserter = new BracketInserter(getSourceViewer(), this);
            ((ITextViewerExtension) sourceViewer).prependVerifyKeyListener(fBracketInserter);
        }
    }

    /** 
     * Initialize TexDocumentModel and enable latex support in projects
     * other than Latex Project.
     * 
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#initializeEditor()
     */
    protected void initializeEditor() {
        super.initializeEditor();
        setEditorContextMenuId("org.eclipse.texlipse.texEditorScope");
        this.documentModel = new TexDocumentModel(this);
        setSourceViewerConfiguration(new TexSourceViewerConfiguration(this));
        // register a document provider to get latex support even in non-latex projects
        if (getDocumentProvider() == null) {
            setDocumentProvider(new TexDocumentProvider());
        }
    }
    
    /** 
     * Create, configure and return the SourceViewer.
     * 
     * @see org.eclipse.ui.texteditor.AbstractTextEditor#createSourceViewer(org.eclipse.swt.widgets.Composite, org.eclipse.jface.text.source.IVerticalRuler, int)
     */
    protected ISourceViewer createSourceViewer(Composite parent, IVerticalRuler ruler, int styles) {
        ProjectionViewer viewer = new ProjectionViewer(parent, ruler, getOverviewRuler(), true, styles); 
        getSourceViewerDecorationSupport(viewer);
        return viewer;
    }

    /** 
     * @see org.eclipse.ui.texteditor.AbstractDecoratedTextEditor#configureSourceViewerDecorationSupport(org.eclipse.ui.texteditor.SourceViewerDecorationSupport)
     */
    protected void configureSourceViewerDecorationSupport(SourceViewerDecorationSupport support) {
        // copy the necessary values from plugin preferences instead of overwriting editor preferences
        getPreferenceStore().setValue(TexlipseProperties.MATCHING_BRACKETS, TexlipsePlugin.getPreference(TexlipseProperties.MATCHING_BRACKETS));
        getPreferenceStore().setValue(TexlipseProperties.MATCHING_BRACKETS_COLOR, TexlipsePlugin.getPreference(TexlipseProperties.MATCHING_BRACKETS_COLOR));
        
        support.setCharacterPairMatcher(fBracketMatcher);
        support.setMatchingCharacterPainterPreferenceKeys(TexlipseProperties.MATCHING_BRACKETS, TexlipseProperties.MATCHING_BRACKETS_COLOR);

        super.configureSourceViewerDecorationSupport(support);
    }
    
    /** 
     * @see org.eclipse.ui.texteditor.AbstractTextEditor#createActions()
     */
    protected void createActions() {
        super.createActions();
        
        IAction a = new TextOperationAction(TexlipsePlugin.getDefault().getResourceBundle(), "ContentAssistProposal.", this, ISourceViewer.CONTENTASSIST_PROPOSALS);
        a.setActionDefinitionId(ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS);
        setAction("ContentAssistProposal", a);
    }
    
    /**
     * @return The source viewer of this editor
     */
    public ISourceViewer getViewer(){
    	return getSourceViewer();
    }
    
    /**
     * Used by platform to get the OutlinePage and ProjectionSupport 
     * adapter. 
     *  
     * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
     */
    public Object getAdapter(Class required) {
        if (IContentOutlinePage.class.equals(required)) {
            if (this.outlinePage == null) {
                this.outlinePage = new TexOutlinePage(this);
                this.documentModel.updateOutline();
            }
            return outlinePage;
        } else if (fProjectionSupport != null) {
            Object adapter = fProjectionSupport.getAdapter(getSourceViewer(), required);
            if (adapter != null)
                return adapter;
        }
        return super.getAdapter(required);
    }
    
    /**
     * @return The outline page associated with this editor
     */
    public TexOutlinePage getOutlinePage() {
        return this.outlinePage;
    }
    
    /**
     * @return Returns the documentModel.
     */
    public TexDocumentModel getDocumentModel() {
        return documentModel;
    }

    /**
     * @return the preference store of this editor
     */
    public IPreferenceStore getPreferences() {
        return getPreferenceStore();
    }

    /** 
     * Triggers parsing. If there is a way to determine whether the
     * platform is currently being shut down, triggering of parsing in 
     * such a case could be skipped.
     * 
     * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor)
     */
    public void doSave(IProgressMonitor monitor) {
        super.doSave(monitor);
        this.documentModel.updateNow();
    }
    
    /**
     * Updates the code folding of this editor.
     * 
     * @param rootNodes The document tree that correspond to folds
     * @param monitor A progress monitor for the job doing the update
     */
    public void updateCodeFolder(ArrayList rootNodes, IProgressMonitor monitor) {
        this.folder.update(rootNodes);        
    }

    /**
     * Triggers the model to be updated as soon as possible.
     * 
     * Used by drag'n'drop and copy paste actions of the outline.
     */
    public void updateModelNow() {
    	this.documentModel.updateNow();
    }
    
    /**
     * Used by outline to determine whether drag'n'drop operations
     * are permitted.
     * 
     * @return true if current model is dirty
     */
    public boolean isModelDirty() {
        return this.documentModel.isDirty();
    }

    /**
     * @see org.eclipse.ui.IWorkbenchPart#dispose()
     */
    public void dispose() {
        super.dispose();
    }
    
//  B----------------------------------- mmaus
    
    /**
     * 
     * @return the fullOutline view.
     */
    public TexOutlineTreeView getFullOutline() {
        return fullOutline;
    }
    
    /**
     * register the full outline.
     * @param view the view.
     */
    public void registerFullOutline(TexOutlineTreeView view) {
        boolean projectChange = false;
        if (view == null || view.getEditor() == null) {
            projectChange = true;
        }
        else if (view.getEditor().getEditorInput() instanceof IFileEditorInput) {
            IFileEditorInput oldInput = (IFileEditorInput) view.getEditor().getEditorInput();
            IProject newProject = getProject();
            // Check whether the project changes
            if (!oldInput.getFile().getProject().equals(newProject))
                projectChange = true;
        } else
            projectChange = true;

        this.fullOutline = view;
        this.fullOutline.setEditor(this);
        if (projectChange) {
            //If the project changes we have to update the fulloutline
            this.fullOutline.projectChanged();
            this.documentModel.updateNow();
        }
    }
    
    /**
     * unregister the full outline if the view is closed.
     * @param view the view.
     */
    public void unregisterFullOutline(TexOutlineTreeView view) {
        this.fullOutline = null;
        
    }
    
    public IDocument getTexDocument(){
        return this.getDocumentProvider().getDocument(getEditorInput());
    } 
    
//  E----------------------------------- mmaus
    
    /**
     * @return The project that belongs to the current file
     * or null if it does not belong to any project
     */
    public IProject getProject() {
        IResource res = (IResource) getEditorInput().getAdapter(IResource.class);
        if (res == null) return null;
        else return res.getProject();
    }
    
    /**
     * Initializes the key binding scopes of this editor.
     */
    protected void initializeKeyBindingScopes() {
        setKeyBindingScopes(new String[] { "org.eclipse.ui.textEditorScope", "org.eclipse.texlipse.texEditorScope" });  //$NON-NLS-1$
    }}