/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.hibernate.eclipse.hqleditor;


import java.util.ResourceBundle;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.ITextListener;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.TextEvent;
import org.eclipse.jface.text.source.DefaultCharacterPairMatcher;
import org.eclipse.jface.text.source.ICharacterPairMatcher;
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.swt.custom.StyledText;
import org.eclipse.swt.events.KeyAdapter;
import org.eclipse.swt.events.KeyEvent;
import org.eclipse.swt.events.MouseAdapter;
import org.eclipse.swt.events.MouseEvent;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.texteditor.DefaultRangeIndicator;
import org.eclipse.ui.texteditor.IDocumentProvider;
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;
import org.hibernate.console.ConsoleConfiguration;
import org.hibernate.console.KnownConfigurations;
import org.hibernate.console.QueryPage;
import org.hibernate.eclipse.console.AbstractQueryEditor;
import org.hibernate.eclipse.console.HibernateConsoleMessages;
import org.hibernate.eclipse.console.HibernateConsolePlugin;
import org.hibernate.eclipse.console.views.IQueryParametersPage;
import org.hibernate.eclipse.console.views.QueryPageTabView;
import org.hibernate.eclipse.console.views.QueryParametersPage;


/**
 * HQL Editor
 */
public class HQLEditor extends AbstractQueryEditor {


	public static final String PLUGIN_NAME = HibernateConsolePlugin.ID;
	public static final String HELP_CONTEXT_ID = PLUGIN_NAME + ".hqleditorhelp"; //$NON-NLS-1$

	/** The HQL code scanner, which is used for colorizing the edit text. */
	private HQLCodeScanner fHQLCodeScanner;
	/** The document setup participant object, which is used partition the edit text. */
	private HQLEditorDocumentSetupParticipant docSetupParticipant;
	/** The projection (code folding) support object. */
	private ProjectionSupport fProjectionSupport;



	/**
	 * Constructs an instance of this class. This is the default constructor.
	 */
	public HQLEditor() {
	    super();
	}

	/**
	 * Creates and installs the editor actions.
	 *
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor#createActions()
	 */
	protected void createActions() {
	    super.createActions();
	    ResourceBundle bundle = getResourceBundle();

	    IAction a = new TextOperationAction( bundle,
	            "HQLEditor_ContentAssistProposal_", this, ISourceViewer.CONTENTASSIST_PROPOSALS ); //$NON-NLS-1$
	    a.setActionDefinitionId( ITextEditorActionDefinitionIds.CONTENT_ASSIST_PROPOSALS );
	    setAction( "ContentAssistProposal", a ); //$NON-NLS-1$

	    a = new TextOperationAction( bundle, "HQLEditor_ContentAssistTip_", this, ISourceViewer.CONTENTASSIST_CONTEXT_INFORMATION ); //$NON-NLS-1$
	    a.setActionDefinitionId( ITextEditorActionDefinitionIds.CONTENT_ASSIST_CONTEXT_INFORMATION );
	    setAction( "ContentAssistTip", a ); //$NON-NLS-1$

	    a = new TextOperationAction( bundle, "HQLEditor_ContentFormat_", this, ISourceViewer.FORMAT ); //$NON-NLS-1$
	    setAction( "ContentFormat", a ); //$NON-NLS-1$

	    /*a = new HQLConnectAction( bundle, "HQLEditor.connectAction." ); //$NON-NLS-1$
	    setAction( "HQLEditor.connectAction", a ); //$NON-NLS-1$
	    */

	    /*a = new HQLDisconnectAction( bundle, "HQLEditor.disconnectAction." ); //$NON-NLS-1$
	    setAction( "HQLEditor.disconnectAction", a ); //$NON-NLS-1$
	    */

	//        a = new ExecuteQueryAction( this ); //$NON-NLS-1$
	//        setAction( "HQLEditor.runAction", a ); //$NON-NLS-1$

	    /*a = new HQLSetStatementTerminatorAction( bundle, "HQLEditor.setStatementTerminatorAction." ); //$NON-NLS-1$
	    setAction( "HQLEditor.setStatementTerminatorAction", a ); //$NON-NLS-1$
	    */
	}

	private ResourceBundle getResourceBundle() {
		return ResourceBundle.getBundle( HibernateConsoleMessages.BUNDLE_NAME );
	}

