package industries.doublehelix.furrynotemanager.gui;

import java.awt.BorderLayout;
import java.awt.Desktop;
import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.TreeMap;
import java.util.TreeSet;

import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JDialog;
import javax.swing.JEditorPane;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JSeparator;
import javax.swing.JSplitPane;
import javax.swing.JTable;
import javax.swing.JTextField;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.SwingConstants;
import javax.swing.SwingUtilities;
import javax.swing.event.HyperlinkEvent;
import javax.swing.event.HyperlinkListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import industries.doublehelix.conditionalfilter.filtertypes.container.ContainerFilter.ContainerType;
import industries.doublehelix.conditionalfilter.filtertypes.container.MasterFilter;
import industries.doublehelix.conditionalfilter.filtertypes.flat.FlatFilter;
import industries.doublehelix.conditionalfilter.filtertypes.flat.StringFilter;
import industries.doublehelix.conditionalfilter.filtertypes.flat.StringFilter.StringFilterMode;
import industries.doublehelix.furrynotemanager.Filters;
import industries.doublehelix.furrynotemanager.Folder;
import industries.doublehelix.furrynotemanager.Note;
import industries.doublehelix.furrynotemanager.User;
import industries.doublehelix.furrynotemanager.siteImporters.SiteImporter;
import industries.doublehelix.furrynotemanager.siteImporters.SiteImporter.ActionType;

public class MainFrame extends JFrame implements ActionListener, ListSelectionListener {
	private static final long serialVersionUID = -8159189726481395337L;

	JButton manageUsers = new JButton("Manage Users");
	JButton update = new JButton("Update");
	JButton sync = new JButton("Synchronize");
	
	JLabel lblQuicksearch = new JLabel("Quicksearch:");
	JTextField txtQuickSearch = new JTextField();
	
	JComboBox<String> filterDropDown = new JComboBox<>();
	JButton addFilter = new JButton("Add");
	JButton editFilter = new JButton("Edit");
	JButton removeFilter = new JButton("Remove");
	
	JScrollPane userListScroll = new JScrollPane();
	JScrollPane notesScroll = new JScrollPane();
	JScrollPane noteContentScroll = new JScrollPane();
	
	private User currentUser = null;
	private Folder currentFolder = null;
	private MasterFilter currentFilter = null;
	private JTable notesTable = null;
	
	HashMap<JList<Folder>, User> listToUserMap = new HashMap<>();
	
	public MainFrame() {
		this.setTitle("Furry Note Manager by Double Helix Industries - 1.0");
		this.setSize(900, 768);
		this.setLocationRelativeTo(null);
		this.setDefaultCloseOperation(JDialog.EXIT_ON_CLOSE);
		
		// add toolbar
		JToolBar toolbar = new JToolBar("Main toolbar");
		toolbar.setFloatable(false);
		toolbar.setRollover(true);
		manageUsers.addActionListener(this);
		update.addActionListener(this);
		sync.addActionListener(this);
		toolbar.add(manageUsers);
		toolbar.add(update);
		toolbar.add(sync);
		
		toolbar.add(Box.createHorizontalStrut(10));
		toolbar.add(lblQuicksearch);
		toolbar.add(Box.createHorizontalStrut(5));
		txtQuickSearch.setToolTipText("Enter to search");
		txtQuickSearch.addFocusListener(new FocusListener() {
            @Override public void focusLost(final FocusEvent pE) {}
            @Override public void focusGained(final FocusEvent pE) {
            	txtQuickSearch.selectAll();
            }
        });
		txtQuickSearch.addActionListener(this);
		toolbar.add(txtQuickSearch);
		toolbar.add(Box.createHorizontalStrut(10));
		
		rebuildFilterList(null);
		addFilter.addActionListener(this);
		editFilter.setEnabled(false);
		editFilter.addActionListener(this);
		removeFilter.setEnabled(false);
		removeFilter.addActionListener(this);
		toolbar.add(filterDropDown);
		toolbar.add(addFilter);
		toolbar.add(editFilter);
		toolbar.add(removeFilter);
		
		add(toolbar, BorderLayout.PAGE_START);
		
		// content
		rebuildUserList();
		
		JSplitPane noteMainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, notesScroll, noteContentScroll);
		noteMainPanel.setOneTouchExpandable(true);
		noteMainPanel.setDividerLocation(400);

