package com.quantum.view;

import java.sql.SQLException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.NoSuchElementException;

import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IToolBarManager;
import org.eclipse.jface.action.Separator;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.jface.preference.PreferenceConverter;
import org.eclipse.jface.util.IPropertyChangeListener;
import org.eclipse.jface.util.PropertyChangeEvent;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.jface.viewers.IStructuredContentProvider;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.ExtendedModifyEvent;
import org.eclipse.swt.custom.ExtendedModifyListener;
import org.eclipse.swt.custom.StyleRange;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.FontData;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IWorkbenchActionConstants;
import org.eclipse.ui.part.ViewPart;

import com.quantum.ImageStore;
import com.quantum.Messages;
import com.quantum.PluginPreferences;
import com.quantum.QuantumPlugin;
import com.quantum.actions.ExecuteAction;
import com.quantum.actions.ExportQueryAction;
import com.quantum.actions.ImportQueryAction;
import com.quantum.editors.ColorManager;
import com.quantum.model.Bookmark;
import com.quantum.model.BookmarkCollection;
import com.quantum.model.NotConnectedException;
import com.quantum.sql.MultiSQLServer;
import com.quantum.sql.SQLGrammar;
import com.quantum.sql.parser.SQLLexx;
import com.quantum.sql.parser.Token;
import com.quantum.ui.dialog.ExceptionDisplayDialog;
import com.quantum.ui.dialog.SQLExceptionDialog;
import com.quantum.util.versioning.VersioningHelper;

public class SQLQueryView extends ViewPart {
	private class ClearAction extends Action {
		
		public ClearAction() {
			setImageDescriptor(ImageStore.getImageDescriptor(ImageStore.CLEAR));
			setToolTipText(Messages.getString("sqlqueryview.clear"));
		}

		public void run() {
			setQuery("");
		}
	}

	private class AutoCommitPreferenceAction extends Action {
		
		public AutoCommitPreferenceAction() {
			super(Messages.getString("SQLQueryView.AutoCommit"));
			setToolTipText(Messages.getString("SQLQueryView.AutoCommit"));
			setImageDescriptor(ImageStore.getImageDescriptor(ImageStore.AUTOCOMMIT));
		}
		
		public void run() {
			setAutoCommitPreference(isChecked());
		}
	}
	
	private class RollbackAction extends Action {
		public RollbackAction() {
			setText(Messages.getString("SQLQueryView.RollBack"));
			setToolTipText(Messages.getString("SQLQueryView.RollBack"));
		}
		
		public void run() {
        	Bookmark[] bookmarks = BookmarkCollection.getInstance().getBookmarks();
        	for (int i = 0, length = bookmarks == null ? 0 : bookmarks.length; i < length; i++) {
        		try {
					if (bookmarks[i].isConnected() && !bookmarks[i].getConnection().getAutoCommit()) {
						MultiSQLServer.getInstance().rollback(bookmarks[i].getConnection());
					}
	            } catch (SQLException e) {
	            	SQLExceptionDialog.openException(getSite().getShell(), bookmarks[i], e);
	            } catch (NotConnectedException e) {
	            	ExceptionDisplayDialog.openError(getSite().getShell(), null, null, e);
	            }
			}
		}
	}
	
	
	private class CommitAction extends Action {
		public CommitAction() {
			setText(Messages.getString("SQLQueryView.Commit"));
			setToolTipText(Messages.getString("SQLQueryView.Commit"));
		}
		