	/**
	 * Creates the SWT controls for the editor.
	 *
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor#createPartControl(org.eclipse.swt.widgets.Composite)
	 */
	public void createPartControl( Composite parent ) {
		parent.setLayout( new GridLayout(1,false) );

		createToolbar(parent);

		super.createPartControl( parent );

		if (getSourceViewer() != null ){
			getSourceViewer().addTextListener(new ITextListener(){

				public void textChanged(TextEvent event) {
					updateExecButton();
				}});
		}

		// move to base class?
		Control control = parent.getChildren()[1];
		control.setLayoutData( new GridData( GridData.FILL_BOTH ) );

	    setProjectionSupport( createProjectionSupport() );

	    /* Now that we have enabled source folding, make sure everything is
	     * expanded.
	     */
	    ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
	    viewer.doOperation( ProjectionViewer.TOGGLE );

	    /* Set a help context ID to enable F1 help. */
	    getSite().getWorkbenchWindow().getWorkbench().getHelpSystem().setHelp( parent, HELP_CONTEXT_ID );

	    // the following is needed to make sure the editor area gets focus when editing after query execution
	    // TODO: find a better way since this is triggered on every mouse click and key stroke in the editor area
    	// one more remark: without this code -> JBIDE-4446
	    StyledText textWidget = getSourceViewer().getTextWidget();
		textWidget.addKeyListener(new KeyAdapter() {

			public void keyPressed(KeyEvent e) {
				getSite().getPage().activate(HQLEditor.this);
			}

		});
		textWidget.addMouseListener(new MouseAdapter() {

			public void mouseDown(MouseEvent e) {
				getSite().getPage().activate(HQLEditor.this);
			}

		});
		initTextAndToolTip(HibernateConsoleMessages.ExecuteQueryAction_run_hql);
	}

	/**
	 * Creates, configures, and returns a <code>ProjectionSupport</code>
	 * object for this editor.
	 *
	 * @return the <code>ProjectSupport</code> object to use with this editor
	 */
	protected ProjectionSupport createProjectionSupport() {
	    ProjectionViewer viewer = (ProjectionViewer) getSourceViewer();
	    ProjectionSupport projSupport = new ProjectionSupport( viewer, getAnnotationAccess(), getSharedColors() );
	    projSupport.addSummarizableAnnotationType( "org.eclipse.ui.workbench.texteditor.error" ); //$NON-NLS-1$
	    projSupport.addSummarizableAnnotationType( "org.eclipse.ui.workbench.texteditor.warning" ); //$NON-NLS-1$
	    projSupport.install();

	    return projSupport;
	}

	/**
	 * Creates the source viewer to be used by this editor.
	 *
	 * @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 ) {
	    HQLSourceViewer viewer = new HQLSourceViewer( parent, ruler, getOverviewRuler(), isOverviewRulerVisible(),
	            styles );
	    // ensure decoration support has been created and configured.
		getSourceViewerDecorationSupport(viewer);
	    return viewer;
	}

	/**
	 * Creates the source viewer configuation to be used by this editor.
	 *
	 * @return the new source viewer configuration object
	 */
	    protected HQLSourceViewerConfiguration createSourceViewerConfiguration() {
	        HQLSourceViewerConfiguration config = new HQLSourceViewerConfiguration( this );

	        return config;
	    }

	    /**
	 * Sets the input of the outline page after this class has set input.
	 *
	 * @param input the new input for the editor
	 * @see org.eclipse.ui.editors.text.TextEditor#doSetInput(org.eclipse.ui.IEditorInput)
	 */
	public void doSetInput( IEditorInput input ) throws CoreException {
	    super.doSetInput( input );

	    /* Make sure the document partitioner is set up. The document setup
	     * participant sets up document partitioning, which is used for text
	     * colorizing and other text features.
	     */
	    IDocumentProvider docProvider = this.getDocumentProvider();
	    if (docProvider != null) {
	        IDocument doc = docProvider.getDocument( input );
	        if (doc != null) {
	            HQLEditorDocumentSetupParticipant docSetupParticipant = getDocumentSetupParticipant();
	            docSetupParticipant.setup( doc );
	        }
	    }

	}

