/*******************************************************************************
 * Copyright (c) 2015 MINRES Technologies GmbH 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:
 *     MINRES Technologies GmbH - initial API and implementation
 *******************************************************************************/
package com.minres.scviewer.ui;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.action.StatusLineContributionItem;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorSite;
import org.eclipse.ui.IFileEditorInput;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.ide.FileStoreEditorInput;
import org.eclipse.ui.part.EditorPart;
import org.eclipse.ui.progress.IProgressService;
import org.eclipse.ui.views.contentoutline.IContentOutlinePage;
import org.eclipse.ui.views.properties.IPropertySheetPage;
import org.eclipse.ui.views.properties.tabbed.ITabbedPropertySheetPageContributor;
import org.eclipse.ui.views.properties.tabbed.TabbedPropertySheetPage;

import com.minres.scviewer.database.IWaveform;
import com.minres.scviewer.database.IWaveformDb;
import com.minres.scviewer.database.IWaveformDbFactory;
import com.minres.scviewer.database.IWaveformEvent;
import com.minres.scviewer.database.swt.WaveformViewerFactory;
import com.minres.scviewer.database.ui.GotoDirection;
import com.minres.scviewer.database.ui.IWaveformViewer;
import com.minres.scviewer.database.ui.TrackEntry;
import com.minres.scviewer.ui.views.TxOutlinePage;

public class TxEditorPart extends EditorPart implements ITabbedPropertySheetPageContributor {

	private IWaveformDbFactory waveformDbFactory;
	
	public synchronized void bind(IWaveformDbFactory factory){
		waveformDbFactory=factory;
	}

	public synchronized void unbind(IWaveformDbFactory factory){
		if(waveformDbFactory==factory)
			waveformDbFactory=null;
	}
	
	private final static String[] zoomLevel={
		"1fs", "10fs", "100fs",
		"1ps", "10ps", "100ps",
		"1ns", "10ns", "100ns",
		"1µs", "10µs", "10µs",
		"1ms", "10ms", "100ms", "1s"};
		
	public static final String ID = "com.minres.scviewer.ui.TxEditorPart"; //$NON-NLS-1$

	public static final String WAVE_ACTION_ID = "com.minres.scviewer.ui.action.AddToWave";

	private IWaveformViewer txDisplay;

	/** This is the root of the editor's model. */
	private IWaveformDb database;

	private Composite myParent;

    private StatusLineContributionItem cursorStatusLineItem;
	private StatusLineContributionItem zoomStatusLineItem;

	public TxEditorPart() {
	}

	/**
	 * Create contents of the editor part.
	 * @param parent
	 */
	@Override
	public void createPartControl(Composite parent) {
		myParent=parent;		
		database=waveformDbFactory.getDatabase();
		database.addPropertyChangeListener(new PropertyChangeListener() {
			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if("WAVEFORMS".equals(evt.getPropertyName())) {
					myParent.getDisplay().syncExec(new Runnable() {						
						@Override
						public void run() {
							txDisplay.setMaxTime(database.getMaxTime());
						}
					});
				}		
			}
		});
		WaveformViewerFactory factory = new WaveformViewerFactory();
		txDisplay = factory.createPanel(parent);
		txDisplay.setMaxTime(0);
		txDisplay.addPropertyChangeListener(IWaveformViewer.CURSOR_PROPERTY, new PropertyChangeListener() {
            @Override
            public void propertyChange(PropertyChangeEvent evt) {
                Long time = (Long) evt.getNewValue();
                cursorStatusLineItem.setText("Cursor: "+ time/1000000+"ns");
            }
        });
		getSite().setSelectionProvider(txDisplay);
		new Thread(new Runnable() {
			@Override
			public void run() {
				try {
					loadDatabases();
				} catch (InvocationTargetException | IOException | InterruptedException e) {
					handleLoadException(e);
				}
			}
		}).run();
		zoomStatusLineItem.setText("Zoom level: "+zoomLevel[txDisplay.getZoomLevel()]);
		cursorStatusLineItem.setText("Cursor: "+ txDisplay.getCursorTime()/1000000+"ns");
		MenuManager menuMgr = new MenuManager("#PopupMenu");
//		menuMgr.setRemoveAllWhenShown(true);
//		menuMgr.addMenuListener(new IMenuListener() {
//			public void menuAboutToShow(IMenuManager manager) {
//				fillContextMenu(manager);
//			}
//		});
		Menu menu = menuMgr.createContextMenu(txDisplay.getControl());
		txDisplay.getControl().setMenu(menu);
		getSite().registerContextMenu(menuMgr, txDisplay);

	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see org.eclipse.ui.part.EditorPart#setInput(org.eclipse.ui.IEditorInput)
	 */
	protected void setInput(IEditorInput input) {
		super.setInput(input);
		if(input instanceof IFileEditorInput && !(input instanceof TxEditorInput))
			super.setInput(new TxEditorInput(((IFileEditorInput)input).getFile()));
		setPartName(input.getName());
	}

