package gui;

import java.awt.Desktop;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.GridData;
import org.eclipse.swt.layout.GridLayout;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.FileDialog;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.MessageBox;
import org.eclipse.swt.widgets.Shell;
import org.jnativehook.GlobalScreen;
import org.jnativehook.NativeHookException;

import util.CacheList;
import util.ListChangeListener;
import util.PmTransException;
import AudioPlayback.AudioPlayerTarsosDSP;
import AudioPlayback.DummyPlayer;
import AudioPlayback.Player;

public class PmTrans {

	private Shell shell;
	private Display d;
	private Player player;
	private MenuManager menuManager;
	private EditingPane textEditor;
	private File transcriptionFile;
	private GlobalKeyListener gKL;
	private FindBar findBar;
	private BarManager barManager;
	private CacheList<File> audioFilesCache;
	private CacheList<File> textFilesCache;

	private PmTrans(GlobalKeyListener gkl) {
		// Initialize the interface
		d = new Display();
		shell = new Shell(d);
		menuManager = new MenuManager(shell, this);
		barManager = new BarManager(shell, this);

		// Global keys
		this.gKL = gkl;

		initState();
		initGui();
		startAutoSave();

		shell.open();

		while (!shell.isDisposed())
			if (!d.readAndDispatch())
				d.sleep();

		if (player != null) {
			player.close();
			player = null;
		}
		GlobalScreen.unregisterNativeHook();
		saveState();
		try {
			Config.getInstance().save();
		} catch (IOException e) {
			// ignore
		}
	}

	private void initGui() {
		shell.setLayout(new GridLayout(1, true));

		shell.setText("pmTrans");

		menuManager.createMenu();
		barManager.createToolBar();

		textEditor = new EditingPane(shell, this);
		GridData gd = new GridData();
		gd.grabExcessHorizontalSpace = true;
		gd.horizontalAlignment = SWT.FILL;
		gd.verticalAlignment = SWT.FILL;
		gd.grabExcessVerticalSpace = true;
		textEditor.setLayoutData(gd);

		createNewDummyPlayer();

		shell.addListener(SWT.Resize, new Listener() {
			public void handleEvent(Event event) {
				if (!menuManager.getMiniModeSelection()) {
					Config.getInstance().setValue(Config.SHELL_HEIGHT, shell.getSize().x);
					Config.getInstance().setValue(Config.SHELL_LENGHT, shell.getSize().y);
				}
			}
		});

		// Add the listeners for the recent files caches
		audioFilesCache.addListChangedListener(new ListChangeListener() {

			@Override
			public void listChanged() {
				menuManager.refreshRecentFilesCaches();
			}
		});
		textFilesCache.addListChangedListener(new ListChangeListener() {

			@Override
			public void listChanged() {
				menuManager.refreshRecentFilesCaches();
			}
		});

		// Set the shell size
		shell.setSize(Config.getInstance().getInt(Config.SHELL_HEIGHT),
				Config.getInstance().getInt(Config.SHELL_LENGHT));

		// Ask if the user want to save changes before closing and close the
		// player
		shell.addListener(SWT.Close, new Listener() {
			public void handleEvent(Event event) {
				event.doit = closeTranscription();
				if (event.doit && player != null) {
					player.stop();
					player.close();
				}
			}
		});
	}

	private void startAutoSave() {
		if (Config.getInstance().getBoolean(Config.AUTO_SAVE)) {
			final int frecuency = Config.getInstance().getInt(Config.AUTO_SAVE_TIME) * 60 * 1000;
			if (frecuency > 0) {
				Display.getCurrent().timerExec(frecuency, new Runnable() {
					public void run() {
						if (!textEditor.isDisposed() && transcriptionFile != null) {
							try {
								textEditor.saveTranscription(transcriptionFile);
							} catch (Exception e) {
								// ignore
							}
						}
						Display.getCurrent().timerExec(frecuency, this);
					}
				});
			}
		}
	}

	@SuppressWarnings("unchecked")
	private void initState() {
		try {
			ObjectInputStream in = new ObjectInputStream(new FileInputStream(Config.STATE_PATH));
			CacheList<File> cl = (CacheList<File>) in.readObject();
			audioFilesCache = cl != null ? cl
					: new CacheList<File>(Config.getInstance().getInt(Config.AUDIO_FILE_CACHE_LENGHT));
			cl = (CacheList<File>) in.readObject();
			textFilesCache = cl != null ? cl
					: new CacheList<File>(Config.getInstance().getInt(Config.TEXT_FILE_CACHE_LENGHT));
			in.close();
		} catch (FileNotFoundException e) {
			audioFilesCache = new CacheList<File>(Config.getInstance().getInt(Config.AUDIO_FILE_CACHE_LENGHT));
			textFilesCache = new CacheList<File>(Config.getInstance().getInt(Config.TEXT_FILE_CACHE_LENGHT));
		} catch (Exception e) {
			MessageBox diag = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
			diag.setMessage("Unable to initialize previous state. Strating from scratch");
			diag.open();
			audioFilesCache = new CacheList<File>(Config.getInstance().getInt(Config.AUDIO_FILE_CACHE_LENGHT));
			textFilesCache = new CacheList<File>(Config.getInstance().getInt(Config.TEXT_FILE_CACHE_LENGHT));
		}
	}