	/**
	 * Sets up this editor's context menu before it is made visible.
	 *
	 * @see org.eclipse.ui.texteditor.AbstractTextEditor#editorContextMenuAboutToShow(org.eclipse.jface.action.IMenuManager)
	 */
	protected void editorContextMenuAboutToShow( IMenuManager menu ) {
	    super.editorContextMenuAboutToShow( menu );

	    menu.add( new Separator() );
	    addAction( menu, "ContentAssistProposal" ); //$NON-NLS-1$
	    addAction( menu, "ContentAssistTip" ); //$NON-NLS-1$
	    addAction( menu, "ContentFormat" ); //$NON-NLS-1$

	    menu.add( new Separator() );
	    /*if (getConnectionInfo() == null) {
	        addAction( menu, "HQLEditor.connectAction" ); //$NON-NLS-1$
	    }
	    else {
	        addAction( menu, "HQLEditor.disconnectAction" ); //$NON-NLS-1$
	    }*/
	    addAction( menu, "HQLEditor.runAction" ); //$NON-NLS-1$
	    //addAction( menu, "HQLEditor.setStatementTerminatorAction" ); //$NON-NLS-1$
	}

	/**
	 * Gets an adapter for the given class. Returns the HQL content outline page
	 * if the get request is for an outline page. Otherwise returns a projection
	 * adapter if one hasn't already been created.
	 *
	 * @see org.eclipse.core.runtime.IAdaptable#getAdapter(java.lang.Class)
	 * @see org.eclipse.jface.text.source.projection.ProjectionSupport#getAdapter(org.eclipse.jface.text.source.ISourceViewer,
	 *      java.lang.Class)
	 */
	@SuppressWarnings("unchecked")
	public Object getAdapter( Class classForWhichAdapterNeeded ) {
	    Object adapter = null;

	    if(IQueryParametersPage.class.equals( classForWhichAdapterNeeded )) {
	    	return new QueryParametersPage(this);
	    }
	    /* Get and return the content outline page, if that's what's requested. */
	  if (IContentOutlinePage.class.equals( classForWhichAdapterNeeded )) {
	//            HQLEditorContentOutlinePage outlinePage = getOutlinePage();
	//            if (outlinePage == null) {
	//                outlinePage = createContentOutlinePage();
	//                setOutlinePage( outlinePage );
	//                if (getEditorInput() != null) {
	//                    outlinePage.setInput( getEditorInput() );
	//                }
	//            }
	  //          adapter = outlinePage;
		  adapter = null;
	    }
	    /* Delegate getting the adapter to the projection support object,
	     * if there is one. Projection refers to the ability to visibly collapse
	     * and expand sections of the document.
	     */
	    else if (adapter == null) {
	        ProjectionSupport projSupport = getProjectionSupport();
	        if (projSupport != null) {
	            adapter = projSupport.getAdapter( getSourceViewer(), classForWhichAdapterNeeded );
	        }
	    }

	    /* If we still don't have an adapter let the superclass handle it. */
	    if (adapter == null) {
	        adapter = super.getAdapter( classForWhichAdapterNeeded );
	    }

	    return adapter;
	}

	/**
	 * Gets the document setup participant object associated with this editor.
	 * The setup participant sets the partitioning type for the document.
	 *
	 * @return the current document setup participant
	 */
	public HQLEditorDocumentSetupParticipant getDocumentSetupParticipant() {
	    if (docSetupParticipant == null) {
	        docSetupParticipant = new HQLEditorDocumentSetupParticipant();
	    }
	    return docSetupParticipant;
	}


	/**
	 * Gets the <code>ProjectionSupport</code> object associated with this
	 * editor.
	 *
	 * @return the current <code>ProjectionSupport</code> object
	 */
	protected ProjectionSupport getProjectionSupport() {
	    return fProjectionSupport;
	}


	/**
	 * Gets the HQL source code text scanner. Creates a default one if it
	 * doesn't exist yet.
	 *
	 * @return the HQL source code text scanner
	 */
	public HQLCodeScanner getHQLCodeScanner() {
	    if (fHQLCodeScanner == null) {
	        fHQLCodeScanner = new HQLCodeScanner( getHQLColorProvider() );
	    }
	    return fHQLCodeScanner;
	}


	/**
	 * Gets the color provider for colorizing HQL source code.
	 *
	 * @return the HQL color provider
	 */
	public HQLColors getHQLColorProvider() {
	    return new HQLColors();
	}

	/**
	 * Initializes the editor.
	 *
	 * @see org.eclipse.ui.editors.text.TextEditor#initializeEditor()
	 */
	protected void initializeEditor() {
	    super.initializeEditor();
	    setSourceViewerConfiguration( createSourceViewerConfiguration() );
	    setRangeIndicator( new DefaultRangeIndicator() );
	}

