/*
 * 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;

	/**
	 * The tour editor window class
	 */
	[GtkTemplate (ui = "/cl/cromer/ubb/sernatur/tour.editor.ui")]
	public class TourEditor : Gtk.ApplicationWindow {
		/**
		 * The open database connection
		 */
		private Connection conn;
		/**
		 * The tour to edit
		 */
		private Tour tour;
		/**
		 * 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 tour name
		 */
		[GtkChild]
		private Gtk.Entry tour_name;
		/**
		 * The individual cost
		 */
		[GtkChild]
		private Gtk.Entry indiv_cost;
		/**
		 * The group cost
		 */
		[GtkChild]
		private Gtk.Entry group_cost;
		/**
		 * The minimum number of people
		 */
		[GtkChild]
		private Gtk.SpinButton minimum_people;
		/**
		 * The region
		 */
		[GtkChild]
		private Gtk.ComboBoxText region;
		/**
		 * The save button
		 */
		[GtkChild]
		private Gtk.Button save;
		/**
		 * The places button
		 */
		[GtkChild]
		private Gtk.Button places;
		/**
		 * The vehicle button
		 */
		[GtkChild]
		private Gtk.Button vehicle;
		/**
		 * The cancel button
		 */
		[GtkChild]
		private Gtk.Button cancel;
		/**
		 * The city
		 */
		[GtkChild]
		private Gtk.ComboBoxText city;
		/**
		 * The city entry
		 */
		[GtkChild]
		private Gtk.Entry city_entry;
		/**
		 * A list of the cities from the database
		 */
		private List<Ciudad> cities;
		/**
		 * A list of the regions from the database
		 */
		private List<Region> regions;
		/**
		 * 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 tour
		 */
		private List<Asociado> list_asociado = null;
		/**
		 * The vehicle to be inserted if the new tour is saved
		 */
		private RequerirAuto requerir_auto = null;
		/**
		 * This signal is called when a tour is saved
		 */
		public signal void save_tour ();

		/**
		 * 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);
					tour.ciudad.region = temp_region;
					if (city_list_store != null && tour.ciudad.region.id_region != 0) {
						reset_city ();
					}
				}
			}
		}

		/**
		 * Reset the city dropdown
		 */
		private void reset_city () {
			try {
				cities = Ciudad.get_all_ciudades_in_region (conn, tour.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 (tour.ciudad.id_ciudad == 0) {
						tour.ciudad.id_ciudad = entry.id_ciudad;
						tour.ciudad.nombre_ciudad = entry.nombre_ciudad;
					}
					else {
						if (entry.id_ciudad == tour.ciudad.id_ciudad) {
							city.set_active_iter (iter);
						}
					}
				});
			}

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

		/**
		 * Validate the tour data before trying to insert it into the database
		 * @return Returns true if the data is valid
		 */
		private bool validate_tour_data () {
			if (tour.nombre_tour.strip () == "") {
				var msg = new Gtk.MessageDialog (this,
					Gtk.DialogFlags.MODAL,
					Gtk.MessageType.ERROR,
					Gtk.ButtonsType.CLOSE,
					_ ("Error: Tour 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<Tour> list = Tour.get_all_tours (conn);
				list.foreach ((entry) => {
					if (tour.nombre_tour.down () == entry.nombre_tour.down () && tour.id_tour != entry.id_tour) {
						var msg = new Gtk.MessageDialog (this,
							Gtk.DialogFlags.MODAL,
							Gtk.MessageType.ERROR,
							Gtk.ButtonsType.CLOSE,
							_ ("Error: A tour named \"%s\" already exists!"), entry.nombre_tour);
						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 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;
		}

		/**
		 * 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_tour_instance () && validate_tour_data ()) {
					if (tour.id_tour == 0) {
						try {
							Tour.insert_tour (conn, tour);
							if (list_asociado != null) {
								// Insert all the pending associations
								list_asociado.foreach ((entry) => {
									try {
										Asociado.insert_asociado (conn, entry);
									}
									catch (Error e) {
										#if DEBUG
											error (e.message);
										#else
											warning (e.message);
										#endif
									}
								});
							}
							if (requerir_auto != null) {
								try {
									RequerirAuto.insert_require_vehicle (conn, requerir_auto);
								}
								catch (Error e) {
									#if DEBUG
										error (e.message);
									#else
										warning (e.message);
									#endif
								}
							}
							save_tour (); // Signal the parent to update itself
							this.close ();
						}
						catch (Error e) {
							#if DEBUG
								error (e.message);
							#else
								warning (e.message);
							#endif
						}
					}
					else {
						try {
							Tour.update_tour (conn, tour);
						}
						catch (Error e) {
							#if DEBUG
								error (e.message);
							#else
								warning (e.message);
							#endif
						}
						finally {
							save_tour (); // Signal the parent to update itself
							this.close ();
						}
					}
				}
			}
			else if (button == places) {
				var tour_places = new TourPlaces (application, conn, tour);
				tour_places.set_transient_for (this); // Set this window as the parent of the new window
				tour_places.initialize ();
				tour_places.show_all ();
				tour_places.save_places.connect (on_save_places);
			}
			else if (button == vehicle) {
				var tour_assign_vehicle = new TourAssignVehicle (application, conn, tour);
				tour_assign_vehicle.set_transient_for (this); // Set this window as the parent of the new window
				tour_assign_vehicle.initialize ();
				tour_assign_vehicle.show_all ();
				tour_assign_vehicle.save_vehicle.connect (on_save_vehicle);
			}
			else if (button == cancel) {
				this.close ();
			}
		}

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

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

		/**
		 * Update the the tour object with new info from the editor
		 * @return Returns false if the information is not valid
		 */
		private bool update_tour_instance () {
			tour.nombre_tour = tour_name.get_text ().strip ();
			tour.costo_indiv = (uint) int.parse (indiv_cost.get_text ());
			tour.costo_grupal = (uint) int.parse (group_cost.get_text ());
			tour.minima_personas = (uint) minimum_people.get_value_as_int ();
			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_tour_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_tour_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_tour_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 {
					tour.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;
					tour.ciudad = ciudad;
				}
			}
			return true;
		}

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

		/**
		 * 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);

			if (tour != null) {
				tour_name.set_text (tour.nombre_tour);
				indiv_cost.set_text (tour.costo_indiv.to_string ());
				group_cost.set_text (tour.costo_grupal.to_string ());
				minimum_people.set_value (tour.minima_personas);
			}
			else {
				tour = new Tour ();
				tour.ciudad = new Ciudad ();
				tour.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 == tour.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, tour.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 == tour.ciudad.id_ciudad) {
						city.set_active_iter (iter);
					}
				});
			}
		}
	}
}