		JSplitPane mainPanel = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT, userListScroll, noteMainPanel);
		mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		mainPanel.setOneTouchExpandable(true);
		mainPanel.setDividerLocation(150);
		
		add(mainPanel, BorderLayout.CENTER);
		
		this.setVisible(true);

		// filled in rebuildUserList, every user has at least one folder and therefore a list
		if (listToUserMap.isEmpty()) {
			manageUsers();
		}
	}
	
	private void rebuildFilterList(String selectFilter) {
		Map<String, MasterFilter> filters = new TreeMap<>();
		try {
			filters = Filters.getSavedFilters();
		} catch (SQLException e) {
			// filters could not be loaded, display empty list
			e.printStackTrace();
		}
		
		filterDropDown.removeActionListener(this);
		filterDropDown.removeAllItems();
		filterDropDown.addItem(Filters.NO_FILTER_PLACEHOLDER);
		for (String item : filters.keySet()) {
			filterDropDown.addItem(item);
		}
		filterDropDown.setMaximumSize(filterDropDown.getPreferredSize()); // width of content
		filterDropDown.addActionListener(this);
		
		if (selectFilter != null) {
			filterDropDown.setSelectedItem(selectFilter);
		}
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == manageUsers) {
			manageUsers();
		} else if (e.getSource() == update || e.getSource() == sync) {
			ActionType at = e.getSource() == update ? ActionType.UPDATE : ActionType.SYNCHRONIZEWITHDELETE;
			
			if (at == ActionType.SYNCHRONIZEWITHDELETE) {
				int option = JOptionPane.showConfirmDialog(this, "This will take quite some time. Are you sure?", "Well are you, punk?", JOptionPane.YES_NO_OPTION);
				if (option != JOptionPane.YES_OPTION) {
					return;
				}
			}
			
			TreeSet<User> allUsers;
			try {
				allUsers = User.getAllUsers();
			} catch (SQLException e1) {
				e1.printStackTrace();
				JOptionPane.showMessageDialog(this, "Could not get all users, " + e1.toString(), ":(", JOptionPane.ERROR_MESSAGE);
				return;
			}
			
			ImprovedProgressMonitor progressMonitor = new ImprovedProgressMonitor(this, "Synchronizing..", "Synchronizing", 0, 100);
			SiteImporter importer = new SiteImporter(at, allUsers, progressMonitor, this::rebuildUserList);
			importer.addPropertyChangeListener(new PropertyChangeListener() {
				@Override
				public void propertyChange(PropertyChangeEvent evt) {
					//System.out.println("property change, event " + evt.getPropertyName() + ", value " + evt.getNewValue());
					if ("progress".equals(evt.getPropertyName())) {
						int progress = (Integer) evt.getNewValue();
						progressMonitor.setProgress(progress);
					}
				}
			});
			importer.execute();
		} else if(e.getSource() == filterDropDown || e.getSource() == editFilter || e.getSource() == removeFilter) {
			String selectedFilterName = (String)filterDropDown.getSelectedItem();
			boolean placeHolderSelected = selectedFilterName.equals(Filters.NO_FILTER_PLACEHOLDER);
			if (placeHolderSelected) {
				if (e.getSource() == filterDropDown) {
					editFilter.setEnabled(false);
					removeFilter.setEnabled(false);
					currentFilter = null;
					if (this.notesTable != null) rebuildMap();
				} else {
					JOptionPane.showMessageDialog(this, "No filter selected", "No filter", JOptionPane.ERROR_MESSAGE);
				}
				return;
			}
			
			if (e.getSource() == removeFilter) {
				try {
					Filters.deleteFilter(selectedFilterName);
				} catch (SQLException e1) {
					e1.printStackTrace();
					JOptionPane.showMessageDialog(this, "Error while deleting: " + e1.toString(), "Error", JOptionPane.ERROR_MESSAGE);
					return;
				}
				rebuildFilterList(null);
				// explicitly select first item (no filter) to rebuild note list
				filterDropDown.setSelectedIndex(0);
			} else if (e.getSource() == filterDropDown || e.getSource() == editFilter) {
				MasterFilter selectedFilter = null;
				try {
					selectedFilter = Filters.getSavedFilter(selectedFilterName);
				} catch (SQLException e1) {
					e1.printStackTrace();
				}
				if (selectedFilter == null) {
					JOptionPane.showMessageDialog(this, "Could not load filter", "Error", JOptionPane.ERROR_MESSAGE);
					return;
				}
				
				if (e.getSource() == filterDropDown) {
					editFilter.setEnabled(true);
					removeFilter.setEnabled(true);
					currentFilter = selectedFilter;
					if (this.notesTable != null) rebuildMap();
				} else if (e.getSource() == editFilter) {
					FilterDialog filterDialog = new FilterDialog(this, selectedFilterName, selectedFilter);
					if (filterDialog.wasAborted) return;
					MasterFilter newFilter = filterDialog.getFilter();
					try {
						Filters.updateFilter(selectedFilterName, newFilter);
						rebuildFilterList(selectedFilterName);
					} catch (SQLException | IOException e1) {
						e1.printStackTrace();
						JOptionPane.showMessageDialog(this, "Could not edit filter: " + e1.toString(), "Error", JOptionPane.ERROR_MESSAGE);
					}
				}
			}
		} else if (e.getSource() == addFilter) {
			FilterDialog filterDialog = new FilterDialog(this);
			if (filterDialog.wasAborted) return;
			String newFilterName = filterDialog.getFilterName();
			MasterFilter newFilter = filterDialog.getFilter();
			
			try {
				Filters.saveFilter(newFilterName, newFilter);
				rebuildFilterList(newFilterName);
			} catch (SQLException | IOException e1) {
				e1.printStackTrace();
				JOptionPane.showMessageDialog(this, "Could not save filter: " + e1.toString(), "Error", JOptionPane.ERROR_MESSAGE);
			}
		} else if (e.getSource() == txtQuickSearch) {
			if (this.notesTable != null) rebuildMap(); // quick search is handled there
		}
	}
	
	private void manageUsers() {
		try {
			new UserManagerDialog(this, this::rebuildUserList);
		} catch (SQLException e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(this, "Could not open manage user dialog, " + e.toString(), "SQL Error", JOptionPane.ERROR_MESSAGE);
		}
	}
	
	private void rebuildUserList() {
		listToUserMap.clear();
		//userListScroll.removeAll(); NO! BAD! WILL REMOVE THE VIEWPORT
		
		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.Y_AXIS));
		
		// build lists
		try {
			TreeSet<User> allUsers = User.getAllUsers();
		
			Iterator<User> iter = allUsers.iterator();
			while (iter.hasNext()) {
				User user = iter.next();
				UserPanel up = new UserPanel(user);
				JList<Folder> folderList = up.getList();
				listToUserMap.put(folderList, user);
				folderList.addListSelectionListener(this);
				mainPanel.add(up);
				
				if (iter.hasNext()) {
					mainPanel.add(Box.createVerticalStrut(10));
					mainPanel.add(new JSeparator(SwingConstants.HORIZONTAL));
				}
			}
		} catch (SQLException e) {
			e.printStackTrace();
			JOptionPane.showMessageDialog(this, "Could not get all users, " + e.toString(), ":(", JOptionPane.ERROR_MESSAGE);
			return;
		}
		//mainPanel.setPreferredSize(new Dimension(200, 200));
		userListScroll.setViewportView(mainPanel);

		//mainPanel.revalidate();
		
	}
	
	private void rebuildMap() {
		MasterFilter filter = buildMasterFilterFromQuicksearchAndFilter();
		
		//load folder from user
		TreeSet<Note> notesForTable;
		try {
			if (this.currentUser != null) {
				if (this.currentFolder != null) {
					notesForTable = Note.getAllNotesByUserAndFolder(this.currentFolder, this.currentUser, filter);
				} else {
					notesForTable = Note.getAllNotesByUser(this.currentUser, filter);
				}
			} else {
				if (this.currentFolder != null) {
					notesForTable = Note.getAllNotesByFolder(this.currentFolder, filter);
				} else {
					notesForTable = Note.getAllNotes(filter);
				}
			}
		} catch (SQLException ex) {
			ex.printStackTrace();
			JOptionPane.showMessageDialog(this, "Could not get notes, " + ex.toString(), ":(", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		this.notesTable = new JTable(new NoteTableModel(notesForTable, false, false));
		this.notesTable.setAutoCreateRowSorter(true);
		this.notesTable.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);
		this.notesTable.setSelectionModel(new DefaultListSelectionModel() {
			private static final long serialVersionUID = 994339011415718978L;

			@Override
			public void clearSelection() {
			}

			@Override
			public void removeSelectionInterval(int index0, int index1) {
			}
		});
		this.notesTable.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		this.notesTable.getSelectionModel().addListSelectionListener(this);
        new NoteTablePopupMenu(this.notesTable);
		
		
		notesScroll.setViewportView(this.notesTable);
	}

	@Override
	public void valueChanged(ListSelectionEvent e) {
		if (e.getValueIsAdjusting()) return;
		
		boolean sourceIsTable = this.notesTable != null && e.getSource() == this.notesTable.getSelectionModel();
		boolean sourceIsUserList = e.getSource() instanceof JList<?>;
		
		if (sourceIsUserList) {
			JList<?> sourceList = (JList<?>) e.getSource();
			
			// loop all lists and fully deselect all but the one that was the cause
			for (JList<Folder> folderList : listToUserMap.keySet()) {
				if (folderList != sourceList) {
					folderList.clearSelection();
				}
			}
			
			Object value = sourceList.getSelectedValue();
			if (!(value instanceof Folder)) return;
			
			this.currentUser = listToUserMap.get(sourceList);
			if (this.currentUser.getUserId() < 0) this.currentUser = null; // "all users"
			this.currentFolder = (Folder)value;
			if (this.currentFolder.getFolderId() < 0) this.currentFolder = null; // "all folders"
			
			rebuildMap();
		} else if(sourceIsTable) {
			int rowInModel = this.notesTable.convertRowIndexToModel(this.notesTable.getSelectedRow());
			//System.out.println(this.notesTable.getSelectedRow());
			if (!(this.notesTable.getModel() instanceof NoteTableModel)) {
				System.err.println("whait what?");
			} else {
				Note note = ((NoteTableModel)this.notesTable.getModel()).getNoteAtRow(rowInModel);
				String text = note.getText();
				
				JEditorPane edit = new JEditorPane();//"text/html","<html><body>" + text + "</body></html>");
				edit.setEditorKit(JEditorPane.createEditorKitForContentType("text/html"));
				edit.setEditable(false);
				edit.setText("<html><body>" + text + "</body></html>");
				System.out.println(edit.getText());
				edit.addHyperlinkListener(new HyperlinkListener() {
					public void hyperlinkUpdate(HyperlinkEvent e) {
						if(e.getEventType() == HyperlinkEvent.EventType.ACTIVATED) {
							if(e.getURL() != null && Desktop.isDesktopSupported()) {
								try {
									Desktop.getDesktop().browse(e.getURL().toURI());
								} catch (IOException | URISyntaxException e1) {
									e1.printStackTrace();
								}
							}
						}
					}
				});
				//edit.setEnabled(false);
				this.noteContentScroll.setViewportView(edit);
			}
		}
	}
	
	private MasterFilter buildMasterFilterFromQuicksearchAndFilter() {
		String quickSearch = txtQuickSearch.getText().trim();
		if (!quickSearch.isEmpty()) {
			// make a masterfilter for the quicksearch if it's not empty
			// just of all string fields
			MasterFilter filter = new MasterFilter(ContainerType.ANY);
			for (FlatFilter possibleFilter : FilterDialog.POSSIBLE_FILTERS) {
				if (possibleFilter instanceof StringFilter) {
					StringFilter newFilter = (StringFilter)possibleFilter.clone();
					newFilter.setFilterMode(StringFilterMode.CONTAINS);
					newFilter.setString(quickSearch);
					filter.addFilter(newFilter);
				}
			}
			
			if (this.currentFilter == null) {
				return filter;
			} else {
				filter = MasterFilter.merge(ContainerType.ALL, this.currentFilter, filter);
				return filter;
			}
		} else {
			return this.currentFilter;
		}
	}
}