	public final static String HQL_EDITOR_MATCHING_BRACKETS = "matchingBrackets"; //$NON-NLS-1$
	public final static String HQL_EDITOR_MATCHING_BRACKETS_COLOR = "matchingBracketsColor"; //$NON-NLS-1$

    @Override
    protected void configureSourceViewerDecorationSupport(
    		SourceViewerDecorationSupport support) {
    	super.configureSourceViewerDecorationSupport(support);		

    	char[] matchChars = {'(', ')', '[', ']', '{', '}'}; //which brackets to match		
    	ICharacterPairMatcher matcher = new DefaultCharacterPairMatcher(matchChars ,
    			IDocumentExtension3.DEFAULT_PARTITIONING);
    	support.setCharacterPairMatcher(matcher);
    	support.setMatchingCharacterPainterPreferenceKeys(HQL_EDITOR_MATCHING_BRACKETS,HQL_EDITOR_MATCHING_BRACKETS_COLOR);

    	//Enable bracket highlighting in the preference store
    	IPreferenceStore store = getPreferenceStore();
    	store.setDefault(HQL_EDITOR_MATCHING_BRACKETS, true);
    	store.setDefault(HQL_EDITOR_MATCHING_BRACKETS_COLOR, "128,128,128"); //$NON-NLS-1$
    }


	    /**
	 * Sets the document setup participant object associated with this editor to
	 * the given object. The setup participant sets the partitioning type for
	 * the document.
	 *
	 * @return the current document setup participant
	 */
	public void setDocumentSetupParticipant( HQLEditorDocumentSetupParticipant docSetupParticipant ) {
	    this.docSetupParticipant = docSetupParticipant;
	}

	/**
	 * Sets the <code>ProjectionSupport</code> object associated with this
	 * editor.
	 *
	 * @param projSupport the <code>ProjectionSupport</code> object to use
	 */
	    protected void setProjectionSupport( ProjectionSupport projSupport ) {
	        fProjectionSupport = projSupport;
	    }


	   public ITextViewer getTextViewer() {
		   return getSourceViewer();
	   }

	protected QueryPage queryPage = null;

	public void executeQuery(ConsoleConfiguration cfg) {
		final IWorkbenchPage activePage = getEditorSite().getPage();
		try {
			activePage.showView(QueryPageTabView.ID);
		} catch (PartInitException e) {	
			// ignore
		}
		if (queryPage == null || !getPinToOneResTab()) {
			queryPage = cfg.executeHQLQuery(getQueryString(), getQueryInputModel().getCopyForQuery());
		} else {
			KnownConfigurations.getInstance().getQueryPageModel().remove(queryPage);
			queryPage = cfg.executeHQLQuery(getQueryString(), getQueryInputModel().getCopyForQuery());
		}
	}

	@Override
	protected String getConnectedImageFilePath() {
		return 	"icons/images/hql_editor_connect.gif";		//$NON-NLS-1$
	}


	@Override
	protected String getSaveAsFileExtension() {
		return "*.hql";	//$NON-NLS-1$
	}
	
	/** 
	 * @return the query without single line comments
	 */
	@Override
	public String getQueryString() {
		String queryString = getEditorText();
		StringBuilder clearHQL = new StringBuilder();
    	int state = 0;
    	
		for (int j = 0; j < queryString.length(); j++) {
			if ((queryString.charAt(j) == '\n')
					|| ((queryString.charAt(j) == '\r')
							&& (j + 1 < queryString.length())
							&& (queryString.charAt(j + 1) == '\r'))) {
				state = 0;
			}

			switch (state) {
			case -1:// skip all till the end of the line
				break;
			case 0:// initial state
				switch (queryString.charAt(j)) {
				case '-':
					if (queryString.length() > j + 1 && queryString.charAt(j + 1) == '-') {
						state = -1;
					}
					break;
				case '\'':
					state = 1;
					break;
				}
				break;
			case 1:// quoted string
				/*
				 * Escape character for the quote is doubled quote:
				 * Example: 'This is escaped quote ('') inside 1 string'.
				 * Our parser switches to state 0 and back to state 1, hence works correct
				 * without additional efforts.
				 */
				if (queryString.charAt(j) == '\'') {/*there is no way to escape it in HQL string*/
					state = 0;
				}
				break;
			}
			if (state != -1) {
				clearHQL.append(queryString.charAt(j));
			}
		}
		return clearHQL.toString();
	}
}