	protected void loadDatabases() throws IOException, InvocationTargetException, InterruptedException {
		IWorkbench wb = PlatformUI.getWorkbench();
		IProgressService ps = wb.getProgressService();
		IEditorInput input = getEditorInput();
		File file=null;
		boolean loadSecondary=false;
		boolean dontAskForSecondary=false;
		ArrayList<File> filesToLoad=new ArrayList<File>();
		if(input instanceof TxEditorInput){
			TxEditorInput txInput = (TxEditorInput) input;
			file = txInput.getFile().getLocation().toFile();
			loadSecondary=txInput.isSecondaryLoaded()==null||txInput.isSecondaryLoaded();
			dontAskForSecondary=txInput.isSecondaryLoaded()!=null;
		} else if(input instanceof FileStoreEditorInput){
			file=new File(((FileStoreEditorInput) input).getURI().getPath());
		}
		if(file.exists()){
			filesToLoad.add(file);
			if(loadSecondary){
			String ext = getFileExtension(file.getName());
			if("vcd".equals(ext.toLowerCase())){
				if(dontAskForSecondary || askIfToLoad(new File(renameFileExtension(file.getCanonicalPath(), "txdb")))){
					filesToLoad.add(new File(renameFileExtension(file.getCanonicalPath(), "txdb")));
					if(input instanceof TxEditorInput)  ((TxEditorInput) input).setSecondaryLoaded(true);
				}else if(dontAskForSecondary ||askIfToLoad(new File(renameFileExtension(file.getCanonicalPath(), "txlog")))){
					filesToLoad.add(new File(renameFileExtension(file.getCanonicalPath(), "txlog")));
					if(input instanceof TxEditorInput)  ((TxEditorInput) input).setSecondaryLoaded(true);
				}
			} else if("txdb".equals(ext.toLowerCase()) || "txlog".equals(ext.toLowerCase())){
				if(dontAskForSecondary || askIfToLoad(new File(renameFileExtension(file.getCanonicalPath(), "vcd")))){
					filesToLoad.add(new File(renameFileExtension(file.getCanonicalPath(), "vcd")));
					if(input instanceof TxEditorInput)  ((TxEditorInput) input).setSecondaryLoaded(true);
				}
			}
			}

		}
		final File[] files=filesToLoad.toArray(new File[filesToLoad.size()]);
		ps.run(true, false, new IRunnableWithProgress() {
		//ps.busyCursorWhile(new IRunnableWithProgress() {
			public void run(IProgressMonitor pm) throws InvocationTargetException {
				pm.beginTask("Loading database "+files[0].getName(), files.length);
				try {
					database.load(files[0]);
					database.addPropertyChangeListener(txDisplay);
					pm.worked(1);
					if(pm.isCanceled()) return;
					if(files.length==2){
						database.load(files[1]);
						pm.worked(1);
					}
					myParent.getDisplay().syncExec(new Runnable() {
						@Override
						public void run() {
							updateTxDisplay();
						}
					});
				} catch (Exception e) {
					database=null;
					throw new InvocationTargetException(e);
				}
				pm.done();
			}
		});
	}

	protected boolean askIfToLoad(File txFile) {
		if(txFile.exists() &&
				MessageDialog.openQuestion(myParent.getDisplay().getActiveShell(), "Database open", 
					"Would you like to open the adjacent database "+txFile.getName()+" as well?")){
				return true;
		}
		return false;
	}

	protected void updateTxDisplay() {
		txDisplay.setMaxTime(database.getMaxTime());
		if(TxEditorPart.this.getEditorInput() instanceof TxEditorInput &&
				((TxEditorInput) TxEditorPart.this.getEditorInput()).getStreamNames().size()>0){
			LinkedList<TrackEntry> entries= new LinkedList<>();
			for(String streamName:((TxEditorInput) TxEditorPart.this.getEditorInput()).getStreamNames()){
				IWaveform<? extends IWaveformEvent> stream = database.getStreamByName(streamName);
				if(stream!=null)
					entries.add(new TrackEntry(stream));
			}
			txDisplay.getStreamList().addAll(entries);
		}
	}