		public void run() {
        	Bookmark[] bookmarks = BookmarkCollection.getInstance().getBookmarks();
        	for (int i = 0, length = bookmarks == null ? 0 : bookmarks.length; i < length; i++) {
        		try {
					if (bookmarks[i].isConnected() && !bookmarks[i].getConnection().getAutoCommit()) {
						MultiSQLServer.getInstance().commit(bookmarks[i].getConnection());
					}
	            } catch (SQLException e) {
	            	SQLExceptionDialog.openException(getSite().getShell(), bookmarks[i], e);
	            } catch (NotConnectedException e) {
	            	ExceptionDisplayDialog.openError(getSite().getShell(), null, null, e);
	            }
			}
		}
	}
	
	
	public class LabelProviderImpl implements ILabelProvider {
		public Image getImage(Object element) {
			return ImageStore.getImage(ImageStore.BOOKMARK);
		}
		public String getText(Object element) {
			if (element instanceof Bookmark) {
				return ((Bookmark) element).getName();
			} else {
				return null;
			}
		}
		public void addListener(ILabelProviderListener listener) {
		}
		public void dispose() {
		}
		public boolean isLabelProperty(Object element, String property) {
			return false;
		}
		public void removeListener(ILabelProviderListener listener) {
		}
	}
	public class ContentProviderImpl implements IStructuredContentProvider {
		public Object[] getElements(Object inputElement) {
			if (inputElement instanceof BookmarkCollection) {
				return ((BookmarkCollection) inputElement).getBookmarks();
			} else {
				return null;
			}
		}
		public void dispose() {
		}
		public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
		}
	}
	
	private ExecuteAction executeAction;
	private ImportQueryAction importQueryAction;
	private ExportQueryAction exportQueryAction;
	private StyledText widget;
	private AutoCommitPreferenceAction autoCommitPreferenceAction;
	private RollbackAction rollbackAction;
	private CommitAction commitAction;
	private boolean autoCommitPreference = true;
	private IPropertyChangeListener listener;
	private ColorManager colorManager = new ColorManager();

	private SyntaxHighlighter textUpdater = new SyntaxHighlighter(this.colorManager);
	
	
	public SQLQueryView() {
		super();
		this.listener = new IPropertyChangeListener() {
			public void propertyChange(PropertyChangeEvent event) {
				setFont();
			}
		};
		QuantumPlugin.getDefault().getPreferenceStore().addPropertyChangeListener(listener);
	}
	
	public void dispose() {
		QuantumPlugin.getDefault().getPreferenceStore().removePropertyChangeListener(this.listener);
		this.colorManager.dispose();
		super.dispose();
	}
	
	public static SQLQueryView getInstance() {
		return (SQLQueryView) QuantumPlugin.getDefault().getView("com.quantum.view.sqlqueryview");

	}

	public void createPartControl(org.eclipse.swt.widgets.Composite parent) {
		initActions();
		
	   	initializeColours(parent);
		GridLayout layout = new GridLayout(1, false);
		layout.horizontalSpacing = 0;
		layout.verticalSpacing = 0;
		layout.marginHeight = 0;
		layout.marginWidth = 0;
		parent.setLayout(layout);
		parent.setLayoutData(new GridData(GridData.FILL_BOTH));
		
		widget = new StyledText(parent, SWT.H_SCROLL | SWT.V_SCROLL);
		
		setFont();

		IActionBars bars = this.getViewSite().getActionBars();
		bars.setGlobalActionHandler(IWorkbenchActionConstants.CUT, cutAction);
		bars.setGlobalActionHandler(IWorkbenchActionConstants.COPY, copyAction);
		bars.setGlobalActionHandler(IWorkbenchActionConstants.PASTE, pasteAction);
		bars.setGlobalActionHandler(IWorkbenchActionConstants.SELECT_ALL, selectAllAction);

		widget.setEditable(true);
		widget.addExtendedModifyListener(modifyListener);

		widget.setLayoutData(new GridData(GridData.FILL_BOTH));

		VersioningHelper.registerActionToKeyBindingService(getSite(), 
				new String[] { "org.eclipse.ui.globalScope", "com.quantum.view.sql" }, 
		        this.executeAction);
	}

	private void setFont() {
		FontData font = PreferenceConverter.getFontData(
				QuantumPlugin.getDefault().getPreferenceStore(), 
				"quantum.font"); //$NON-NLS-1$
		if (font != null && this.widget != null) {
			this.widget.setFont(new Font(Display.getCurrent(), font));
		}
	}
	/**
	 * @param parent
	 */
	private void initializeColours(Composite parent) {
	    IPreferenceStore store = QuantumPlugin.getDefault().getPreferenceStore();

		parent.setBackground(this.colorManager.getColor(
				PreferenceConverter.getColor(store, PluginPreferences.BACKGROUND_COLOR)));
		this.textUpdater.initializeColours();
	}
	
	private void initActions() {

		IToolBarManager toolBar = getViewSite().getActionBars().getToolBarManager();

		executeAction = new ExecuteAction(this);
		toolBar.add(this.executeAction);
		toolBar.add(new ClearAction());
		
        IActionBars actionBars = getViewSite().getActionBars();
		this.importQueryAction = new ImportQueryAction();
		this.importQueryAction.init(this);
        actionBars.getMenuManager().add(this.importQueryAction);
        this.exportQueryAction = new ExportQueryAction();
        this.exportQueryAction.init(this);
        actionBars.getMenuManager().add(this.exportQueryAction);
        actionBars.getMenuManager().add(new Separator());
        this.autoCommitPreferenceAction = new AutoCommitPreferenceAction();
        this.autoCommitPreferenceAction.setChecked(this.autoCommitPreference);
        actionBars.getMenuManager().add(this.autoCommitPreferenceAction);

        this.rollbackAction = new RollbackAction();
        actionBars.getMenuManager().add(this.rollbackAction);

        this.commitAction = new CommitAction();
        actionBars.getMenuManager().add(this.commitAction);
}

	/**
	 * Returns the query to be executed. The query is either 1) the 
	 * text currently highlighted/selected in the editor or 2) all of 
     * the text in the editor. 
	 * @return query string to be executed
	 */
	public String getQuery() {
	    String query; 
	    
	    if (widget.getSelectionText().length() > 0)
	        query = widget.getSelectionText();
	    else    
	        query = widget.getText();
	    
		return query;
	}
	
	public void setQuery(String text) {
		widget.setText(text);
	}
	
	private class UpdateRequest {
		public UpdateRequest(String text, int start, int length) {
			this.text = text;
			this.start = start;
			this.length = length;
		}
		public String text;
		public int start;
		public int length;
	}
	
	private class SyntaxHighlighter extends Thread {
		
		private Color STRING_LITERAL;
		private Color KEYWORD;
		private Color COMMENT;
		private Color NUMERIC;
		private Color DEFAULT;
		
		private boolean running = true;
		private LinkedList requests = new LinkedList();
		private final ColorManager colorManager;
		public SyntaxHighlighter(ColorManager colorManager) {
			super();
			this.colorManager = colorManager;
			
			setPriority(Thread.MIN_PRIORITY);
			start();
		}
		public void initializeColours() {
		    IPreferenceStore store = QuantumPlugin.getDefault().getPreferenceStore();

			this.DEFAULT = this.colorManager.getColor(
					PreferenceConverter.getColor(store, PluginPreferences.TEXT_COLOR));
			this.KEYWORD = this.colorManager.getColor(
					PreferenceConverter.getColor(store, PluginPreferences.KEYWORD_COLOR)); 
			this.STRING_LITERAL = this.colorManager.getColor(
					PreferenceConverter.getColor(store, PluginPreferences.STRING_COLOR));
			this.COMMENT = this.colorManager.getColor(
					PreferenceConverter.getColor(store, PluginPreferences.COMMENT_COLOR));
			this.NUMERIC = this.colorManager.getColor(
					PreferenceConverter.getColor(store, PluginPreferences.NUMERIC_COLOR));
			
		}
		public synchronized void updateText(String text, int start, int length) {
			requests.add(new UpdateRequest(text, start, length));
			notify();
		}
		public void run() {
			while (running) {
				try {
					synchronized (this) {
						if (requests.size() <= 0) {
							wait();
						} else {
							Thread.sleep(10);
						}
					}
					UpdateRequest request = (UpdateRequest) requests.removeFirst();
					String text = request.text.toUpperCase();
					//int dirtyStart = request.start;
					//int dirtyEnd = request.start + request.length;
					StyleRange styleRange;
					List tokens = SQLLexx.parse(text);
					List styles = new ArrayList();
					int min = Integer.MAX_VALUE;
					int max = 0;
					for (int i = 0; i < tokens.size(); i++) {
						Token t = (Token) tokens.get(i);
						String value = t.getValue();
						int start = t.getStart();
						int length = t.getEnd() - t.getStart();
						styleRange = new StyleRange();
						styleRange.start = start;
						styleRange.length = value.length();
						styleRange.fontStyle = SWT.NULL;
						styleRange.foreground = DEFAULT;
						//boolean upper = start <= dirtyEnd && start >= dirtyStart;
						//boolean lower = ((start + length) >= dirtyStart && (start + length) <= dirtyEnd);
						//boolean both = (start <= dirtyStart && (start + length) >= dirtyEnd);
						//if (upper || lower || both) {
						if (true) { // Let's update the whole text, as some comment changes can alter everything
							min = Math.min(start, min);
							max = Math.max(max, start + length);
							if (t.getType() == Token.IDENTIFIER) {
								boolean keyword = false;
								for (int index = 0; index < SQLGrammar.KEYWORDS.length; index++) {
									if (value.equals(SQLGrammar.KEYWORDS[index])) {
										keyword = true;
									}
								}
								if (keyword) {
									styleRange.fontStyle = SWT.BOLD;
									styleRange.foreground = KEYWORD;
								} else {
									styleRange.foreground = DEFAULT;
								}
								styles.add(styleRange);
							} else if (t.getType() == Token.COMMENT) {
								styleRange.foreground = COMMENT;
								styles.add(styleRange);
							} else if (t.getType() == Token.LITERAL) {
								styleRange.foreground = STRING_LITERAL;
								styles.add(styleRange);
							} else if (t.getType() == Token.NUMERIC) {
								styleRange.foreground = NUMERIC;
								styles.add(styleRange);
							} else {
								styles.add(styleRange);
							}
						}
					}
					StyleRange[] ranges = 
							(StyleRange[]) styles.toArray(new StyleRange[styles.size()]);
					if (max >= 0 && ranges.length > 0) {
						setStyles(ranges, min, max - min);
					}
				} catch (NoSuchElementException e) {
					// ignore a missing request
				} catch (InterruptedException e) {
					// ignore any interruptions
				}
			}
		}
	}
	public void setStyles(final StyleRange[] styles, final int start, final int length) {
		getViewSite().getShell().getDisplay().asyncExec(new Runnable() {
			public void run() {
				try {
					for (int i = 0; i < styles.length; i++) {
						widget.setStyleRange(styles[i]);
					}
				} catch (Throwable t) {
					System.out.println("Error with styles: " + t.getClass().toString()); //$NON-NLS-1$
					// ignore any errors
				}
			}
		});
	}
	
	ExtendedModifyListener modifyListener = new ExtendedModifyListener() {
		public void modifyText(ExtendedModifyEvent event) {
			textUpdater.updateText(getQuery(), event.start, event.length);
		}
	};
		
	private Action cutAction = new Action() {
		public void run() {
			widget.cut();
		}
	};
	private Action copyAction = new Action() {
		public void run() {
			widget.copy();
		}
	};
	private Action pasteAction = new Action() {
		public void run() {
			widget.paste();
		}
	};
	private Action selectAllAction = new Action() {
		public void run() {
			widget.selectAll();
		}
	};

	public void setFocus() {
	}
	public boolean isAutoCommitPreference() {
		return this.autoCommitPreference;
	}
	public void setAutoCommitPreference(boolean autoCommitPreference) {
		this.autoCommitPreference = autoCommitPreference;
	}
}