package com.uca.bookings;


import com.uca.flights.Flight;
import java.time.ZonedDateTime;
import java.util.UUID;
import org.joda.money.Money;


/**
* Represent a flight booking 
* @Author Cesano Ugo, Colmerauer Clément
* 
*/
public class Booking {

  /** Booking date*/
  private final ZonedDateTime date;
  /** Booking identifier*/
  private final UUID          id;
  /** Booking price*/
  private final Money         price;
  /** Booking customer*/
  private final Customer      customer;
  /** Booking passenger*/
  private final Passenger     passenger;
  /** Booking flight*/
  private final Flight        flight;
  /** Booking seat number*/
  private       String        seatNumber; //Would be better to use a seat number type 
  /** Booking state*/
  private       State         state;


  /**
  * Constructor
  * @param price the booking price
  * @param flight the booking flight
  * @param customer the booking customer
  * @param passenger the booking passenger
  * @param seatNumber the booking seat number
  */
  private Booking(Money price, Flight flight, Customer customer, Passenger passenger,String seatNumber) 
  {
    this.date       = flight.getDeparture();
    this.id         = UUID.randomUUID();
    this.price      = price;
    this.seatNumber = seatNumber;
    this.state      = BookingState.INITIAL;
    this.customer   = customer;
    this.passenger  = passenger;
    this.flight     = flight;
    customer.addBooking(this);
    passenger.addBooking(this);
    flight.addBooking(this);
  }

  /**
  * Static constructor
  * @param price the booking price
  * @param flight the booking flight
  * @param customer the booking customer
  * @param passenger the booking passenger
  * @throws IllegalArgumentException on null parameter or unreservable flight
  */
  public synchronized static Booking createBooking(Money price, Flight flight, Customer customer, Passenger passenger)
  {
    if(price == null)
    {
      throw new IllegalArgumentException("Price can't be null.");
    }
    if(flight == null)
    {
      throw new IllegalArgumentException("Flight can't be null.");
    }
    if(customer == null)
    {
      throw new IllegalArgumentException("Customer can't be null.");
    }
    if(passenger == null)
    {
      throw new IllegalArgumentException("Passenger can't be null.");
    }
    if(!flight.isReservable())
    {
      throw new UnsupportedOperationException("This flight is not open to reservation : " + flight.toString() + ".");
    }
    return new Booking(price,flight,customer,passenger,null);
  }

  /**
  * Static constructor
  * @param price the booking price
  * @param flight the booking flight
  * @param customer the booking customer
  * @param passenger the booking passenger
  * @param seatNumber the booking seat number
  * @throws IllegalArgumentException on null parameter or unreservable flight
  */
  public synchronized static Booking createBooking(Money price, Flight flight, Customer customer, Passenger passenger, String seatNumber)
  {
    if(price == null)
    {
      throw new IllegalArgumentException("Price can't be null.");
    }
    if(flight == null)
    {
      throw new IllegalArgumentException("Flight can't be null.");
    }
    if(customer == null)
    {
      throw new IllegalArgumentException("Customer can't be null.");
    }
    if(passenger == null)
    {
      throw new IllegalArgumentException("Passenger can't be null.");
    }
    if(seatNumber == null || seatNumber.trim().isEmpty())
    {
      throw new IllegalArgumentException("Seat number can't be null or spaces characters.");
    }
    if(!flight.isReservable())
    {
      throw new IllegalStateException("This flight is not open to reservation : " + flight.toString() + ".");
    }
    return new Booking(price,flight,customer,passenger,seatNumber);
  }

  /**
  * Getter
  * @return booking state
  */
  public State getState()
  {
    return this.state;
  }

  /**
  * Getter
  * @return booking flight
  */
  public Flight getFlight()
  {
    return this.flight;
  }

  /**
  * Getter
  * @return booking state
  */
  public Money getPrice() 
  {
    return this.price;
  }

  /**
  * Getter
  * @return booking identifier
  */
  public UUID getId()
  {
    return this.id;
  }

  /**
  * Getter
  * @return booking date
  */
  public ZonedDateTime getDate()
  {
    return this.date;
  }

  /**
  * Getter
  * @return booking passenger
  */
  public Passenger getPassenger()
  {
    return this.passenger;
  }

  /**
  * Getter
  * @return booking seat number
  */
  public String getSeatNumber()
  {
    return this.seatNumber;
  }

  /**
  * Getter
  * @return booking customer
  */
  public Customer getCustomer()
  {
    return this.customer;
  }

  /**
  * Setter
  * @param seatNumber the booking seat number
  * @throws IllegalArgumentException on null
  */
  public void setSeatNumber(String seatNumber)
  {
    if(seatNumber == null || seatNumber.trim().isEmpty())
    {
      throw new IllegalArgumentException("Seat number can't be null or spaces characters.");
    }
    this.seatNumber = seatNumber;
  }

  /**
  * Change the state of reservation
  * @param state Le nouvel état de la réservation.
  */
  void setState(State state) 
  {
    if(state == null)
    {
      throw new IllegalArgumentException("State can't be null.");
    }
    this.state = state;
  }

  public static void removeBooking(Booking booking) {
    if (booking == null) {
      throw new NullPointerException();
    }

    booking.getCustomer().removeBookingNonRec(booking);
    booking.getPassenger().removeBookingNonRec(booking);
    try{
      booking.getFlight().removeBooking(booking);
    }
    catch(IllegalArgumentException e)
    {
      return;
    }

  }

  /**
  * pay the booking, changing behaviour depending on state
  */
  void pay() 
  {
    this.state.pay(this);
  }


  /**
  * Confirm the booking, changing behaviour depending on state
  */
  void confirm() {
    this.state.confirm(this);
  }


  /**
  * Cancel the booking, changing behaviour depending on state
  */
  void cancel() 
  {
    this.state.cancel(this);
  }

  /**
  * Redefinition of hashCode
  * @return the hashcode
  */
  @Override
  public int hashCode()
  {
    return this.id.hashCode();
  }

  /**
  * Redefinition of toString
  * @return a string refering to passenger and flight
  */
  @Override
  public String toString()
  {
    return "Passenger : " + this.passenger.toString() + "  on flight " + this.flight.toString();
  }
}
