// Autore:              Eyad Issa
// Anno accademico:     2022/23

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RampaAccesso {

    public static enum Veicolo {
        NESSUNO, AUTOMOBILE, AMBULANZA;
    }

    private final Lock lock = new ReentrantLock();

    private final CountingCondition macchineAttesaIn = new CountingCondition(lock, "Macchine in attesa entrata");
    private final CountingCondition ambulanzeAttesaInSirenaOn = new CountingCondition(lock,
            "Ambulanze sirena ON in attesa entrata");
    private final CountingCondition ambulanzeAttesaInSirenaOff = new CountingCondition(lock,
            "Ambulanze sirena OFF in attesa entrata");
    private final CountingCondition macchineAttesaOut = new CountingCondition(lock, "Macchine in attesa uscita");
    private final CountingCondition ambulanzeAttesaOut = new CountingCondition(lock, "Ambulanze in attesa uscita");

    private Veicolo rampaIn = Veicolo.NESSUNO;
    private Veicolo rampaOut = Veicolo.NESSUNO;

    /* N massimo di soste nella camera calda */
    private final int maxCameraCalda;

    private int macchineCameraCalda = 0;
    private int ambulanzeCameraCalda = 0;

    public RampaAccesso(final int maxCameraCalda) {
        this.maxCameraCalda = maxCameraCalda;
    }

    private int veicoliCameraCalda() {
        return ambulanzeCameraCalda + macchineCameraCalda;
    }

    private boolean cameraCaldaPiena() {
        return veicoliCameraCalda() >= maxCameraCalda;
    }

    public void entraRampaEntrata(Veicolo veicolo, boolean sirena) throws InterruptedException {
        try {
            lock.lock();
            if (veicolo == Veicolo.AMBULANZA) {
                if (sirena) {
                    ambulanzeAttesaInSirenaOn.waitWhile(() -> cameraCaldaPiena()
                            // La rampa di ingresso è occupata
                            || rampaIn != Veicolo.NESSUNO

                            // C'è un ambulanza nel verso opposto
                            || rampaOut == Veicolo.AMBULANZA

                            // Precedenza a quelle che vogliono uscire
                            || ambulanzeAttesaOut.anyoneWaiting()
                            || macchineAttesaOut.anyoneWaiting());
                } else {
                    ambulanzeAttesaInSirenaOff.waitWhile(() -> cameraCaldaPiena()
                            // La rampa di ingresso è occupata
                            || rampaIn != Veicolo.NESSUNO

                            // C'è un ambulanza nel verso opposto
                            || rampaOut == Veicolo.AMBULANZA

                            // Precedenza alle uscite
                            || ambulanzeAttesaOut.anyoneWaiting()

                            // Precedenza a quelle in entrata con sirena on
                            || ambulanzeAttesaInSirenaOn.anyoneWaiting());
                }
            } else if (veicolo == Veicolo.AUTOMOBILE) {

                macchineAttesaIn.waitWhile(() -> cameraCaldaPiena()
                        // Rampa piena
                        || rampaIn != Veicolo.NESSUNO

                        // Precedenza ambulanze in entrata
                        || ambulanzeAttesaInSirenaOff.anyoneWaiting()
                        || ambulanzeAttesaInSirenaOn.anyoneWaiting()

                        // Macchine in camera calda non devono superare tot
                        || macchineCameraCalda > (maxCameraCalda / 2));

            } else
                throw new IllegalArgumentException();

            rampaIn = veicolo;

        } finally {
            lock.unlock();
        }

    }

    public void esciRampaEntrata(Veicolo veicolo) {
        try {
            lock.lock();

            rampaIn = Veicolo.NESSUNO;
            if (veicolo == Veicolo.AMBULANZA) {
                ambulanzeCameraCalda++;
            } else if (veicolo == Veicolo.AUTOMOBILE) {
                macchineCameraCalda++;
            } else
                throw new IllegalArgumentException();

            ambulanzeAttesaInSirenaOn.signal();
            ambulanzeAttesaInSirenaOff.signal();
            macchineAttesaIn.signal();

            ambulanzeAttesaOut.signal(); // Nel caso ci sia un ambulanza bloccata nella "camera calda"

        } finally {
            lock.unlock();
        }
    }

    public void entraRampaUscita(Veicolo veicolo) throws InterruptedException {
        try {
            lock.lock();

            if (veicolo == Veicolo.AMBULANZA) {
                ambulanzeAttesaOut.waitWhile(() ->
                // La rampa deve essere vuota
                rampaOut != Veicolo.NESSUNO
                        // Non ci devono essere ambulanze nel verso opposto
                        || rampaIn == Veicolo.AMBULANZA);

                ambulanzeCameraCalda--;

            } else if (veicolo == Veicolo.AUTOMOBILE) {
                macchineAttesaOut.waitWhile(() ->
                // La rampa deve essere vuota
                rampaOut != Veicolo.NESSUNO
                        // Precedenza alle amb. in uscita
                        || ambulanzeAttesaOut.anyoneWaiting());

                macchineCameraCalda--;
            } else
                throw new IllegalArgumentException();

            rampaOut = veicolo;
            // Segnala alla gente che aspettava si liberasse un posto
            ambulanzeAttesaInSirenaOn.signal();
            ambulanzeAttesaInSirenaOff.signal();
            macchineAttesaIn.signal();

        } finally {
            lock.unlock();
        }
    }

    public void esciRampaUscita() throws InterruptedException {
        try {
            lock.lock();

            rampaOut = Veicolo.NESSUNO;
            // Segnala alla gente che aspettava si liberasse la rampa
            ambulanzeAttesaOut.signal();
            macchineAttesaOut.signal();

            ambulanzeAttesaInSirenaOn.signal();
            ambulanzeAttesaInSirenaOff.signal();

        } finally {
            lock.unlock();
        }
    }

}