package net.filebot.ui.sfv;

import static net.filebot.ui.sfv.ChecksumTableModel.*;
import static net.filebot.ui.transfer.BackgroundFileTransferablePolicy.*;
import static net.filebot.util.FileUtilities.*;
import static net.filebot.util.ui.SwingUI.*;

import java.awt.datatransfer.Transferable;
import java.awt.event.ActionEvent;
import java.awt.event.KeyEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ExecutorService;

import javax.swing.AbstractAction;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComponent;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToggleButton;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.border.TitledBorder;

import com.google.common.eventbus.Subscribe;

import net.filebot.ResourceManager;
import net.filebot.hash.HashType;
import net.filebot.ui.SelectDialog;
import net.filebot.ui.transfer.DefaultTransferHandler;
import net.filebot.ui.transfer.LoadAction;
import net.filebot.ui.transfer.SaveAction;
import net.filebot.ui.transfer.TransferablePolicy;
import net.filebot.ui.transfer.TransferablePolicy.TransferAction;
import net.filebot.util.FileUtilities;
import net.miginfocom.swing.MigLayout;

public class SfvPanel extends JComponent {

	private final ChecksumComputationService computationService = new ChecksumComputationService();

	private final ChecksumTable table = new ChecksumTable();

	private final ChecksumTableTransferablePolicy transferablePolicy = new ChecksumTableTransferablePolicy(table, computationService);
	private final ChecksumTableExportHandler exportHandler = new ChecksumTableExportHandler(table.getModel());

	public SfvPanel() {
		table.setTransferHandler(new DefaultTransferHandler(transferablePolicy, exportHandler));

		JPanel contentPane = new JPanel(new MigLayout("insets 0, nogrid, novisualpadding, fill", "", "[fill]10px[center, pref!]4px"));
		contentPane.setBorder(new TitledBorder("SFV"));

		setLayout(new MigLayout("insets dialog, fill"));
		add(contentPane, "grow");

		contentPane.add(new JScrollPane(table), "grow, wrap");

		contentPane.add(new JButton(loadAction), "gap left 15px");
		contentPane.add(new JButton(saveAction));
		contentPane.add(new JButton(clearAction), "gap right 40px");

		// hash function toggle button group
		ButtonGroup group = new ButtonGroup();

		for (HashType hash : HashType.values()) {
			JToggleButton button = new ChecksumButton(new ChangeHashTypeAction(hash));

			group.add(button);
			contentPane.add(button);
		}

		contentPane.add(new TotalProgressPanel(computationService), "gap left 35px:push, gap right 7px, hidemode 1");

		// cancel and restart computations whenever the hash function has been changed
		table.getModel().addPropertyChangeListener(new PropertyChangeListener() {

			@Override
			public void propertyChange(PropertyChangeEvent evt) {
				if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) {
					restartComputation((HashType) evt.getNewValue());
				}
			}
		});

