//written by: Brian Goodacre
//assisted by:
//debugged by: Brian Goodacre

package dataStructures;

import java.sql.*;
import java.util.Calendar;
import java.util.ArrayList;

import money.Accountant;
import database.*;
import databases.*;


public class Reservation extends Database implements Comparable<Reservation>{
	/**Fields**/
	private int reservationID;
	private int reservationType; //0 for walk-in, 1 for reserved, 2 for long-term
	private int customerID;
	private int carID;
	private int parkingSpot;
	private Calendar reservationTimeIn;
	private Calendar reservationTimeOut;
	private Calendar actualTimeIn;
	private Calendar actualTimeOut;
	
	/*	Purpose:	Comparison
		More:		For comparison. Compares the times of the reservation
		*/
	// TODO - Check accuracy
	public int compareTo(Reservation r){
		if (reservationTimeIn.before(r.reservationTimeIn)){
			return -1;
		}
		else if (reservationTimeIn.after(r.reservationTimeIn)){
			return 1;
		}
		else{
			return 0;
		}
	}
	
	/**Constructors**/
	//everything known, create this object.
	public Reservation(
			int revID, int resType, int custID, int crID, int pSpot, 
			Calendar in, Calendar out, Calendar aIn, Calendar aOut){
		reservationID = revID;
		reservationType = resType; 
		customerID = custID;
		carID = crID;
		parkingSpot=pSpot;
		reservationTimeIn = in;
		reservationTimeOut = out;
		actualTimeIn = aIn;
		actualTimeOut = aOut;
	}
	/*	Purpose:	Make a reservation
	 * 	Input:		Customer ID
	 * 				int Reservation type
	 * 				Calendar timeIn
	 * 				Calendar timeOut
	 * 	Output:		Reservation Object if successful
	 * 				no parking spots: -1 for reservationID
	 * 				too many reservation: -2 for reservationID*/
	public Reservation(int custID, int type, int car_ID, Calendar in, Calendar out) throws ClassNotFoundException, SQLException{
		parkingSpot = LotMonitor.availableParkingSpot(in, out);
		//No parking spot
		if (parkingSpot == -1){
			reservationID=-1;
		}
		//Too many reservations
		else if (Database_Constructors.ID_to_Customer(custID).
				getUpcomingReservationsID().size()==maxReservationsPerCustomer){
			reservationID=-2;
		}
		else{//There is a parking spot and can make the reservation
			stmt.executeUpdate("" +
					" INSERT INTO Reservations " +
					" (reservation_type, customerID, carID, arrivalTime, departureTime, parkingSpaceID)" +
					" VALUES " +
					" ("+type+", " + custID+", "+car_ID+", '"+Database_Helpers.Calendar_to_SQLTime(in) +"', '" + Database_Helpers.Calendar_to_SQLTime(out) + "', " + parkingSpot+ ");");
			rs=stmt.executeQuery("SELECT MAX(reservationID) FROM Reservations");
			rs.next();
			
			/*update Reservation object*/
			reservationID=rs.getInt(1);
			customerID = custID;
			carID = car_ID; //if known
			//parkingSpot is known
			reservationTimeIn = in;
			reservationTimeOut = out;
			//actualTimeIn is updated later
			//actualTimeOut is updated later
			
			/*Update Customer Database*/
			Customer tempCust = Database_Constructors.ID_to_Customer(custID);
			tempCust.getUpcomingReservationsID().add(reservationID);
			Database_Constructors.Customer_to_Database(tempCust);
			
			/*Update Parking Space Database*/
			LotMonitor.reserveParkingSpace(parkingSpot, reservationID, reservationTimeIn, reservationTimeOut);
		}
	}
	
	
	/**Functions that edit the Database**/
	//	Purpose:	When a car has confirmed their reservation information, they call this function
	public void confirmArrival(Calendar  in) throws SQLException, ClassNotFoundException{
		//early arrival
		if(reservationTimeIn.after(in)){
			Calendar temp = Database_Helpers.copyCalendar(reservationTimeIn);
			temp.add(Calendar.MINUTE, -15);
			if(LotMonitor.availablityForAParkingSpot(parkingSpot, in, temp)){
				LotMonitor.reserveParkingSpace(parkingSpot, reservationID, in, temp);
			}
		}
		//update reservation object - add actual Arrival Time
		actualTimeIn=in;
		
		//update reservation database
		Reservation.Reservation_to_Database(this);
		
		//update customer database - current reservation
		Customer tempCust = Customer.ID_to_Customer(customerID);
		tempCust.setCurrentReservationID(reservationID);
		tempCust.getUpcomingReservationsID().remove(tempCust.getUpcomingReservationsID().indexOf(reservationID));
		Customer.Customer_to_Database(tempCust);
		
		//update parkingspaces - current reservation ID
		LotMonitor.reservationCheckIn (reservationID, parkingSpot);
	}
	
