package de.ugoe.cs.eventbench.swing;

import java.awt.BorderLayout;
import java.util.Enumeration;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import de.ugoe.cs.eventbench.assertions.AssertEvent;
import de.ugoe.cs.eventbench.assertions.FileEqualsReplay;
import de.ugoe.cs.eventbench.assertions.TextEqualsReplay;
import de.ugoe.cs.eventbench.data.Event;
import de.ugoe.cs.eventbench.data.GlobalDataContainer;
import de.ugoe.cs.util.console.Console;

import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JPanel;
import javax.swing.border.EmptyBorder;
import javax.swing.JComboBox;
import javax.swing.JLabel;
import javax.swing.JTextField;
import javax.swing.JOptionPane;
import javax.swing.JFileChooser;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.io.File;
import java.awt.event.ActionListener;
import java.awt.event.ActionEvent;
import javax.swing.JScrollPane;
import javax.swing.border.EtchedBorder;
import javax.swing.JTree;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import javax.swing.tree.TreeSelectionModel;

/**
 * <p>
 * This class provides the dialog to insert one of the available assertion
 * types.
 * </p>
 * 
 * @author Jeffrey Hall
 * @version 1.0
 */
public class DlgInsert extends JDialog {

	/**
	 * Id for object serialization.
	 */
	private static final long serialVersionUID = 1L;

	private final JPanel contentPanel = new JPanel();
	private JTextField textFieldExpectedValue;
	private JTextField textFieldActualFile;
	private JTextField textFieldExpectedFile;