	private void saveState() {
		try {
			ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(Config.STATE_PATH));
			out.writeObject(audioFilesCache);
			out.writeObject(textFilesCache);
			out.close();
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	protected void openNewAudio() {
		FileDialog fd = new FileDialog(shell, SWT.OPEN);
		fd.setText("Select the audio file");
		String[] filterExt = { "*.wav;*.WAV;*.mp3;*.MP3", "*.*" };
		String[] filterNames = { "WAV and MP3 files", "All files" };
		fd.setFilterExtensions(filterExt);
		fd.setFilterNames(filterNames);
		String lastPath = Config.getInstance().getString(Config.LAST_OPEN_AUDIO_PATH);
		if (lastPath != null && lastPath.isEmpty())
			fd.setFileName(lastPath);
		String selected = fd.open();
		if (selected != null) {
			closePlayer();
			openAudioFile(new File(selected));
			Config.getInstance().putValue(Config.LAST_OPEN_AUDIO_PATH, selected);
			try {
				Config.getInstance().save();
			} catch (IOException e) {
				// The user do not NEED to know about this...
			}
		}
	}

	protected void openAudioFile(File file) {
		// Add new file to cache and refresh the list
		closePlayer();

		// Create the player
		try {
			if (file != null && file.exists()) {

				player = new AudioPlayerTarsosDSP(file);
				GridData gd = new GridData();
				gd.grabExcessHorizontalSpace = true;
				gd.horizontalAlignment = SWT.FILL;
				gd.verticalAlignment = SWT.FILL;
				player.initGUI(shell, gd);

				audioFilesCache.add(file);
				shell.layout();
			} else {
				createNewDummyPlayer();
				audioFilesCache.remove(file);
				MessageBox diag = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
				diag.setMessage("Unable to open file " + file.getPath());
				diag.open();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	private void closePlayer() {
		if (player != null) {
			player.close();
			player = null;
		}
	}

	protected void importText() {
		if (!textEditor.isDisposed()) {
			FileDialog fd = new FileDialog(shell, SWT.OPEN);
			fd.setText("Open");
			String[] filterExt = { "*.txt;*.TXT" };
			String[] filterNames = { "TXT files" };
			fd.setFilterExtensions(filterExt);
			fd.setFilterNames(filterNames);
			String lastPath = Config.getInstance().getString(Config.LAST_OPEN_TEXT_PATH);
			if (lastPath != null && !lastPath.isEmpty())
				fd.setFileName(lastPath);
			String selected = fd.open();
			if (selected != null) {
				importTextFile(new File(selected));
				Config.getInstance().putValue(Config.LAST_OPEN_TEXT_PATH, selected);
				try {
					Config.getInstance().save();
				} catch (IOException e) {
					// The user do not NEED to know about this...
				}
			}
		}
	}

	public void openTranscription() {
		if (!textEditor.isDisposed()) {
			FileDialog fd = new FileDialog(shell, SWT.OPEN);
			fd.setText("Select the transcription file");
			String[] filterExt = { "*.xpmt;*.XPMT" };
			String[] filterNames = { "pmTrans transcription files" };
			fd.setFilterExtensions(filterExt);
			fd.setFilterNames(filterNames);
			String lastPath = Config.getInstance().getString(Config.LAST_OPEN_TEXT_PATH);
			if (lastPath != null && !lastPath.isEmpty())
				fd.setFileName(lastPath);
			String selected = fd.open();
			if (selected != null) {
				openTranscriptionFile(new File(selected));
				Config.getInstance().putValue(Config.LAST_OPEN_TEXT_PATH, selected);
				try {
					Config.getInstance().save();
				} catch (IOException e) {
					// The user do not NEED to know about this...
				}
			}
		}
	}

	protected void openTranscriptionFile(File f) {
		if (!textEditor.isDisposed()) {
			try {
				closeTranscription();
				transcriptionFile = f;
				textEditor.loadTranscription(transcriptionFile);
				textFilesCache.add(transcriptionFile);
				shell.setText(f.getName());
			} catch (Exception e) {
				textEditor.clear();
				textFilesCache.remove(transcriptionFile);
				MessageBox diag = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
				diag.setMessage("Unable to open file " + f.getPath());
				diag.open();
				transcriptionFile = null;
			}
		}
	}

	/**
	 * @return true if the transcription was closed, false if the operation was
	 *         cancelled
	 */
	protected boolean closeTranscription() {
		if (!textEditor.isDisposed()) {
			if (textEditor.isChanged()) {
				MessageBox diag = new MessageBox(shell,
						SWT.APPLICATION_MODAL | SWT.ICON_QUESTION | SWT.YES | SWT.NO | SWT.CANCEL);
				diag.setMessage("You have unsaved changes, would you like to save them?");
				int opt = diag.open();
				if (opt == SWT.YES)
					saveTranscription();
				if (opt == SWT.CANCEL)
					return false;
			}
			textEditor.clear();
			transcriptionFile = null;
		}
		return true;
	}

	public static void main(String[] args) {
		// Global keys
		try {
			GlobalScreen.registerNativeHook();
		} catch (NativeHookException ex) {
			System.err.println(ex);
		}
		GlobalKeyListener GKL = new GlobalKeyListener();
		GlobalScreen.getInstance().addNativeKeyListener(GKL);

		new PmTrans(GKL);
	}

	protected void pauseAndRewind() {
		d.syncExec(new Runnable() {
			@Override
			public void run() {
				if (player != null)
					player.pauseAndRewind();
			}
		});
	}

	public void rewind(final int seconds) {
		d.syncExec(new Runnable() {
			@Override
			public void run() {
				if (player != null)
					player.rewind(seconds);
			}
		});
	}

	public void modifyAudioPlaybackRate(final int factor) {
		d.syncExec(new Runnable() {
			@Override
			public void run() {
				player.setRate(player.getRate() + factor);
			}
		});
	}

	public void selectAudioLoops() {
		d.syncExec(new Runnable() {
			@Override
			public void run() {
				player.selectAudioLoops();
			}
		});
	}

	public void insertTimeStamp() {
		if (!textEditor.isDisposed()) {
			if (player == null)
				return;
			textEditor.insertTimestamp(player.getMediaTime());
		}
	}

	public void saveTranscription() {
		if (!textEditor.isDisposed()) {
			if (transcriptionFile != null)
				try {
					textEditor.saveTranscription(transcriptionFile);
				} catch (Exception e) {
					e.printStackTrace();
					MessageBox diag = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
					diag.setMessage("Unable to write file " + transcriptionFile.getPath());
					diag.open();
				}
			else {
				try {
					boolean done = false;
					while (!done) {
						FileDialog fd = new FileDialog(Display.getCurrent().getActiveShell(), SWT.SAVE);
						fd.setFilterNames(new String[] { "pmTrans transcription file", "All Files (*.*)" });
						fd.setFilterExtensions(new String[] { "*.xpmt", "*.*" });
						String file = fd.open();
						if (file != null) {
							transcriptionFile = new File(file);
							boolean overwrite = true;
							if (transcriptionFile.exists())
								overwrite = MessageDialog.openConfirm(shell, "Overwrite current file?",
										"Would you like to overwrite " + transcriptionFile.getName() + "?");
							if (overwrite) {
								shell.setText(transcriptionFile.getName());
								textEditor.saveTranscription(transcriptionFile);
								textFilesCache.add(transcriptionFile);
								done = true;
							}
						} else
							done = true;
					}
				} catch (Exception e) {
					e.printStackTrace();
					MessageBox diag = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
					diag.setMessage("Unable to write file " + transcriptionFile.getPath());
					diag.open();
				}
			}
		}
	}

	public void goTo(double seconds) {
		player.setMediaTime(seconds);
	}

	protected void exportTextFile() {
		boolean done = false;
		while (!done)
			if (!textEditor.isDisposed()) {
				FileDialog fd = new FileDialog(Display.getCurrent().getActiveShell(), SWT.SAVE);
				fd.setFilterNames(new String[] { "Plain text file (*.txt)", "All Files (*.*)" });
				fd.setFilterExtensions(new String[] { "*.txt", "*.*" });
				String lastPath = Config.getInstance().getString(Config.LAST_EXPORT_TRANSCRIPTION_PATH);
				if (lastPath != null && !lastPath.isEmpty())
					fd.setFileName(lastPath);
				String file = fd.open();
				try {
					if (file != null) {
						Config.getInstance().putValue(Config.LAST_EXPORT_TRANSCRIPTION_PATH, file);
						File destFile = new File(file);
						boolean overwrite = true;
						if (destFile.exists())
							overwrite = MessageDialog.openConfirm(shell, "Overwrite current file?",
									"Would you like to overwrite " + destFile.getName() + "?");
						if (overwrite) {
							textEditor.exportText(new File(file));
							done = true;
						}
					} else
						done = true;
				} catch (Exception e) {
					e.printStackTrace();
					MessageBox diag = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
					diag.setMessage("Unable to export to file " + transcriptionFile.getPath());
					diag.open();
				}
			}
	}

	protected void importTextFile(File f) {
		if (!textEditor.isDisposed()) {
			FileDialog fd = new FileDialog(shell, SWT.OPEN);
			fd.setText("Import text");
			fd.setFilterExtensions(new String[] { "*.txt;*.TXT" });
			fd.setFilterNames(new String[] { "Plain text files (*.txt)" });
			String selected = fd.open();
			if (selected != null) {
				try {
					textEditor.importText(new File(selected));
				} catch (IOException e) {
					e.printStackTrace();
					MessageBox diag = new MessageBox(shell, SWT.ICON_WARNING | SWT.OK);
					diag.setMessage("Unable to open file " + transcriptionFile.getPath());
					diag.open();
				}
			}
		}
	}

	public void enterMiniMode() {
		gKL.registerMainWindow(this);
		if (findBar != null)
			findBar.dispose();
		textEditor.dispose();
		barManager.setBarVisible(false);
		shell.layout();
		shell.pack();
	}

	public void exitMiniMode() {
		gKL.unregisterMainWindow();
		barManager.setBarVisible(true);
		textEditor = new EditingPane(shell, this);
		GridData gd = new GridData();
		gd.grabExcessHorizontalSpace = true;
		gd.horizontalAlignment = SWT.FILL;
		gd.verticalAlignment = SWT.FILL;
		gd.grabExcessVerticalSpace = true;
		textEditor.setLayoutData(gd);
		player.getVisualization().moveBelow(textEditor);

		shell.setSize(Config.getInstance().getInt(Config.SHELL_HEIGHT),
				Config.getInstance().getInt(Config.SHELL_LENGHT));
		shell.layout();
	}

	public void cut() {
		if (!textEditor.isDisposed())
			textEditor.cut();
	}

	public void copy() {
		if (!textEditor.isDisposed())
			textEditor.copy();
	}

	public void paste() {
		if (!textEditor.isDisposed())
			textEditor.paste();
	}

	public void find() {
		if (menuManager.getMiniModeSelection())
			return;

		if (findBar != null) {
			findBar.dispose();
		} else {
			findBar = new FindBar(shell, this);
			GridData gd = new GridData();
			gd.grabExcessHorizontalSpace = true;
			gd.horizontalAlignment = SWT.FILL;
			gd.verticalAlignment = SWT.FILL;
			findBar.setLayoutData(gd);
			menuManager.setFindEnabled(true);
			findBar.addListener(SWT.Dispose, new Listener() {
				public void handleEvent(Event event) {
					menuManager.setFindEnabled(false);
					findBar = null;
				}
			});
		}
		shell.layout();
	}

	public boolean quickFindNext() {
		return textEditor.quickFindNext(findBar.getSearchString());
	}

	public boolean quickFindPrevious() {
		return textEditor.quickFindPrevious(findBar.getSearchString());
	}

	public void zoomIn() {
		textEditor.zoomIn();
	}

	public void zoomOut() {
		textEditor.zoomOut();
	}

	public void setFontName(String item) {
		textEditor.setFontName(item);
	}

	public void adjustLayout() {
		shell.layout();
	}

	public void findReplace() {
		FindReplaceDialog diag = textEditor.getFindReplaceDialog();
		diag.open();
	}

	public void changeBackgroundColor() {
		textEditor.changeBackgroundColor();
	}

	public void changeFontColor() {
		textEditor.changeFontColor();
	}

	public void preferences() {
		try {
			Config.getInstance().showConfigurationDialog(shell);
		} catch (PmTransException e) {
			MessageBox diag = new MessageBox(Display.getCurrent().getActiveShell(),
					SWT.APPLICATION_MODAL | SWT.ICON_ERROR | SWT.OK);
			diag.setMessage("Unable to save preferences");
			diag.open();
		}
	}

	public void selectAll() {
		textEditor.selectAll();
	}

	public CacheList<File> getRecentTrasncriptions() {
		return textFilesCache;
	}

	public CacheList<File> getRecentAudios() {
		return audioFilesCache;
	}

	private void createNewDummyPlayer() {
		closePlayer();
		player = new DummyPlayer();
		GridData gd = new GridData();
		gd.grabExcessHorizontalSpace = true;
		gd.horizontalAlignment = SWT.FILL;
		gd.verticalAlignment = SWT.FILL;
		player.initGUI(shell, gd);
		shell.setSize(shell.getSize());
		shell.layout();
	}

	/**
	 * Opens the contribute page in a web browser
	 */
	public void contribute() {
		org.eclipse.swt.program.Program.launch(Config.CONTRIBUTE_URL);
	}
}