/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.events;

import net.ranides.assira.annotations.Meta;
import net.ranides.assira.events.EventLockQue.OwnableLockQue;
import net.ranides.assira.events.EventLockQue.SingleLock;

/**
 * Klasa pozwalająca na synchroniczną obsługę zdarzeń. Pozwala wstrzymać wykonanie
 * bieżącego wątku i oczekiwać, aż do wskazanego {@link EventRouter}'a dotrze
 * zdarzenie określonego typu.
 * <p>
 * Zależnie od rodzaju implementacji, klasa może oferować usługi o różnym stopniu
 * dokładności i bezpieczeństwa.
 * </p>
 * <p>Najsilniejsze gwarancje, to możliwość obsługi wszystkich zdarzeń określonego
 * rodzaju bez ryzyka pominięcia żadnego. Nawet jeśli jedno lub więcej zdarzeń
 * nastąpiło przed (lub pomiędzy) wywołaniem metody {@link #waitForEvent}, to zostanie
 * ono dostarczone. Gwarantowana jest również prawidłowa kolejność dostarczenia
 * komunikatów przez kolejne wywołania metod blokujących. Implementacje "silne"
 * mają duże wymagania pamięciowe, w ekstremalnym przypadku mogą spowodować
 * przepełnienie sterty, jeśli program zostanie zalany komunikatami.
 * </p>
 * <p>
 * "Słaby {@code EventLock}" daje gwarancje, że metody {@link #waitForEvent} nie
 * będą blokować, jeśli pomiędzy ich wywołaniami (albo przed wywołaniem) wystąpiło
 * obserwowane zdarzenie. Nie dają jednak gwarancji dostarczenia wszystkich
 * komunikatów - część z nich może być utracona/pominięta, w przypadku wysokiego
 * obciążenia. Słaby {@code EventLock} udostępnia informację o ilości zgubionych
 * komunikatów. Jego zaletą jest znacznie mniejsze zużycie pamięci oraz brak podatności
 * na przeciążenie aplikacji.
 * </p>
 * <p>
 * "Niebezpieczny {@code EventLock}" nie oferuje żadnych gwarancji. Co więcej:
 * metody {@link #waitForEvent} zawsze blokują, a jako wynik zwracają tylko i
 * wyłącznie wydarzenie, które nastąpiło w czasie blokady. Z tego powodu jest
 * silnie podatny na <i>race condition</i>. Jego zaletą jest niemal całkowity
 * brak obciążenia pamięci oraz procesora. Z tego powodu może być z powodzeniem
 * używany do obserwacji zdarzeń przychodzących w bardzo dużo odstępach (względem czasu
 * obsługi) bez obaw o wydajność aplikacji.
 * </p>
 * @author ranides
 */
public abstract class EventLock {

    /**
     * Niszczy obiekt {@code EventLock} i zwalnia wszystkie zasoby.
     */
    public abstract void release();

    /**
     * Resetuje stan obiektu - jeśli {@code EventLock} przechowywał w kolejce
     * nieobsłużone zdarzenia - są one usuwane. Wywołanie funkcji {@code reset}
     * pomiędzy dwoma wywołaniami metody {@code wait} może spowodować, że część zdarzeń,
     * którymi {@code EventLock} był zainteresowany zostanie utracona.
     */
    public abstract void reset();

    /**
     * Metoda, której obsługa przez konkretne implementacje jest obowiązkowa.
     * Obiekt zwrócony przez metodę jest używany do łączenia z routerem, na który
     * będziemy oczekiwać.
     * @return
     */
    protected abstract EventListener<? super Event> listener();

    /**
     * Metoda blokująca: wstrzymuje wykonanie bieżącego wątku do momentu, aż
     * do obserwowanego {@code EventRouter}'a dotrze dowolne zdarzenie oczekiwane
     * przez {@code EventLock} lub wystąpi wyjątek {@code InterruptedException}.
     * Zobacz opis klasy {@link EventLock} aby poznać szczegóły.
     * @return oczekiwane zdarzenie
     * @throws InterruptedException
     */
    public abstract Event waitForEvent() throws InterruptedException;

    /**
     * Metoda blokująca: wstrzymuje wykonanie bieżącego wątku do momentu, aż
     * do obserwowanego {@code EventRouter}'a dotrze dowolne zdarzenie oczekiwane
     * przez {@code EventLock}, wystąpi wyjątek {@code InterruptedException}, lub
     * minie podany czas.
     * Zobacz opis klasy {@link EventLock} aby poznać szczegóły.
     * @param timeout
     * @return oczekiwane zdarzenie
     * @throws InterruptedException
     */
    public abstract Event waitForEvent(long timeout) throws InterruptedException;