	//	Purpose:	Call when the car is leaving
	public void confirmExit() throws ClassNotFoundException, SQLException{
		// Charge user
		float price = Accountant.chargeCustomer(this);
		
		//get current new line balance
		float temp_balance = Accountant.addToBalance(1 * price);
		
		// Update Accountant Database
		stmt.executeUpdate("" +
				" INSERT INTO accountant " +
				" (charge, hours, parkingSpot, userID, userName, carID, licensePlate, newLineBalance) " +
				" VALUES ("+
							price + ", " + 
							Accountant.getTimeDifference(actualTimeIn , actualTimeOut) + ", " + 
							parkingSpot + ", " + 
							customerID + ", " + 
							"' " + Customer.ID_to_Customer(customerID).getCustomerName() + "' " + ", " +
							carID + ", " + 
							"' " + Car.ID_to_Car(carID).getLicensePlate() + "' " + ", " +
							temp_balance + " )");
	}
	
	//extend reservation 
	public int extendReservation(Calendar end) throws ClassNotFoundException, SQLException{
		int cur_parkingSpot = parkingSpot;
		Calendar temp_reservationTimeIn = Calendar.getInstance();
		temp_reservationTimeIn.set(reservationTimeOut.get(Calendar.YEAR), reservationTimeOut.get(Calendar.MONTH), reservationTimeOut.get(Calendar.DATE), reservationTimeOut.get(Calendar.HOUR), 15 + reservationTimeOut.get(Calendar.MINUTE));
		
		if(true == LotMonitor.availablityForAParkingSpot(cur_parkingSpot, temp_reservationTimeIn, end)){
			// can extend
			// update Reservation object
			reservationTimeOut = end;
			// update Reservation database
			Reservation.Reservation_to_Database(this);
			// update parking lot database
			LotMonitor.reserveParkingSpace(parkingSpot, reservationID, temp_reservationTimeIn, end);
			return reservationID;
		}
		else{
			return -1;
		}
	}
	
