package industries.doublehelix.furrynotemanager.gui;

import java.awt.Component;
import java.awt.Cursor;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.IOException;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.TreeSet;
import java.util.function.Function;

import javax.swing.BorderFactory;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPasswordField;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.ListSelectionModel;
import javax.swing.table.AbstractTableModel;

import industries.doublehelix.furrynotemanager.Folder;
import industries.doublehelix.furrynotemanager.Note;
import industries.doublehelix.furrynotemanager.User;
import industries.doublehelix.furrynotemanager.siteImporters.Importable;
import industries.doublehelix.furrynotemanager.siteImporters.SiteImporter;
import industries.doublehelix.furrynotemanager.siteImporters.SiteImporter.ActionType;
import layout.TableLayout;

public class UserManagerDialog extends JDialog implements ActionListener {
	private static final long serialVersionUID = 8961342445670320688L;
	JButton addUser = new JButton("Add user");
	JButton removeUser = new JButton("Remove User");
	JButton editUser = new JButton("Edit Password");
	JButton close = new JButton("Close");
	
	JTable userList = null;
	
	TreeSet<User> usersBefore = new TreeSet<>();
	
	Runnable callback = null;
	boolean usersRemoved = false;
	
	/**
	 * new user manager dialog
	 * @param parent
	 * @param callback callback method to be called if any users changed
	 * @throws SQLException
	 */
	public UserManagerDialog(Component parent, Runnable callback) throws SQLException {
		this.callback = callback;
		
		this.setSize(new Dimension(400, 200));
		this.setTitle("Manage Users");
		this.setLocationRelativeTo(parent);
		this.setModal(true);
		this.setResizable(false);
		this.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); // we do it manually in windowlistener
		this.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent e) {
				determineClosingMethod();
			}
		});
		
		// table list
		usersBefore = User.getAllUsers();
		if (usersBefore.size() == 0) {
			removeUser.setEnabled(false);
			editUser.setEnabled(false);
		}
		
		userList = new JTable(new UserModel(new ArrayList<>(usersBefore)));
		userList.setFillsViewportHeight(true);
		userList.setAutoCreateRowSorter(true);
		
		// select only single line
		userList.setSelectionModel(new DefaultListSelectionModel() {
			private static final long serialVersionUID = 6792155557098114088L;
			@Override
		    public void clearSelection() {}
		    @Override
		    public void removeSelectionInterval(int index0, int index1) {}
		});
		if (usersBefore.size() > 0) userList.setRowSelectionInterval(0, 0);
		userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		
		JPanel mainPanel = new JPanel();
		mainPanel.setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
		double sizeSettings[][] = {{TableLayout.FILL, 10, TableLayout.PREFERRED}, //width
				{TableLayout.PREFERRED, 10, TableLayout.PREFERRED, 10, TableLayout.PREFERRED, TableLayout.FILL, 10, TableLayout.PREFERRED}}; // height
		mainPanel.setLayout(new TableLayout(sizeSettings));
		mainPanel.add(new JScrollPane(userList), "0, 0, 0, 7");
		
		addUser.addActionListener(this);
		mainPanel.add(addUser, "2, 0");

		removeUser.addActionListener(this);
		mainPanel.add(removeUser, "2, 2");
		
		editUser.addActionListener(this);
		mainPanel.add(editUser, "2, 4");
		
		close.addActionListener(this);
		mainPanel.add(close, "2, 7");
		
		
		this.add(mainPanel);
		
		
		this.setVisible(true);
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		if (e.getSource() == addUser) {
			addUser();
		} else if (e.getSource() == removeUser) {
			removeUser();
		} else if(e.getSource() == editUser) {
			editUserPassword();
		} else if(e.getSource() == close) {
			determineClosingMethod();
		}
	}
	
	private void addUser() {
		try {
			AddUserDialog aud = new AddUserDialog(this);
			User user = aud.getUser();
			if (user == null) return; // user aborted
			if (user.equals(User.getUser(user.getSite(), user.getName()))) {
				JOptionPane.showMessageDialog(this, "This user is already saved", "Not Adding", JOptionPane.INFORMATION_MESSAGE);
				return;
			}
			
			this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			
			Importable importable = SiteImporter.getImportableForSite(user.getSite());
			importable.logout();
			boolean loginWorked = importable.login(user);
			if (loginWorked) {
				user.saveToDb();
				TreeSet<User> users = User.getAllUsers();
				userList.setModel(new UserModel(new ArrayList<>(users)));
			} else {
				JOptionPane.showMessageDialog(this, "Could not login with the given user",
						"Login on " + user.getSite().getName() + " failed", JOptionPane.ERROR_MESSAGE);
				return;
			}
		} catch (SQLException | IOException e1) {
			e1.printStackTrace();
			JOptionPane.showMessageDialog(this, "Problem when trying to add user: " + e1.toString(),
					"Oh god I'm so sorry", JOptionPane.ERROR_MESSAGE);
			return;
		} finally {
			this.setCursor(Cursor.getDefaultCursor());
		}
		
		if (userList.getRowCount() == 0) {
			removeUser.setEnabled(false);
			editUser.setEnabled(false);
		} else {
			removeUser.setEnabled(true);
			editUser.setEnabled(true);
			userList.setRowSelectionInterval(0, 0); // if none was there before
		}
	}
	
	private void removeUser() {
		int row = userList.getSelectedRow();
		User selectedUser = ((UserModel)userList.getModel()).getUserAt(row);
		TreeSet<Note> notes;
		try {
			notes = selectedUser.getNotes();
		} catch (SQLException e1) {
			e1.printStackTrace();
			JOptionPane.showMessageDialog(this, "Could not get notes by user, " + e1.toString(), "SQL Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		if (notes.size() > 0) {
			int response = JOptionPane.showConfirmDialog(this, "<html><body>" + selectedUser.getName() + " has "
					+ notes.size() + " Messages, are you sure you want to delete them all?<br>(Only from the app, they'll "
					+ "stay on the website)</body></html>", "Confirm Deletion", JOptionPane.YES_NO_OPTION);
			if (response != JOptionPane.YES_OPTION) return;
		}
		
		Importable importable = SiteImporter.getImportableForSite(selectedUser.getSite());
		try {
			importable.logout();
		} catch (IOException e) {
			// does not matter that much
			e.printStackTrace();
		}
		
		try {
			// remove all notes (from that user) from the db
			for (Note note : notes) {
				note.removeFromDB();
			}
			
			// get folders that are now empty
			TreeSet<Folder> folders = Folder.getFoldersByExtendedStatement("FOLDERID NOT IN (SELECT DISTINCT FOLDERID FROM NOTES) AND SYSTEMFOLDER != TRUE");
			for (Folder folder : folders) {
				folder.removeFromDB();
			}
			
			// finally, remove the user
			selectedUser.removeFromDB();
			usersRemoved = true;
			
			// rebuild table
			TreeSet<User> users = User.getAllUsers();
			userList.setModel(new UserModel(new ArrayList<>(users)));
			
		} catch (SQLException e1) {
			e1.printStackTrace();
			JOptionPane.showMessageDialog(this, "Could not delete user, " + e1.toString(), "SQL Error", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		if (userList.getRowCount() == 0) {
			removeUser.setEnabled(false);
			editUser.setEnabled(false);
		} else {
			removeUser.setEnabled(true);
			editUser.setEnabled(true);
			userList.setRowSelectionInterval(0, 0); // if none was there before
		}
	}
	
	private void editUserPassword() {
		JPasswordField pf = new JPasswordField();
		int okCxl = JOptionPane.showConfirmDialog(this, pf, "New Password", JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE);

		if (okCxl != JOptionPane.OK_OPTION) return;
		
		String password = new String(pf.getPassword());
		
		// try to login with that pass
		int row = userList.getSelectedRow();
		User selectedUser = ((UserModel)userList.getModel()).getUserAt(row);
		selectedUser.setPassword(password);
		
		try {
			this.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
			Importable importable = SiteImporter.getImportableForSite(selectedUser.getSite());
			importable.logout();
			boolean loginWorked = importable.login(selectedUser);
			if (loginWorked) {
				selectedUser.saveToDb(); // will update password
			} else {
				JOptionPane.showMessageDialog(this, "Could not login with the new password, not saved",
						"Login on " + selectedUser.getSite().getName() + " failed", JOptionPane.ERROR_MESSAGE);
				return;
			}
		} catch (SQLException | IOException e1) {
			e1.printStackTrace();
			JOptionPane.showMessageDialog(this, "Could not change password for user " + selectedUser.getName()
				+ ", " + e1.toString(), "Error", JOptionPane.ERROR_MESSAGE);
			return;
		} finally {
			this.setCursor(Cursor.getDefaultCursor());
		}
	}
	
	private void determineClosingMethod() {
		if (userList.getRowCount() == 0) {
			// no user set
			int option = JOptionPane.showConfirmDialog(this, "You have no user set. Closing this dialog causes this program to exit. Are you sure?", "Well are you, punk?", JOptionPane.YES_NO_OPTION);
			if (option == JOptionPane.YES_OPTION) {
				System.exit(0);
			}
			// do nothing
			return;
		}
		
		// see if users were changed
		try {
			TreeSet<User> allUsers = User.getAllUsers();
			TreeSet<User> addedUsers = new TreeSet<>();
			// ignore deleted users (already deleted in full at this place) and changed passwords
			for (User newUser : allUsers) {
				boolean isNew = true;
				for (User oldUser : usersBefore) {
					if (oldUser.getSite().equals(newUser.getSite()) && oldUser.getName().equals(newUser.getName())) {
						isNew = false;
						break;
					}
				}
				if (isNew) addedUsers.add(newUser);
			}
			
			if (addedUsers.size() > 0) {
				int option = JOptionPane.showConfirmDialog(this, "You added new Users. Download of the notes might take a while. Download now? (can be done later through update or sync button)", "Download notes?", JOptionPane.YES_NO_OPTION);
				if (option == JOptionPane.YES_OPTION) {
					ImprovedProgressMonitor progressMonitor = new ImprovedProgressMonitor(this, "Synchronizing..", "Synchronizing", 0, 100);
					SiteImporter importer = new SiteImporter(ActionType.SYNCHRONIZE, addedUsers, progressMonitor, callback);
					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 {
					callback.run();
				}
			} else if (usersRemoved) {
				callback.run();
			}
		} catch (SQLException e) {
			// should not happen, same method was called in ctor
			e.printStackTrace();
			JOptionPane.showMessageDialog(this, "Could not determine if users were changed, consider doing a full sync", "Error", JOptionPane.WARNING_MESSAGE);
		}
		
		this.dispose();
		return;
	}
	
	/**
	 * 
	 * @author klaue
	 *
	 */
	class UserModel extends AbstractTableModel {
		private static final long serialVersionUID = -5693432774978958382L;
		private List<User> users = null;
	    
	    public UserModel(List<User> users) {
			this.users = users;
		}

	    public Object getValueAt(int rowIndex, int columnIndex) {
	        User user = users.get(rowIndex);
	        if (columnIndex == 0)
	            return user.getName();
	        else
	            return user.getSite().getName(); 
	    }
	    
	    public User getUserAt(int rowIndex) {
	    	return users.get(rowIndex);
	    }

	    public int getColumnCount() {
	        return 2;
	    }
	    
	    @Override
	    public String getColumnName(int column) {
	    	switch(column) {
	    		case 0: return "User";
	    		case 1: return "Site";
	    	}
	    	return super.getColumnName(column);
	    }

	    public int getRowCount() {
	        return users.size();
	    }
	}
}
