package com.uca.bookings;


import com.uca.data_validation.StringValidator;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;


/**
* Represent a passenger
*
* @author Ugo Cesano
* @author Clément Colmerauer
*/
public class Passenger {

  private final UUID          id;
  private final ZonedDateTime birthdate;
  private       String        gender;
  private       String        nationality;
  private       String        passportNumber;
  private       String        countryOfResidence;
  private final Set<Booking>  bookings;
  private final String        firstName;
  private final String        lastName;
  private       int           fidelityPoint;

  //Insert other relevent fields

  /**
  * Crée une nouvelle instance de l'objet passager.
  *
  * @param firstName          Le prénom du passager.
  * @param lastName           Le nom de famille du passager.
  * @param birthdate          La date de naissance du passager.
  * @param gender             Le genre du passager.
  * @param nationality        La nationalité du passager.
  * @param passeportNumber    Le numéro de passeport du passager.
  * @param countryOfResidence Le pays de résidence du passager.
  */
  public Passenger(String firstName, String lastName, ZonedDateTime birthdate, String gender, String nationality, String passeportNumber, String countryOfResidence) 
  {
    boolean isFirstNameValid          = StringValidator.isNameValid(firstName);
    boolean isLastNameValid           = StringValidator.isNameValid(lastName);
    boolean isBirthDateValid          = Duration.between(birthdate, ZonedDateTime.now()).compareTo(Duration.ofDays(7)) >= 0;
    boolean isGenderValid             = gender != null && !gender.isBlank();
    boolean isNationalityValid        = nationality != null && !nationality.isBlank();
    boolean isPassportNumberValid     = passeportNumber != null && !passeportNumber.isBlank();
    boolean isCountryOfResidenceValid = countryOfResidence != null && !countryOfResidence.isBlank();

    if(!isFirstNameValid)
    {
      throw new IllegalArgumentException("Firstname isn't valid.");
    }
    if(!isLastNameValid)
    {
      throw new IllegalArgumentException("Lastname isn't valid.");
    }
    if(!isBirthDateValid)
    {
      throw new IllegalArgumentException("Birthdate isn't valid.");
    }
    if(!isFirstNameValid)
    {
      throw new IllegalArgumentException("Address isn't valid.");
    }
    if(!isGenderValid) 
    {
      throw new IllegalArgumentException("Gender isn't valid.");
    }
    if(!isNationalityValid)
    {
      throw new IllegalArgumentException("Nationality isn't valid.");
    }
    if(!isPassportNumberValid)
    {
      throw new IllegalArgumentException("Passport number isn't valid.");
    }
    if(!isCountryOfResidenceValid)
    {
      throw new IllegalArgumentException("Country of residence isn't valid.");
    }

    this.firstName          = firstName;
    this.lastName           = lastName;
    this.gender             = gender;
    this.nationality        = nationality;
    this.passportNumber     = passeportNumber;
    this.countryOfResidence = countryOfResidence;
    this.birthdate          = birthdate;
    this.bookings           = new HashSet<>();
    this.id = UUID.randomUUID();
    this.fidelityPoint = 0;
  }

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

  /**
  * Getter
  * @return passenger firstname
  */
  public String getFirstName()
  {
    return this.firstName;
  }

  /**
  * Getter
  * @return passenger lastname
  */
  public String getLastName() 
  {
    return this.lastName;
  }

  /**
  * Getter
  * @return passenger gender
  */
  public String getGender() 
  {
    return this.gender;
  }


  /**
  * Getter
  * @return passenger date of birth
  */
  public ZonedDateTime getBirthdate() 
  {
    return this.birthdate;
  }


  /**
  * Getter
  * @return passenger nationality
  */
  public String getNationality() 
  {
    return this.nationality;
  }

  /**
  * Getter
  * @return passenger passport number
  */
  public String getPassportNumber() 
  {
    return this.passportNumber;
  }

  /**
  * Getter
  * @return passenger country of residence
  */
  public String getCountryOfResidence() 
  {
    return this.countryOfResidence;
  }