	protected static String renameFileExtension(String source, String newExt) {
		String target;
		String currentExt = getFileExtension(source);
		if (currentExt.equals("")){
			target=source+"."+newExt;
		} else {
			target=source.replaceFirst(Pattern.quote("."+currentExt)+"$", Matcher.quoteReplacement("."+newExt));
		}
		return target;
	}

	protected static String getFileExtension(String f) {
		String ext = "";
		int i = f.lastIndexOf('.');
		if (i > 0 &&  i < f.length() - 1) {
			ext = f.substring(i + 1);
		}
		return ext;
	}

	private void handleLoadException(Exception e) {
		e.printStackTrace();
		MessageDialog.openError(PlatformUI.getWorkbench().getDisplay().getActiveShell(),
				"Error loading database", e.getMessage());
		database = null;
	}

	@Override
	public void setFocus() {
		myParent.setFocus();
	}

	@Override
	public void doSave(IProgressMonitor monitor) {
	}

	@Override
	public void doSaveAs() {
	}

	@SuppressWarnings({ "rawtypes", "unchecked" })
	@Override
	public Object getAdapter(Class type) {
		if (type == IContentOutlinePage.class) // outline page
			return new TxOutlinePage(this);
		else if (type == IPropertySheetPage.class) // use tabbed property sheet instead of standard one
			return new TabbedPropertySheetPage(this);
		return super.getAdapter(type);
	}

	public IWaveformDb getModel() {
		return database;
	}

	@Override
	public void init(IEditorSite site, IEditorInput input)
			throws PartInitException {
		// Initialize the editor part
		setSite(site);
		setInput(input);
		zoomStatusLineItem = new StatusLineContributionItem("TxEditorZoomContributionItem");
        cursorStatusLineItem = new StatusLineContributionItem("TxEditorCursorContributionItem");
		IActionBars actionBars = getEditorSite().getActionBars();
		IStatusLineManager manager = actionBars.getStatusLineManager();
		manager.add(zoomStatusLineItem);
        manager.add(cursorStatusLineItem);
		actionBars.updateActionBars();
	}

	@Override
	public boolean isDirty() {
		return false;
	}

	@Override
	public boolean isSaveAsAllowed() {
		return false;
	}

	public IWaveformDb getDatabase() {
		return database;
	}

	public void addStreamToList(IWaveform<? extends IWaveformEvent> obj){
		if(getEditorInput() instanceof TxEditorInput && !((TxEditorInput) getEditorInput()).getStreamNames().contains(obj.getFullName())){
			((TxEditorInput) getEditorInput()).getStreamNames().add(obj.getFullName());
			txDisplay.getStreamList().add(new TrackEntry(obj));
		} else
			txDisplay.getStreamList().add(new TrackEntry(obj));

	}

	public void addStreamsToList(IWaveform<? extends IWaveformEvent>[] iWaveforms){
		for(IWaveform<? extends IWaveformEvent> stream:iWaveforms)
			addStreamToList(stream);
	}

	public void removeStreamFromList(IWaveform<? extends IWaveformEvent> obj){
		if(getEditorInput() instanceof TxEditorInput && ((TxEditorInput) getEditorInput()).getStreamNames().contains(obj.getFullName())){
			((TxEditorInput) getEditorInput()).getStreamNames().remove(obj.getFullName());
			txDisplay.getStreamList().remove(obj);
		} else
			txDisplay.getStreamList().remove(obj);
	}

	public void removeStreamsFromList(IWaveform<? extends IWaveformEvent>[] iWaveforms){
		for(IWaveform<? extends IWaveformEvent> stream:iWaveforms)
			removeStreamFromList(stream);
	}

	public List<TrackEntry> getStreamList(){
		return txDisplay.getStreamList();
	}

	public void setSelection(final ISelection selection){
		myParent.getDisplay().asyncExec(new Runnable() {
			@Override
			public void run() {
				txDisplay.setSelection(selection);
			}
		});
	}

	public ISelection getSelection(){
		return txDisplay.getSelection();
	}
	
	@Override
	public String getContributorId() {
		return getSite().getId();
	}

	public void moveSelection(GotoDirection next) {
		txDisplay.moveSelection( next);		
	}

	public void setZoomLevel(Integer level) {
		txDisplay.setZoomLevel(level);
	}

	public void setZoomFit() {
		txDisplay.setZoomLevel(6);		
	}

	public int getZoomLevel() {
		return txDisplay.getZoomLevel();
	}

	public void removeSelected() {
		// TODO TxDisplay needs to be extended
	}

}