	/**
	 * <p>
	 * Launch the dialog
	 * </p>
	 * 
	 * @param sequences
	 *            A list of the events where an assertion will be inserted.
	 * @param selectedIndex
	 *            The position for inserting an assertion.
	 * @param insertBefore
	 *            To decide if the user clicked 'insert before' or 'insert
	 *            after'.
	 */
	public static void showDialog(List<Event<?>> sequences, int selectedIndex,
			final boolean insertBefore) {
		try {
			DlgInsert dialog = new DlgInsert(sequences, selectedIndex,
					insertBefore);
			dialog.setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
			dialog.setVisible(true);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * <p>
	 * Create the dialog.
	 * </p>
	 * 
	 * @param sequences
	 *            A list of the events where an assertion will be inserted.
	 * @param selectedIndex
	 *            The position for inserting an assertion.
	 * @param insertBefore
	 *            To decide if the user clicked 'insert before' or 'insert
	 *            after'.
	 */
	public DlgInsert(final List<Event<?>> sequences, final int selectedIndex,
			final boolean insertBefore) {
		initialize(sequences, selectedIndex, insertBefore);
	}

	/**
	 * <p>
	 * Initialize the contents of the frame.
	 * </p>
	 * 
	 * @param sequences
	 *            A list of the events where an assertion will be inserted.
	 * @param selectedIndex
	 *            The position for inserting an assertion.
	 * @param insertBefore
	 *            To decide if the user clicked 'insert before' or 'insert
	 *            after'.
	 */
	private void initialize(final List<Event<?>> sequences,
			final int selectedIndex, final boolean insertBefore) {

		setResizable(false);

		setDefaultCloseOperation(JDialog.DISPOSE_ON_CLOSE);
		setTitle("Insert testcase");

		setModal(true);
		setBounds(100, 100, 676, 673);
		getContentPane().setLayout(new BorderLayout());
		contentPanel.setBorder(new EmptyBorder(5, 5, 5, 5));
		getContentPane().add(contentPanel, BorderLayout.CENTER);
		contentPanel.setLayout(null);
		final JComboBox comboBoxAssertionType = new JComboBox();
		final JPanel panelTextEquals = new JPanel();
		panelTextEquals.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null,
				null));
		final JPanel panelFileEquals = new JPanel();
		panelFileEquals.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null,
				null));

		// *****
		// define your assertion types here
		final int numberOfAssertionTypes = 2;
		final JPanel[] panels = new JPanel[numberOfAssertionTypes];
		String[] assertionTypes = new String[numberOfAssertionTypes];

		panels[0] = panelTextEquals;
		assertionTypes[0] = "TextEquals";
		panels[1] = panelFileEquals;
		assertionTypes[1] = "OutputFileEquals";
		// *****

		// add assertion types to comboBox
		for (int i = 0; i < numberOfAssertionTypes; i++) {
			comboBoxAssertionType.addItem(assertionTypes[i]);
		}

		comboBoxAssertionType.setSelectedIndex(0);
		comboBoxAssertionType.setBounds(90, 11, 180, 20);
		contentPanel.add(comboBoxAssertionType);

		final JPanel buttonPane = new JPanel();
		final JButton okButton = new JButton("Insert");

		// selecting of another assertion type
		comboBoxAssertionType.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if ("comboBoxChanged".equals(arg0.getActionCommand())) {

					selectAssertionType(numberOfAssertionTypes, panels,
							comboBoxAssertionType.getSelectedIndex(),
							buttonPane, okButton);
				}
			}
		});

		JLabel label = new JLabel("Testcase:");
		label.setBounds(12, 14, 86, 14);
		contentPanel.add(label);

		JLabel label_1 = new JLabel("Expected value:");
		label_1.setBounds(10, 11, 96, 14);

		JLabel label_2 = new JLabel("Target:");
		label_2.setBounds(10, 38, 86, 14);

		textFieldExpectedValue = new JTextField();
		textFieldExpectedValue.setColumns(10);
		textFieldExpectedValue.setBounds(116, 8, 524, 20);

		panelTextEquals.setLayout(null);
		panelTextEquals.setBounds(10, 40, 650, 401);
		contentPanel.add(panelTextEquals);
		panelTextEquals.add(label_1);
		panelTextEquals.add(label_2);
		panelTextEquals.add(textFieldExpectedValue);

		JScrollPane scrollPane_1 = new JScrollPane();
		scrollPane_1.setBounds(10, 58, 630, 291);
		panelTextEquals.add(scrollPane_1);

		final JTree tree = new JTree();
		DefaultTreeModel treeModel = new DefaultTreeModel(null);
		final DefaultMutableTreeNode root = new DefaultMutableTreeNode(
				"Targets");
		treeModel.setRoot(root);
		tree.setModel(treeModel);

		tree.getSelectionModel().setSelectionMode(
				TreeSelectionModel.CONTIGUOUS_TREE_SELECTION);
		buildTargetTree(root);

		scrollPane_1.setViewportView(tree);

		// expand all targets
		JButton btnExpandAll = new JButton("Expand all");
		btnExpandAll.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent arg0) {

				expandAll(tree, new TreePath(root), true);
			}
		});
		btnExpandAll.setBounds(10, 360, 112, 30);
		panelTextEquals.add(btnExpandAll);

		// collapse all targets
		JButton btnCollapseAll = new JButton("Collapse all");
		btnCollapseAll.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent arg0) {
				expandAll(tree, new TreePath(root), false);
			}
		});
		btnCollapseAll.setBounds(132, 360, 112, 30);
		panelTextEquals.add(btnCollapseAll);

		panelFileEquals.setBounds(34, 452, 607, 120);
		contentPanel.add(panelFileEquals);
		panelFileEquals.setLayout(null);
		panelFileEquals.setVisible(false);

		JLabel lblNewLabel = new JLabel("Actual file:");
		lblNewLabel.setBounds(10, 11, 89, 14);
		panelFileEquals.add(lblNewLabel);

		textFieldActualFile = new JTextField();
		textFieldActualFile.setBounds(10, 30, 587, 20);
		panelFileEquals.add(textFieldActualFile);
		textFieldActualFile.setColumns(10);

		// open search file dialog
		JButton btnSearchFile = new JButton("Search file");
		btnSearchFile.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent arg0) {
				final JFileChooser fc = new JFileChooser();
				if (fc.showOpenDialog(contentPanel) == 0) {
					textFieldExpectedFile.setText(fc.getSelectedFile()
							.getAbsolutePath());
				}
			}
		});
		btnSearchFile.setBounds(93, 61, 89, 23);
		panelFileEquals.add(btnSearchFile);

		JLabel lblNewLabel_1 = new JLabel("Expected file:");
		lblNewLabel_1.setBounds(10, 70, 89, 14);
		panelFileEquals.add(lblNewLabel_1);

		textFieldExpectedFile = new JTextField();
		textFieldExpectedFile.setColumns(10);
		textFieldExpectedFile.setBounds(10, 88, 587, 20);
		panelFileEquals.add(textFieldExpectedFile);

		{
			buttonPane.setBorder(new EtchedBorder(EtchedBorder.LOWERED, null,
					null));
			buttonPane.setBounds(12, 583, 607, 51);
			contentPanel.add(buttonPane);
			{
				// clicking 'Insert'
				okButton.setBounds(462, 11, 135, 31);
				okButton.addMouseListener(new MouseAdapter() {
					public void mouseClicked(MouseEvent arg0) {
						if (insertAssertion(sequences, selectedIndex,
								insertBefore, tree, comboBoxAssertionType
										.getSelectedItem().toString()) == false) {
							return;
						}

						dispose();
					}
				});
				buttonPane.setLayout(null);
				okButton.setActionCommand("OK");
				buttonPane.add(okButton);
				getRootPane().setDefaultButton(okButton);
			}

			{
				// clicking 'Cancel'
				JButton cancelButton = new JButton("Cancel");
				cancelButton.setBounds(10, 11, 135, 31);
				cancelButton.addMouseListener(new MouseAdapter() {

					public void mouseClicked(MouseEvent arg0) {
						dispose();
					}
				});
				cancelButton.setActionCommand("Cancel");
				buttonPane.add(cancelButton);
			}
		}
		
		selectAssertionType(numberOfAssertionTypes, panels,
				comboBoxAssertionType.getSelectedIndex(),
				buttonPane, okButton);
	}

	/**
	 * Build up the tree containing all available targets.
	 * 
	 * @param root
	 *            The tree root.
	 */
	@SuppressWarnings("unchecked")
	private void buildTargetTree(final DefaultMutableTreeNode root) {
		// get targets from GlobalDataContainer
		SortedSet<String> listTargets = new TreeSet<String>();
		try {
			listTargets = (SortedSet<String>) GlobalDataContainer.getInstance()
					.getData("ListTargets");
		} catch (ClassCastException e) {
			Console.println("Not able to cast data in GlobalDataContainer to SortedSet of Strings");
		}

		// build the tree
		for (String target : listTargets) {
			DefaultMutableTreeNode currentParent = root;

			String splitted[] = target.split("/>");

			for (String targetPart : splitted) {
				DefaultMutableTreeNode node = compareTargetWithNode(
						currentParent, targetPart + "/>");

				if (node != null) {
					currentParent = node;
				} else {
					node = new DefaultMutableTreeNode(targetPart + "/>");
					currentParent.add(node);
					currentParent = node;
				}
			}
		}
	}

	/**
	 * Check if there is a child equal to 'target'.
	 * 
	 * @param node
	 *            The parent node which is browsed for a node equal to 'target'.
	 * @param target
	 *            'node' is browsed for this String.
	 * @return If there was no fitting node found, the return value is null.
	 *         Otherwise it is the node.
	 */
	DefaultMutableTreeNode compareTargetWithNode(DefaultMutableTreeNode node,
			String target) {

		if (node.isLeaf()) {
			if (target.contains(node.toString()))
				return node;
			else
				return null;
		} else {
			for (@SuppressWarnings("unchecked")
			Enumeration<DefaultMutableTreeNode> e = node.children(); e
					.hasMoreElements();) {
				DefaultMutableTreeNode nodeReturn = compareTargetWithNode(
						e.nextElement(), target);
				if (nodeReturn == null) {
					if (target.contains(node.toString())) {
						return node;
					}
				} else {
					return nodeReturn;
				}
			}
		}

		return null;
	}

	/**
	 * Expand or collapse the target tree.
	 * 
	 * @param tree
	 *            The tree itself.
	 * @param parent
	 *            The parent node which has to be expanded/collapsed.
	 * @param expand
	 *            To choose wether it is expanded or collapsed.
	 */
	private void expandAll(JTree tree, TreePath parent, boolean expand) {
		// Traverse children
		TreeNode node = (TreeNode) parent.getLastPathComponent();
		if (node.getChildCount() >= 0) {
			for (@SuppressWarnings("unchecked")
			Enumeration<DefaultMutableTreeNode> e = node.children(); e
					.hasMoreElements();) {
				TreeNode n = (TreeNode) e.nextElement();
				TreePath path = parent.pathByAddingChild(n);
				expandAll(tree, path, expand);
			}
		}

		// Expansion or collapse must be done bottom-up
		if (expand) {
			tree.expandPath(parent);
		} else {
			tree.collapsePath(parent);
		}
	}

	/**
	 * Select another assertion type in the comboBox.
	 * 
	 * @param numberOfAssertionTypes
	 *            Number of available assertion types.
	 * @param panels
	 *            The corresponding panels of the types.
	 * @param selectedIndex
	 *            The index of the selected type.
	 * @param buttonPane
	 *            The buttonPane of the dialog.
	 * @param okButton
	 *            The okButton of the buttonPane.
	 */
	private void selectAssertionType(final int numberOfAssertionTypes,
			final JPanel[] panels, int selectedIndex, final JPanel buttonPane,
			final JButton okButton) {
		for (int i = 0; i < numberOfAssertionTypes; i++) {
			panels[i].setVisible(false);
		}

		JPanel activePanel = panels[selectedIndex];
		activePanel.setVisible(true);
		activePanel.setLocation(10, 40);

		buttonPane.setLocation(activePanel.getX(), activePanel.getY()
				+ activePanel.getHeight() + 15);
		buttonPane.setSize(activePanel.getWidth(), buttonPane.getHeight());
		setSize(activePanel.getX() + activePanel.getSize().width + 15,
				buttonPane.getY() + buttonPane.getSize().height + 35);
		okButton.setLocation(buttonPane.getWidth() - okButton.getWidth() - 10,
				okButton.getY());

	}

	/**
	 * To check if all the parameters needed where entered correctly.
	 * 
	 * @param sequences
	 *            A list of the events where an assertion will be inserted.
	 * @param selectedIndex
	 *            The position for inserting an assertion.
	 * @param insertBefore
	 *            To decide if the user clicked 'insert before' or 'insert
	 *            after'.
	 * @param tree
	 *            The target tree.
	 * @param selectedItem
	 *            To identify the selected assertion type.
	 * @return If the assertion was inserted, the return value is true.
	 *         Otherwise it is false.
	 */
	private boolean insertAssertion(final List<Event<?>> sequences,
			final int selectedIndex, final boolean insertBefore,
			final JTree tree, final String selectedItem) {

		// FileEquals
		if (selectedItem == "OutputFileEquals") {
			if (textFieldActualFile.getText().length() == 0) {
				JOptionPane.showMessageDialog(null,
						"Please declare an actual file.",
						"No actual file declared", JOptionPane.OK_OPTION);

				return false;
			} else if (!new File(textFieldExpectedFile.getText()).exists()) {
				if (textFieldExpectedFile.getText().length() == 0) {
					JOptionPane.showMessageDialog(null,
							"Please choose an expected file.",
							"No expected file chosen", JOptionPane.OK_OPTION);
				} else {
					JOptionPane.showMessageDialog(null, "The expected file \""
							+ textFieldActualFile.getText()
							+ "\" does not exist.",
							"Expected file does not exist",
							JOptionPane.OK_OPTION);
				}

				return false;
			} else {
				FileEqualsReplay file = new FileEqualsReplay();
				file.setActualFile(textFieldActualFile.getText());
				file.setExpectedFile(textFieldExpectedFile.getText());

				AssertEvent<FileEqualsReplay> e = new AssertEvent<FileEqualsReplay>(
						"FileEquals");
				e.addReplayEvent(file);
				e.setTarget(" ");
				if (insertBefore)
					sequences.add(selectedIndex, e);
				else
					sequences.add(selectedIndex + 1, e);
			}
		}
		// TextEquals
		else if (selectedItem == "TextEquals") {
			if (textFieldExpectedValue.getText().length() == 0) {
				JOptionPane.showMessageDialog(null,
						"\"Expected value\" is missing.", "Expected value",
						JOptionPane.OK_OPTION);
				return false;
			} else if (tree.getSelectionCount() == 0
					|| tree.getSelectionPath().toString()
							.compareTo("[Targets]") == 0) {
				JOptionPane.showMessageDialog(null, "Please select a target.",
						"No target selected", JOptionPane.OK_OPTION);
				return false;
			} else {

				// get target ***
				String selectionPath = tree.getSelectionPath().toString();

				// remove leading and ending brackets
				selectionPath = selectionPath.substring(1);
				selectionPath = selectionPath.substring(0,
						selectionPath.length() - 1);
				// remove leading string "targets"
				selectionPath = selectionPath.substring(7);

				String splitted[] = selectionPath.toString().split(", ");

				// get all parents
				String target = "";
				for (int i = 0; i < splitted.length; i++) {
					target += splitted[i];
				}
				// ***

				TextEqualsReplay text = new TextEqualsReplay();
				text.setExpectedValue(textFieldExpectedValue.getText());
				text.setTarget(target);

				AssertEvent<TextEqualsReplay> e = new AssertEvent<TextEqualsReplay>(
						"TextEquals");
				e.addReplayEvent(text);
				e.setTarget(" ");
				if (insertBefore)
					sequences.add(selectedIndex, e);
				else
					sequences.add(selectedIndex + 1, e);
			}
		}

		return true;
	}
}