package com.uca.bookings;


import com.uca.data_validation.StringValidator;
import java.util.HashSet;
import java.util.Set;
import org.joda.money.Money;
import java.util.UUID;


/**
* Represent a customer of the company
*
* @author Ugo Cesano, Clément Colmerauer
*/
public class Customer {

  private final UUID           id;
  private final String         firstName;
  private final String         lastName;
  private       String         email;
  private       String         address;
  private       PaymentMethod  paymentMethod;
  private final Set<Booking>   bookings;

  //Insert other relevent fields

  /**
  * Constructor
  *
  * @param firstName     Customer firstname
  * @param lastName      Customer lastname
  * @param email         Customer email
  * @param address       Customer address
  * @param paymentMethod Customer means of payment
  * @throws IllegalArgumentException On null or non-valid argument
  */
  public Customer(String firstName, String lastName, String email, String address, PaymentMethod paymentMethod) 
  {
    boolean isFirstNameValid     = StringValidator.isNameValid(firstName);
    boolean isLastNameValid      = StringValidator.isNameValid(lastName);
    boolean isEmailValid         = StringValidator.isEmailValid(email);
    boolean isAddressValid       = StringValidator.isAddressValid(address);
    boolean isPaymentMethodValid = paymentMethod != null;

    if(!isFirstNameValid)
    {
      throw new IllegalArgumentException("Firstname isn't valid.");
    }
    if(!isLastNameValid)
    {
      throw new IllegalArgumentException("Lastname isn't valid.");
    }
    if(!isEmailValid)
    {
      throw new IllegalArgumentException("Email isn't valid.");
    }
    if(!isAddressValid)
    {
      throw new IllegalArgumentException("Address isn't valid.");
    }
    if(!isPaymentMethodValid) 
    {
      throw new IllegalArgumentException("Payment method can't be null");
    }

    this.firstName     = firstName;
    this.lastName      = lastName;
    this.email         = email;
    this.address       = address;
    this.paymentMethod = paymentMethod;
    this.bookings      = new HashSet<>();
    this.id = UUID.randomUUID();
  }

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

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

  /**
  * Getter
  * @return customer mail
  */
  public String getEmail() 
  {
    return this.email;
  }

  /**
  * Getter
  * @return customer address
  */
  public String getAddress() 
  {
    return this.address;
  }

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

  /**
  * Getter
  * @return a safe version of customer payment method
  */
  public PaymentMethod getPaymentMethod()
  {
    return this.paymentMethod.hide();
  } 

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

  /**
  * Setter
  * @param email the customer email
  * @throws IllegalArgumentException on null or empty parameter
  */
  public void setEmail(String email)
  {
    if(!StringValidator.isEmailValid(email))
    {
      throw new IllegalArgumentException("Email isn't valid.");
    }
    this.email = email;
  }

  /**
  * Setter
  * @param address the customer address
  * @throws IllegalArgumentException on null or empty parameter
  */
  public void setAdress(String address)
  {
    if(!StringValidator.isAddressValid(address))
    {
      throw new IllegalArgumentException("Address isn't valid.");
    }
    this.address = address;
  }

  /**
  * Setter
  * @param paymentMethod the customer payment method
  * @throws IllegalArgumentException on null parameter
  */
  public void setPaymentMethod(PaymentMethod paymentMethod)
  {
    if(paymentMethod == null)
    {
      throw new IllegalArgumentException("Payment method can't be null.");
    }
    this.paymentMethod = paymentMethod;
  }


  /**
  * Charge the amount to the customer
  * @param amount a money amount
  * @throws IllegalArgumentException on null parameter
  */
  void charge(Money amount) 
  {
    if(amount == null) //Should also check if negative 
    {
      throw new IllegalArgumentException("Amount can't be null.");
    }
    this.paymentMethod.charge(amount);
  }


  /**
  * Refund the price of the booking to the customer
  * @param booking a Booking
  * @throws IllegalArgumentException on null parameter
  */
  void refund(Money amount) 
  {
    if(amount == null) //Should also check if negative
    {
      throw new IllegalArgumentException("Amount can't be null.");
    }
    this.paymentMethod.refund(amount);
  }


  /**
  * Remove given booking.
  *
  * @param booking Booking to remove.
  * @throws IllegalArgumentException on null or non existent parameter
  */
  public 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();
  }
}
