/*
 * Copyright 2018-2019 Chris Cromer
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

namespace Sernatur {
	using LibSernatur.DB;
	using LibSernatur.Misc;

	/**
	 * The staff editor window class
	 */
	[GtkTemplate (ui = "/cl/cromer/ubb/sernatur/staff.editor.ui")]
	public class StaffEditor : Gtk.ApplicationWindow {
		/**
		 * The open database connection
		 */
		private Connection conn;
		/**
		 * The staff to edit
		 */
		private Guia staff;
		/**
		 * The city data stored in the list store
		 */
		private enum CityColumn {
			/**
			 * The city name
			 */
			CITY_NAME,
			/**
			 * The city object
			 */
			CITY,
			/**
			 * The number of colums in this enum
			 */
			N_COLUMNS
		}
		/**
		 * The region data stored in the list store
		 */
		private enum RegionColumn {
			/**
			 * The region name
			 */
			REGION_NAME,
			/**
			 * The region object
			 */
			REGION,
			/**
			 * The number of colums in this enum
			 */
			N_COLUMNS
		}
		/**
		 * The staff run
		 */
		[GtkChild]
		private Gtk.Entry run;
		/**
		 * The staff name
		 */
		[GtkChild]
		private Gtk.Entry guide_name;
		/**
		 * The street
		 */
		[GtkChild]
		private Gtk.Entry street;
		/**
		 * The number
		 */
		[GtkChild]
		private Gtk.Entry number;
		/**
		 * The region
		 */
		[GtkChild]
		private Gtk.ComboBoxText region;
		/**
		 * The city
		 */
		[GtkChild]
		private Gtk.ComboBoxText city;
		/**
		 * The city entry
		 */
		[GtkChild]
		private Gtk.Entry city_entry;
		/**
		 * The save button
		 */
		[GtkChild]
		private Gtk.Button save;
		/**
		 * The illnesses button
		 */
		[GtkChild]
		private Gtk.Button specialties;
		/**
		 * The cancel button
		 */
		[GtkChild]
		private Gtk.Button cancel;
		/**
		 * The list that stores the regions for the combo box
		 */
		private Gtk.ListStore region_list_store;
		/**
		 * The list that stores the cities for the combo box
		 */
		private Gtk.ListStore city_list_store = null;
		/**
		 * A list of associated places, this is only used in the case of a new staff
		 */
		//private List<Asociado> list_asociado = null;
		/**
		 * A saved copy of the original RUN
		 */
		private string original_run;
		/**
		 * A list of the cities from the database
		 */
		private List<Ciudad> cities;
		/**
		 * A list of the regions from the database
		 */
		private List<Region> regions;
		/**
		 * This signal is called when a staff is saved
		 */
		public signal void save_staff ();

		/**
		 * Called when a combobox changes
		 * @param combobox The combobox that changed
		 */
		[GtkCallback]
		public void on_changed_combobox (Gtk.ComboBox combobox) {
			if (combobox == region) {
				Gtk.TreeIter iter;
				region.get_active_iter (out iter);
				Region temp_region;
				if (region_list_store.iter_is_valid (iter)) {
					// The region is from the list, not typed
					region_list_store.get (iter,
						RegionColumn.REGION, out temp_region);
					staff.ciudad.region = temp_region;
					if (city_list_store != null && staff.ciudad.region.id_region != 0) {
						reset_city ();
					}
				}
			}
		}

		/**
		 * Reset the city dropdown
		 */
		private void reset_city () {
			try {
				cities = Ciudad.get_all_ciudades_in_region (conn, staff.ciudad.region.id_region);
			}
			catch (Error e) {
				#if DEBUG
					error (e.message);
				#else
					warning (e.message);
				#endif
			}

			if (cities.length () > 0) {
				cities.sort_with_data ((a, b) => {
					return strcmp (a.nombre_ciudad, b.nombre_ciudad);
				});

				if (city_list_store.iter_n_children (null) > 0) {
					// If there are more than 0 rows clear it
					city_list_store.clear ();
				}
				cities.foreach ((entry) => {
					Gtk.TreeIter iter;
					city_list_store.append (out iter);
					city_list_store.set (iter,
						CityColumn.CITY_NAME, entry.nombre_ciudad,
						CityColumn.CITY, entry);
					if (staff.ciudad.id_ciudad == 0) {
						staff.ciudad.id_ciudad = entry.id_ciudad;
						staff.ciudad.nombre_ciudad = entry.nombre_ciudad;
					}
					else {
						if (entry.id_ciudad == staff.ciudad.id_ciudad) {
							city.set_active_iter (iter);
						}
					}
				});
			}

			city.set_active (-1);
			city_entry.set_text ("");
		}

		/**
		 * Validate the staff data before trying to insert it into the database
		 * @return Returns true if the data is valid
		 */
		private bool validate_staff_data () {
			if (staff.nombre_guia.strip () == "") {
				var msg = new Gtk.MessageDialog (this,
					Gtk.DialogFlags.MODAL,
					Gtk.MessageType.ERROR,
					Gtk.ButtonsType.CLOSE,
					_ ("Error: Tourist name cannot be left blank!"));
				msg.response.connect ((response_id) => {
					msg.destroy ();
				});
				msg.set_title (_ ("Error"));
				msg.run ();
				return false;
			}
			if (staff.rut_guia.strip () == "") {
				var msg = new Gtk.MessageDialog (this,
					Gtk.DialogFlags.MODAL,
					Gtk.MessageType.ERROR,
					Gtk.ButtonsType.CLOSE,
					_ ("Error: Staff member RUN cannot be left blank!"));
				msg.response.connect ((response_id) => {
					msg.destroy ();
				});
				msg.set_title (_ ("Error"));
				msg.run ();
				return false;
			}
			try {
				new Rut (staff.rut_guia.strip ());
			}
			catch (Error e) {
				var msg = new Gtk.MessageDialog (this,
					Gtk.DialogFlags.MODAL,
					Gtk.MessageType.ERROR,
					Gtk.ButtonsType.CLOSE,
					_ ("Error: The RUN entered is invalid!"));
				msg.response.connect ((response_id) => {
					msg.destroy ();
				});
				msg.set_title (_ ("Error"));
				msg.run ();
				return false;
			}
			if (staff_exists () && staff.rut_guia != original_run) {
				var msg = new Gtk.MessageDialog (this,
					Gtk.DialogFlags.MODAL,
					Gtk.MessageType.ERROR,
					Gtk.ButtonsType.CLOSE,
					_ ("Error: A staff member with the RUN \"%s\" already exists!"), staff.rut_guia);
				msg.response.connect ((response_id) => {
					msg.destroy ();
				});
				msg.set_title (_ ("Error"));
				msg.run ();
				return false;
			}
			return true;
		}

		/**
		 * Validate the city data before trying to insert it into the database
		 * @param ciudad The city to validate
		 * @return Returns true if the data is valid
		 */
		private bool validate_city_data (Ciudad ciudad) {
			if (ciudad.nombre_ciudad.strip () == "") {
				var msg = new Gtk.MessageDialog (this,
					Gtk.DialogFlags.MODAL,
					Gtk.MessageType.ERROR,
					Gtk.ButtonsType.CLOSE,
					_ ("Error: City name cannot be left blank!"));
				msg.response.connect ((response_id) => {
					msg.destroy ();
				});
				msg.set_title (_ ("Error"));
				msg.run ();
				return false;
			}
			bool list_success = true;
			try {
				List<Ciudad> list = Ciudad.get_all_ciudades (conn);
				list.foreach ((entry) => {
					if (ciudad.nombre_ciudad.down () == entry.nombre_ciudad.down ()) {
						var msg = new Gtk.MessageDialog (this,
							Gtk.DialogFlags.MODAL,
							Gtk.MessageType.ERROR,
							Gtk.ButtonsType.CLOSE,
							_ ("Error: A city named \"%s\" already exists!"), entry.nombre_ciudad);
						msg.response.connect ((response_id) => {
							msg.destroy ();
						});
						msg.set_title (_ ("Error"));
						msg.run ();
						list_success = false;
					}
				});
			}
			catch (Error e) {
				#if DEBUG
					error (e.message);
				#else
					warning (e.message);
					return false;
				#endif
			}
			return list_success;
		}

		/**
		 * Validate the region data before trying to insert it into the database
		 * @param region The region to validate
		 * @return Returns true if the data is valid
		 */
		private bool validate_region_data (Region region) {
			if (region.nombre_region.strip () == "") {
				var msg = new Gtk.MessageDialog (this,
					Gtk.DialogFlags.MODAL,
					Gtk.MessageType.ERROR,
					Gtk.ButtonsType.CLOSE,
					_ ("Error: Region name cannot be left blank!"));
				msg.response.connect ((response_id) => {
					msg.destroy ();
				});
				msg.set_title (_ ("Error"));
				msg.run ();
				return false;
			}
			bool list_success = true;
			try {
				List<Region> list = Region.get_all_regiones (conn);
				list.foreach ((entry) => {
					if (region.nombre_region.down () == entry.nombre_region.down ()) {
						var msg = new Gtk.MessageDialog (this,
							Gtk.DialogFlags.MODAL,
							Gtk.MessageType.ERROR,
							Gtk.ButtonsType.CLOSE,
							_ ("Error: A region named \"%s\" already exists!"), entry.nombre_region);
						msg.response.connect ((response_id) => {
							msg.destroy ();
						});
						msg.set_title (_ ("Error"));
						msg.run ();
						list_success = false;
					}
				});
			}
			catch (Error e) {
				#if DEBUG
					error (e.message);
				#else
					warning (e.message);
					return false;
				#endif
			}
			return list_success;
		}

		private bool staff_exists () {
			try {
				staff.rut_guia = new Rut (run.get_text ().strip ()).get_clean_rut ();
			}
			catch (Error e) {
				#if DEBUG
					error (e.message);
				#else
					warning (e.message);
				#endif
			}
			try {
				Guia guia = Guia.get_guia_by_run (conn, staff.rut_guia);
				if (guia == null) {
					return false;
				}
				else {
					return true;
				}
			}
			catch (Error e) {
				#if DEBUG
					error (e.message);
				#else
					warning (e.message);
					return false;
				#endif
			}
		}

		/**
		 * This callback is called when a button is clicked
		 * @param button The button that was clicked
		 */
		[GtkCallback]
		public void on_clicked_button (Gtk.Button button) {
			if (button == save) {
				if (update_staff_instance () && validate_staff_data ()) {
					if (!staff_exists () && original_run == null) {
						/*try {
							Guia.insert_staff (conn, staff);
							save_staff (); // Signal the parent to update itself
							this.close ();
						}
						catch (Error e) {
							#if DEBUG
								error (e.message);
							#else
								warning (e.message);
							#endif
						}*/
					}
					else {
						/*try {
							Guia.update_staff (conn, staff, original_run);
						}
						catch (Error e) {
							#if DEBUG
								error (e.message);
							#else
								warning (e.message);
							#endif
						}
						finally {
							save_staff (); // Signal the parent to update itself
							this.close ();
						}*/
					}
				}
			}
			else if (button == specialties) {
				/*var staff_illnesses = new TouristIllnesses (application, conn, staff);
				staff_illnesses.set_transient_for (this); // Set this window as the parent of the new window
				staff_illnesses.initialize ();
				staff_illnesses.show_all ();*/
				//staff_illnesses.save_places.connect (on_save_places);
			}
			else if (button == cancel) {
				this.close ();
			}
		}

		/**
		 * Called when some associated places are saved and a staff id doesn't exist yet
		 * @param staff_places The TouristPlaces instance that called this signal
		 * @param list_asociado The list of new associations
		 */
		/*private void on_save_places (TouristPlaces staff_places, List<Asociado> list_asociado) {
			this.list_asociado = list_asociado.copy ();
			places.sensitive = false;
		}*/

		/**
		 * Called when a vehcile is assigned to the staff, and the staff is not in the database yet
		 * @param staff_assign_vehicle The TouristAssignVehicle instance that called this signal
		 * @param requerir_auto The vehicle to assign to this staff
		 */
		/*private void on_save_vehicle (TouristAssignVehicle staff_assign_vehicle, RequerirAuto? requerir_auto) {
			if (requerir_auto != null) {
				this.requerir_auto = requerir_auto;
				vehicle.sensitive = false;
			}
		}*/

		/**
		 * Update the the staff object with new info from the editor
		 * @return Returns false if the information is not valid
		 */
		private bool update_staff_instance () {
			staff.rut_guia = run.get_text ().strip ();
			staff.nombre_guia = guide_name.get_text ().strip ();

			if (region.get_active () == -1) {
				Region new_region = new Region (0, region.get_active_text ().strip ());
				try {
					if (validate_region_data (new_region)) {
						new_region.id_region = Region.insert_region (conn, new_region);
						return update_staff_instance_city (new_region);
					}
					else {
						return false;
					}
				}
				catch (Error e) {
					#if DEBUG
						error (e.message);
					#else
						warning (e.message);
					#endif
				}
			}
			else {
				Region new_region;
				Gtk.TreeIter iter;
				region.get_active_iter (out iter);
				if (region_list_store.iter_is_valid (iter)) {
					region_list_store.get (iter,
						RegionColumn.REGION, out new_region);
				}
				else {
					new_region = new Region ();
				}
				return update_staff_instance_city (new_region);
			}
			return true;
		}

		/**
		 * This method updates the city part of the tour instance
		 * @param new_region The region to insert into the city object
		 * @return Returns false if the information is not valid
		 */
		private bool update_staff_instance_city (Region new_region) {
			Ciudad ciudad;
			if (city.get_active () == -1) {
				ciudad = new Ciudad (0, city.get_active_text ().strip ());
				ciudad.region = new_region;
				try {
					if (validate_city_data (ciudad)) {
						ciudad.id_ciudad = Ciudad.insert_city (conn, ciudad);
					}
					else {
						return false;
					}
				}
				catch (Error e) {
					#if DEBUG
						error (e.message);
					#else
						warning (e.message);
					#endif
				}
				finally {
					staff.ciudad = ciudad;
				}
			}
			else {
				Gtk.TreeIter iter;
				city.get_active_iter (out iter);
				if (city_list_store.iter_is_valid (iter)) {
					city_list_store.get (iter,
						CityColumn.CITY, out ciudad);
					ciudad.region = new_region;
					staff.ciudad = ciudad;
				}
			}
			return true;
		}

		/**
		 * Initialize the staff editor class
		 * @param application The application used to make the GLib object
		 * @param conn The database connection to use
		 * @param staff The staff to edit
		 */
		public StaffEditor (Gtk.Application application, Connection conn, Guia? staff) {
			Object (application: application);
			this.conn = conn;
			this.staff = staff;
		}

		/**
		 * Initialize what is needed for this window
		 */
		public void initialize () {
			try {
				regions = Region.get_all_regiones (conn);
			}
			catch (Error e) {
				#if DEBUG
					error (e.message);
				#else
					warning (e.message);
				#endif
			}

			regions.sort_with_data ((a, b) => {
				return strcmp (a.nombre_region, b.nombre_region);
			});

			region_list_store = new Gtk.ListStore (RegionColumn.N_COLUMNS,
				typeof (string),
				typeof (Region));

			region.set_model (region_list_store);

			specialties.destroy ();
			if (staff != null) {
				original_run = staff.rut_guia;
				try {
					run.set_text (new Rut (staff.rut_guia).get_rut ());
				}
				catch (Error e) {
					#if DEBUG
						error (e.message);
					#else
						warning (e.message);
					#endif
				}
				guide_name.set_text (staff.nombre_guia);
				street.set_text (staff.calle);
				number.set_text (staff.numero.to_string ());
			}
			else {
				//specialties.destroy ();
				staff = new Guia ();
				staff.ciudad = new Ciudad ();
				staff.ciudad.region = new Region ();
			}

			regions.foreach ((entry) => {
				Gtk.TreeIter iter;
				region_list_store.append (out iter);
				region_list_store.set (iter,
					RegionColumn.REGION_NAME, entry.nombre_region,
					RegionColumn.REGION, entry);
				if (entry.id_region == staff.ciudad.region.id_region) {
					region.set_active_iter (iter);
				}
			});

			region.set_entry_text_column (RegionColumn.REGION_NAME);

			city_list_store = new Gtk.ListStore (CityColumn.N_COLUMNS,
				typeof (string),
				typeof (Ciudad));

			city.set_model (city_list_store);

			try {
				cities = Ciudad.get_all_ciudades_in_region (conn, staff.ciudad.region.id_region);
			}
			catch (Error e) {
				#if DEBUG
					error (e.message);
				#else
					warning (e.message);
				#endif
			}

			if (cities.length () > 0) {
				cities.sort_with_data ((a, b) => {
					return strcmp (a.nombre_ciudad, b.nombre_ciudad);
				});

				cities.foreach ((entry) => {
					Gtk.TreeIter iter;
					city_list_store.append (out iter);
					city_list_store.set (iter,
						CityColumn.CITY_NAME, entry.nombre_ciudad,
						CityColumn.CITY, entry);
					if (entry.id_ciudad == staff.ciudad.id_ciudad) {
						city.set_active_iter (iter);
					}
				});
			}
		}
	}
}
