/**
 * license: GPLv3
 * lodge.reservationsystem
*/
package lodge.reservation;

import java.io.BufferedWriter;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.ZonedDateTime;

import lodge.data.Address;
import lodge.data.IllegalOperationException;
import lodge.data.KitchenTypeEnum;
import lodge.data.ReservationStatusEnum;
import lodge.reservationsystem.DataRepository;

public abstract class Reservation implements IReservation{
    private char type;
    private String reservation_number = "-99";
    private Address physical_address;
    private Address mailing_address;

    private ZonedDateTime reservation_start_date;
    private ZonedDateTime reservation_end_date;
    private ReservationStatusEnum reservation_status;

    private KitchenTypeEnum kitchen;

    private Integer numberOfBeds;
    private Integer numberOfBedRooms;
    private Integer numberOfBathRooms;
    private Integer numberOfFloors;
    private Integer squareFeet;

    private Float price;

    protected String accountNumber = null;

    protected Reservation() {
        numberOfBeds = 1;
        numberOfBedRooms = 1;
        numberOfBathRooms = 1;
        numberOfFloors = 1;
        kitchen = KitchenTypeEnum.None;
        price = 120.0f;
        reservation_status = ReservationStatusEnum.Draft;
        mailing_address = null;
        physical_address = null;
    }

    public void setReservation_number(String reservation_number) {
        this.reservation_number = reservation_number;
    }

    public String getReservation_number() {
        return this.reservation_number;
    }

    public String getAccountNumber() {
        return this.accountNumber;
    }

    public void setAccountNumber(String account_number) {
        this.accountNumber = account_number;
    }

    public void setPhysical_address(Address physical_address) {
        this.physical_address = physical_address;
    }

    public Address mailing_address() {
        return mailing_address;
    }

    public void setMailing_address(Address mailing_address) {
        this.mailing_address = mailing_address;
    }

    public void setReservation_start_date(ZonedDateTime reservation_start_date) {
        this.reservation_start_date = reservation_start_date;
    }

    public void setReservation_end_date(ZonedDateTime reservation_end_date) {
        this.reservation_end_date = reservation_end_date;
    }

    public void setReservation_status(ReservationStatusEnum reservation_status) {
        this.reservation_status = reservation_status;
    }

    public KitchenTypeEnum getKitchen() {
        return kitchen;
    }

    public void setKitchen(KitchenTypeEnum kitchen) {
        this.kitchen = kitchen;
    }

    public void setNumberOfBeds(Integer numberOfBeds) {
        this.numberOfBeds = numberOfBeds;
    }

    public Integer numberOfBedRooms() {
        return numberOfBedRooms;
    }

    public void setNumberOfBedRooms(Integer numberOfBedRooms) {
        this.numberOfBedRooms = numberOfBedRooms;
    }

    public void setNumberOfBathRooms(Integer numberOfBathRooms) {
        this.numberOfBathRooms = numberOfBathRooms;
    }

    public void setNumberOfFloors(Integer numberOfFloors) {
        this.numberOfFloors = numberOfFloors;
    }

    public void setSquareFeet(Integer squareFeet) {
        this.squareFeet = squareFeet;
    }

    public void setPrice(Float price) {
        this.price = price;
    }

    public char getType() {
        return type;
    }

    public void setType(char type) {
        this.type = type;
    }

    public Address getPhysical_address() {
        return physical_address;
    }

    public Address getMailing_address() {
        return mailing_address;
    }

    public ZonedDateTime getReservation_start_date() {
        return reservation_start_date;
    }

    public ZonedDateTime getReservation_end_date() {
        return reservation_end_date;
    }

    public ReservationStatusEnum getReservation_status() {
        return reservation_status;
    }

    public Integer getNumberOfBeds() {
        return numberOfBeds;
    }

    public Integer getNumberOfBedRooms() {
        return numberOfBedRooms;
    }

    public Integer getNumberOfBathRooms() {
        return numberOfBathRooms;
    }

    public Integer getNumberOfFloors() {
        return numberOfFloors;
    }

    public Integer getSquareFeet() {
        return squareFeet;
    }

    public Float getPrice() {
        return price;
    }

    @Override
    public int hashCode() {
        final int prime = 31;
        int result = 1;
        result = prime * result + ((reservation_number == null) ? 0 : reservation_number.hashCode());
        return result;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        Reservation other = (Reservation) obj;
        if (reservation_number == null) {
            if (other.reservation_number != null)
                return false;
        } else if (!reservation_number.equals(other.reservation_number))
            return false;
        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("{ \"%s\":{", this.ReservationType()));
        sb.append("\"reservation_type\": \"").append(ReservationType()).append("\",");
        sb.append("\"reservation_number\": \"").append(reservation_number).append("\",");
        sb.append("\"reservation_status\": \"").append(reservation_status).append("\",");
        sb.append("\"reservation_start_date\": \"").append(reservation_start_date).append("\",");
        sb.append("\"reservation_end_date\": \"").append(reservation_end_date).append("\",");
        sb.append("\"account_number\": \"").append(accountNumber).append("\",");
        sb.append("\"physical_address\": ").append(physical_address).append(",");
        sb.append("\"mailing_address\": ").append(mailing_address).append(",");
        sb.append("\"kitchen\": \"").append(kitchen).append("\",");
        sb.append("\"numberOfBeds\": \"").append(numberOfBeds).append("\",");
        sb.append("\"numberOfBedRooms\": \"").append(numberOfBedRooms).append("\",");
        sb.append("\"numberOfBathRooms\": \"").append(numberOfBathRooms).append("\",");
        sb.append("\"numberOfFloors\": \"").append(numberOfFloors).append("\",");
        sb.append("\"squareFeet\": \"").append(squareFeet).append("\",");
        sb.append("\"price\": \"").append(price).append("\"");
        sb.append("}}");
        return sb.toString();
    }

    // @TODO write reservation out in json
    public void Write(Reservation reservation) throws IOException {
        String dataRoot = DataRepository.getPath();
        dataRoot = dataRoot + "/rsv-" + reservation.reservation_number + ".json";
        Path path = Paths.get(dataRoot);

        try (BufferedWriter writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) {
            writer.write(toString());
            writer.flush();
        }
    }

    public void Change(Reservation reservation, ReservationStatusEnum newStatus) throws IllegalOperationException {
        try {
            if (reservation.reservation_status == ReservationStatusEnum.Completed ||
                    reservation.reservation_status == ReservationStatusEnum.Canceled) {
                if (newStatus == ReservationStatusEnum.Canceled) {
                    throw new IllegalOperationException(
                            String.format("Invalid Change, reservation has %s.", reservation.getReservation_status()));
                }
                if (ZonedDateTime.now().compareTo(this.reservation_start_date) > -1) {
                    throw new IllegalOperationException("Invalid Change, reservation has begun.");
                }
            }

            if (newStatus == ReservationStatusEnum.Completed) {
                IReservation irsrv = (IReservation) this;
                this.setPrice(irsrv.calculatePrice());
            }
            reservation.setReservation_status(newStatus);
        } catch (IllegalOperationException e) {
            System.out.println(e.toString());
        }
    }

    public void update(Reservation rsrv) {

    }

    public abstract String ReservationType();
}