  /**
  * Getter
  * @return Set of bookings.
  */
  public Set<Booking> getBookings() 
  {
    return new HashSet<>(this.bookings);
  }

  /**
  * Getter
  * @return passenger fidelity points
  */
  public int getFidelityPoint()
  {
    return this.fidelityPoint;
  }

  /**
  * Setter
  * @param gender the passenger mail
  * @throws IllegalArgumentException on null or empty parameter
  */
  public void setGender(String gender)
  {
    if(gender == null || gender.isBlank())
    {
      throw new IllegalArgumentException("Gender isn't valid.");
    }
    this.gender = gender;
  }

  /**
 * Setter
 * @param countryOfResidence the passenger mail
 * @throws IllegalArgumentException on null or empty parameter
 */
  public void setCountryOfResidence(String countryOfResidence)
  {
    if(countryOfResidence == null || countryOfResidence.isBlank())
    {
      throw new IllegalArgumentException("Country of residence isn't valid.");
    }
    this.countryOfResidence = countryOfResidence;
  }

  /**
  * Setter
  * @param passportNumber the passenger mail
  * @throws IllegalArgumentException on null or empty parameter
  */
  public void setPassportNumber(String passportNumber)
  {
    if(passportNumber == null || passportNumber.isBlank())
    {
      throw new IllegalArgumentException("Passport number isn't valid.");
    }
    this.passportNumber = passportNumber;
  }

  /**
  * Add a specific amout to the passenger fidelity points
  * @param i the amount to add
  * @throws IllegalArgumentException on negative i
  */
  public void addFidelityPoint(int i)
  {
    if(i <= 0)
    {
      throw new IllegalArgumentException("Can't add null or negative number.");
    }
    this.fidelityPoint += i;
  }

  /**
  * Remove a specific amout to the passenger fidelity points
  * @param i the amount to add
  * @throws IllegalArgumentException on negative or too big i
  */
  public void removeFidelityPoint(int i)
  {
    if(i <= 0)
    {
      throw new IllegalArgumentException("Can't remove null or negative number.");
    }
    if(this.fidelityPoint - i < 0)
    {
      throw new IllegalArgumentException("Can't remove more than the fidelity points.");
    }
    this.fidelityPoint -= i;
  }

  /**
  * Remove given booking.
  *
  * @param booking Booking to remove.
  * @throws IllegalArgumentException on null or non existent parameter
  */
  void removeBooking(Booking booking) 
  {
    if(booking == null)
    {
      throw new IllegalArgumentException("Booking can't be null");
    }
    if(!this.bookings.contains(booking))
    {
      throw new IllegalArgumentException(booking.getId().toString() + " isn't referenced for " + this.id.toString());
    }
    Booking.removeBooking(booking);
  }

  /**
  * Remove given booking. Non recursive on double navigability
  *
  * @param booking Booking to remove.
  * @throws IllegalArgumentException on null or non existent parameter
  */
  void removeBookingNonRec(Booking booking) 
  {
    if(booking == null)
    {
      throw new IllegalArgumentException("Booking can't be null");
    }
    if(!this.bookings.contains(booking))
    {
      throw new IllegalArgumentException(booking.getId().toString() + " isn't referenced for " + this.id.toString());
    }
    this.bookings.remove(booking);
  }


  /**
  * Add a booking to the customer, warning if already existing
  * @param booking a Booking
  * @throws IllegalArgumentException on null parameter
  */
  void addBooking(Booking booking) 
  {
    if(booking == null)
    {
      throw new IllegalArgumentException("Booking can't be null");
    }
    if(this.bookings.contains(booking))
    {
      System.err.println(this.id.toString() +" already has reservation " + booking.getId().toString());
    }
    else
    {
      this.bookings.add(booking);
    }
  }

  /**
  * Redefinition of toString
  * @return a string refering to firstname and lastname
  */
  @Override
  public String toString()
  {
    return this.firstName + " " + this.lastName;
  }

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

}