	/*purpose: 	update reservation
	 * output:	new reservationID
	 * 				Can find new spot: 		positive*reservationID
	 * 				Cannot Find new spot	negative*reservationID*/
	public int changeReservation(Calendar newIn, Calendar newOut) throws SQLException, ClassNotFoundException{
		// save the parking spot, arrival Time, departure Time
		int cur_reservationType=reservationType;
		int cur_customerID = customerID;
		int cur_carID = carID;
		Calendar cur_reservationTimeIn = reservationTimeIn;
		Calendar cur_reservationTimeOut = reservationTimeOut;
		
		// delete the current reservation
		this.deleteReservation();
		
		// check for a new reservation for the new given times
		Reservation tempRes = new Reservation(cur_customerID, cur_reservationType, cur_carID, newIn, newOut);
		if(tempRes.getReservationID()==-1){	// if there are no parking spaces available, recreate the current one
			tempRes = new Reservation(cur_customerID, cur_reservationType, cur_carID, cur_reservationTimeIn, cur_reservationTimeOut);
			return (-1 * tempRes.getReservationID());
		}
		else{
			return tempRes.getReservationID();
		}
	}
	/*	Purpose:	Delete a reservation
	 * 	Input:		None - call via the Reservation Object
	 * 	Output:		boolean true*/
	public boolean deleteReservation() throws ClassNotFoundException, SQLException{
		/*Update Parking Space Database*/
		LotMonitor.unReserveParkingSpace(parkingSpot, reservationTimeIn, reservationTimeOut);
		
		/*Update Customer Database*/
		Customer tempCust = Customer.ID_to_Customer(customerID); //create
		tempCust.getUpcomingReservationsID().remove(
				tempCust.getUpcomingReservationsID().indexOf(reservationID)); //remove Reservation
		Customer.Customer_to_Database(tempCust); //update Database
		
		/*Delete from Reservation Database*/
		stmt.executeUpdate("" +
				" DELETE FROM reservations " +
				" WHERE reservationID = " + reservationID);
		
		/*delete fields of this object*/
		reservationID=-1;
		
		return true;
	}
	
	
	/**Functions that access the Database but do not affect it**/
	/*	Purpose:	Given a time, return all reservations starting at that time*/
	public static ArrayList<Reservation> getAllStartReservations (Calendar c) throws SQLException, ClassNotFoundException{
		//return object
		ArrayList<Reservation> alr= new ArrayList<Reservation>(); 
		ArrayList<Integer> temp1 = new ArrayList<Integer>();
		String temp = Database_Helpers.Calendar_to_SQLTime(c);
		
		temp=temp.substring(0, 16);
		
		//query database
		ResultSet rs2 = stmt.executeQuery("" +
				" SELECT reservationID " +
				" FROM reservations " +
				" WHERE arrivalTime LIKE '" +temp+"%'");
		 
		//get reservation IDs
		while (rs2.next()){
			temp1.add(rs2.getInt(1));
			}
		//convert
		for (int i=0; i<temp1.size(); i++){
			alr.add(Database_Constructors.ID_to_Reservation(temp1.get(i)));
		}
		rs2.close();
		
		return alr;
	}
	/*	Purpose:	Given a time, return all end reservations at that time*/
	public static ArrayList<Reservation> getAllEndReservations (Calendar c) throws SQLException, ClassNotFoundException{
		//return object
		ArrayList<Reservation> alr= new ArrayList<Reservation>(); 
		ArrayList<Integer> temp1 = new ArrayList<Integer>();
		String temp = Database_Helpers.Calendar_to_SQLTime(c);
		
		temp=temp.substring(0, 16);
		
		//query database
		ResultSet rs2 = stmt.executeQuery("" +
				" SELECT reservationID " +
				" FROM reservations " +
				" WHERE departureTime LIKE '" +temp+"%'");
		
		//get reservation IDs
		while (rs2.next()){
			temp1.add(rs2.getInt(1));
			}
		//convert
		for (int i=0; i<temp1.size(); i++){
			alr.add(Database_Constructors.ID_to_Reservation(temp1.get(i)));
		}
		rs2.close();
		
		return alr;
	}
	/*	Purpose:	Given a time, return all reservations of that are during that time*/
	public static ArrayList<Integer> getAllCurrentReservationsID (Calendar c) throws SQLException, ClassNotFoundException{
		//return object
		ArrayList<Integer> temp1 = new ArrayList<Integer>();
		String column = "currentReservationID";
		
		//query database
		ResultSet rs2 = stmt.executeQuery("" +
				" SELECT " + column + 
				" FROM parkingspaces " +
				" WHERE " + column + " IS NOT NULL");
		
		//get reservationIDs
		while (rs2.next()){
			temp1.add(rs2.getInt(1));
		}
		rs2.close();
		
		return temp1;
	}
	//See above getAllCurrentReservationsID 
	public static ArrayList<Reservation> getAllCurrentReservations (Calendar c) throws SQLException, ClassNotFoundException{
		ArrayList<Integer> temp1 = getAllCurrentReservationsID (c);
		ArrayList<Reservation> alr = new ArrayList<Reservation>();
		
		//convert
			for (int i=0; i<temp1.size(); i++){
				alr.add(Database_Constructors.ID_to_Reservation(temp1.get(i)));
			}
		
		return alr;
	}
	//	Purpose:	Given an ID for a Reservation, return the Reservation
	public static Reservation ID_to_Reservation (int id) throws SQLException, ClassNotFoundException{
		return Database_Constructors.ID_to_Reservation(id);
	}
	//	Purpose:	Update Database, given a Reservation Object
	public static void Reservation_to_Database(Reservation r) throws ClassNotFoundException, SQLException{
		Database_Constructors.Reservation_to_Database(r);
	}
	
	
	/**Helper Functions*/
	public int getReservationID(){
		return reservationID;
	}
	public int getCustomerID(){
		return customerID;
	}
	public Customer getCustomer() throws ClassNotFoundException, SQLException{
		return Customer.ID_to_Customer(customerID);
	}
	public int getCarID(){
		return carID;
	}
	public Car getCar() throws ClassNotFoundException, SQLException{
		return Car.ID_to_Car(carID);
	}
	public int getReservationType(){
		return reservationType;
	}
	public int getParkingSpot(){
		return parkingSpot;
	}
	public int getFloorNumber(){
		return parkingSpot/spotsPerFloor+1;
	}
	public void setActualTimeIn(Calendar c){
		actualTimeIn=c;
	}
	public void setActualTimeOut(Calendar c){
		actualTimeOut=c;
	}
	/*purpose: returns ArrayList of Calendar objects:
	 * 1	reservationTimeIn;
	 * 2	reservationTimeOut
	 * 3	actualTimeIn
	 * 4	actualTimeOut
	 * 	*/
	public ArrayList<Calendar> reservationInformation(){
		ArrayList<Calendar> output = new ArrayList<Calendar>();
		output.add(reservationTimeIn);
		output.add(reservationTimeOut);
		output.add(actualTimeIn);
		output.add(actualTimeOut);
		return output;
	}
	public Calendar getReservedTimeIn(){
		return reservationTimeIn;
	}
	public Calendar getReservedTimeOut(){
		return reservationTimeOut;
	}
}