		// Shortcut DELETE
		installAction(this, KeyStroke.getKeyStroke(KeyEvent.VK_DELETE, 0), removeAction);
	}

	public TransferablePolicy getTransferablePolicy() {
		return transferablePolicy;
	}

	protected void restartComputation(HashType hash) {
		// cancel all running computations
		computationService.reset();

		ChecksumTableModel model = table.getModel();

		// calculate new hashes, one executor for each checksum column
		Map<File, ExecutorService> executors = new HashMap<File, ExecutorService>(4);

		for (ChecksumRow row : model.rows()) {
			for (ChecksumCell cell : row.values()) {
				if (cell.getChecksum(hash) == null && cell.getRoot().isDirectory()) {
					cell.putTask(new ChecksumComputationTask(new File(cell.getRoot(), cell.getName()), hash));

					ExecutorService executor = executors.get(cell.getRoot());

					if (executor == null) {
						executor = computationService.newExecutor();
						executors.put(cell.getRoot(), executor);
					}

					// start computation
					executor.execute(cell.getTask());
				}
			}
		}

		// start shutdown sequence for all created executors
		for (ExecutorService executor : executors.values()) {
			executor.shutdown();
		}
	}

	@Subscribe
	public void handle(Transferable transferable) throws Exception {
		TransferablePolicy handler = getTransferablePolicy();

		if (handler != null && handler.accept(transferable)) {
			handler.handleTransferable(transferable, TransferAction.PUT);
		}
	}

	private final SaveAction saveAction = new ChecksumTableSaveAction();

	private final LoadAction loadAction = new LoadAction(this::getTransferablePolicy);

	private final AbstractAction clearAction = new AbstractAction("Clear", ResourceManager.getIcon("action.clear")) {

		@Override
		public void actionPerformed(ActionEvent e) {
			transferablePolicy.reset();
			computationService.reset();

			table.getModel().clear();
		}
	};

	private final AbstractAction removeAction = new AbstractAction("Remove") {

		@Override
		public void actionPerformed(ActionEvent e) {
			if (table.getSelectedRowCount() < 1)
				return;

			int[] rows = table.getSelectedRows();

			if (rows.length <= 0) {
				// no rows selected
				return;
			}

			// first selected row
			int selectedRow = table.getSelectedRow();

			// convert view index to model index
			for (int i = 0; i < rows.length; i++) {
				rows[i] = table.getRowSorter().convertRowIndexToModel(rows[i]);
			}

			// remove selected rows
			table.getModel().remove(rows);

			// update computation service task count
			computationService.purge();

			// auto select next row
			selectedRow = Math.min(selectedRow, table.getRowCount() - 1);

			table.getSelectionModel().setSelectionInterval(selectedRow, selectedRow);
		}
	};

	protected class ChangeHashTypeAction extends AbstractAction implements PropertyChangeListener {

		private ChangeHashTypeAction(HashType hash) {
			super(hash.toString());
			putValue(HASH_TYPE_PROPERTY, hash);

			// initialize selected state
			propertyChange(new PropertyChangeEvent(this, HASH_TYPE_PROPERTY, null, table.getModel().getHashType()));

			transferablePolicy.addPropertyChangeListener(this);
			table.getModel().addPropertyChangeListener(this);
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			table.getModel().setHashType((HashType) getValue(HASH_TYPE_PROPERTY));
		}

		@Override
		public void propertyChange(PropertyChangeEvent evt) {
			if (LOADING_PROPERTY.equals(evt.getPropertyName())) {
				// update enabled state
				setEnabled(!(Boolean) evt.getNewValue());
			} else if (HASH_TYPE_PROPERTY.equals(evt.getPropertyName())) {
				// update selected state
				putValue(SELECTED_KEY, evt.getNewValue() == getValue(HASH_TYPE_PROPERTY));
			}
		}

	}

	protected class ChecksumTableSaveAction extends SaveAction {

		private File selectedColumn = null;

		public ChecksumTableSaveAction() {
			super(exportHandler);
		}

		@Override
		public ChecksumTableExportHandler getExportHandler() {
			return (ChecksumTableExportHandler) super.getExportHandler();
		}

		@Override
		protected boolean canExport() {
			return selectedColumn != null && super.canExport();
		}

		@Override
		protected void export(File file) throws IOException {
			getExportHandler().export(file, selectedColumn);
		}

		@Override
		protected File getDefaultFile() {
			// use the column root as default folder in the file dialog
			return new File(selectedColumn, validateFileName(getExportHandler().getDefaultFileName(selectedColumn)));
		}

		@Override
		public void actionPerformed(ActionEvent e) {
			List<File> options = new ArrayList<File>();

			// filter out verification file columns
			for (File file : table.getModel().getChecksumColumns()) {
				if (file.isDirectory())
					options.add(file);
			}

			// can't export anything
			if (options.isEmpty()) {
				return;
			}

			try {
				if (options.size() == 1) {
					// auto-select option if there is only one option
					this.selectedColumn = options.get(0);
				} else if (options.size() > 1) {
					// user must select one option
					SelectDialog<File> selectDialog = new SelectDialog<File>(SwingUtilities.getWindowAncestor(SfvPanel.this), options) {

						@Override
						protected String convertValueToString(Object value) {
							return FileUtilities.getFolderName((File) value);
						}
					};

					selectDialog.getMessageLabel().setText("Select checksum column:");
					selectDialog.pack();
					selectDialog.setLocationRelativeTo(SfvPanel.this);
					selectDialog.setVisible(true);

					this.selectedColumn = selectDialog.getSelectedValue();
				}

				if (this.selectedColumn != null) {
					// continue if a column was selected
					super.actionPerformed(e);
				}
			} finally {
				// reset selected column
				this.selectedColumn = null;
			}
		}
	}

}