    /**
     * Wersja oczekująca na zdarzenie konkretnego rodzaju. Zachowuje się identycznie
     * jak {@link #waitForEvent()}, z tą różnicą, że reaguje na mniejszy zakres
     * zdarzeń - zawężony tylko do podanej klasy oraz jej pochodnych.
     * <p>
     * Metoda przydatna szczególnie wtedy, gdy utworzony obiekt {@code EventLock}
     * reaguje na bardzo ogólną klasę zdarzeń, a użytkownik w danym momencie chce
     * wstrzymać wykonanie w oczekiwaniu na zdarzenie bardziej szczegółowe.
     * </p>
     * @param <T>
     * @param event
     * @return oczekiwane zdarzenie lub {@code null}, jeśli minął {@code timeout}
     * @throws InterruptedException
     */
    public abstract <T extends Event> T waitForEvent(Class<T> event) throws InterruptedException;

    /**
     * Wersja oczekująca na zdarzenie konkretnego rodzaju. Zachowuje się identycznie
     * jak {@link #waitForEvent(long timeout)}, z tą różnicą, że reaguje na mniejszy zakres
     * zdarzeń - zawężony tylko do podanej klasy oraz jej pochodnych.
     * <p>
     * Metoda przydatna szczególnie wtedy, gdy utworzony obiekt {@code EventLock}
     * reaguje na bardzo ogólną klasę zdarzeń, a użytkownik w danym momencie chce
     * wstrzymać wykonanie w oczekiwaniu na zdarzenie bardziej szczegółowe.
     * </p>
     * @param <T>
     * @param event
     * @param timeout
     * @return oczekiwane zdarzenie lub {@code null}, jeśli minął {@code timeout}
     * @throws InterruptedException
     */
    public abstract <T extends Event> T waitForEvent(Class<T> event, long timeout) throws InterruptedException;



    /**
     * Metoda tworzy silny {@code EventLock} - szczegóły "silnego kontraktu"
     * są w opisie klasy {@link EventLock}).
     * <p>
     * Jeśli obiekt nie jest już potrzebny, musi zostać zwolniony za pomocą metody {@link #release()}
     * </p>
     * @param event
     * @param router
     * @return
     */
    public static EventLockQue lock(Class<? extends Event> event, EventRouter router) {
        return bind(event, router, new OwnableLockQue(event, router));
    }

    /**
     * Metoda tworzy słaby {@code EventLock} - szczegóły "słabego kontraktu"
     * są w opisie klasy {@link EventLock}).
     * <p>
     * Jeśli obiekt nie jest już potrzebny, musi zostać zwolniony za pomocą metody {@link #release()}
     * </p>
     * @param event
     * @param router
     * @param capacity
     * @return
     */
    public static EventLockQue weakLock(Class<? extends Event> event, EventRouter router, int capacity) {
        return bind(event, router, new OwnableLockQue(event, router, capacity));
    }

    /**
     * Metoda tworzy słaby {@code EventLock}, który może zostać wykorzystany tylko raz
     * - szczegóły "słabego kontraktu" są w opisie klasy {@link EventLock}).
     * <p>
     * Po wywołaniu zakończeniu metody {@code waitForEvent} obiekt sam automatycznie
     * zwalnia wszystkie zasoby i przestaje być funcjonalny.
     * </p>
     * @param event
     * @param router
     * @return
     */
    public static EventLock singleLock(Class<? extends Event> event, EventRouter router) {
        return bind(event, router, new SingleLock(event, router));
    }

    /**
     * Metoda tworzy niebezpieczny {@code EventLock} - szczegóły "niebezpiecznego kontraktu"
     * są w opisie klasy {@link EventLock}).
     * <p>
     * Jeśli obiekt nie jest już potrzebny, musi zostać zwolniony za pomocą metody {@link #release()}
     * </p>
     * @param event
     * @param router
     * @return
     */
    @Meta.Unsafe
    public static EventLock unsafeLock(Class<? extends Event> event, EventRouter router) {
        return bind(event, router, new EventLockUnsafe(event, router) );
    }


    @SuppressWarnings("PMD.UnusedPrivateMethod") // false positive
    private static <T extends EventLock> T bind(Class<? extends Event> event, EventRouter router, T lock) {
        router.addEventListener(event, lock.listener());
        return lock;
    }

}
