package org.springframework.prospring.ticket.web;

import java.util.*;

import javax.servlet.http.*;

import org.springframework.prospring.ticket.domain.*;
import org.springframework.prospring.ticket.domain.support.*;
import org.springframework.prospring.ticket.service.*;
import org.springframework.web.bind.*;
import org.springframework.web.servlet.*;
import org.springframework.web.servlet.mvc.*;

/**
 * Kontroler pobierający przedstawienie z kalendarza zdarzeń (EventsCalendar)
 * w celu jego przedstawienia na stronie.<p>
 * Dane wejściowe: showId (identyfiaktor przedstawienia)<p>
 * Model:
 * <ul>
 *  <li><code>show</code>: przedstawienie jakie będzie prezentowane
 *  <li><code>performances</code>: lista obiektów PerformancesWithAvailability
 *  <li><code>showInfoUrl</code>: adres URL do strony zawierającej informacje o przedstawieniu.
 *  <li><code>seatingPlanUrl</code>: adres URL do strony zawierającej plan miejsc dla przedstawienia.
 *  <li><code>seatClasses</code>: lista wszystkich kategorii miejsc
 * </ul>
 */
public class DisplayShowController extends AbstractController {

    private final static String STATIC_CONTENT_SUFFIX = ".htm";
    private final static String URL_PATH_SEPERATOR = "/";

    // usługa BoxOffice używana przez ten kontroler.
    private BoxOffice boxOffice;

    // usługa kalendarza zdarzeń używana przez ten kontroler.
    private EventsCalendar eventsCalendar;

    // nazwa zwracanego widoku.
    private String viewName;

    // położenie plików z informacjami o przedstawieniu.
    private String showInfosLocation;

    // położenie plików z informacjami o planie miejsc dla tego przedstawienia.
    private String seatingPlansLocation;

    /**
     * @see AbstractController#handleRequestInternal(
     *      javax.servlet.http.HttpServletRequest,
            javax.servlet.http.HttpServletResponse)
     */
    protected ModelAndView handleRequestInternal(HttpServletRequest httpRequest, HttpServletResponse httpResponse)
        throws Exception {

        // jeśli w sesji jest jakaś rezerwacja to odwołujemy ją i usuwamy z sesji.
        cancelBookingIfOneExists(httpRequest);

        long showId = RequestUtils.getRequiredLongParameter(httpRequest, "showId");

        Show show = eventsCalendar.getShow(showId);
        PerformanceWithAvailability[] availability = boxOffice.getAvailabilityForPerformances(show);

        Date lastPerformanceDate = (availability.length == 0) ? null : availability[availability.length-1].getDateAndTime();

        ModelAndView mav = new ModelAndView(viewName);
        mav.addObject("performances", availability);
        mav.addObject("performancesCount", new Integer(availability.length));
        mav.addObject("lastPerformanceDate", lastPerformanceDate);
        mav.addObject("show", show);
        mav.addObject("showInfoUrl", getInfoUrl(show));
        mav.addObject("seatingPlanUrl", getSeatingPlanUrl(show));
        mav.addObject("seatClasses", getSeatClasses(availability));

        return mav;
    }

    /**
     * Sets the box office service this controller should use.
     * Ustawia obiekt usługi BoxOffice jakiego powinien używać ten kontroler.
     * @param boxOffice Obiekt usługi BoxOffice jakiego powinien używać ten kontroler.
     */
    public void setBoxOffice(BoxOffice boxOffice) {
        this.boxOffice = boxOffice;
    }

    /**
     * Ustawia kalendarz zdarzeń jakiego powinien używać ten kontroler.
     * @param eventsCalendar Kalendarz zdarzeń jakiego powinien używać ten kontroler.
     */
    public void setEventsCalendar(EventsCalendar eventsCalendar) {
        this.eventsCalendar = eventsCalendar;
    }

    /**
     * Ustawia lokalizację polików zawierających informacje o przedstawieniu. 
     * Położenie to jest określane względem katalogu głównego aplikacji.
     * @param showInfosLocation Położenie plików z informacjami o przedstawieniu.
     */
    public void setShowInfosLocation(String showInfosLocation) {
        this.showInfosLocation = showInfosLocation;
    }

    /**
     * Ustawia lokalizację polików zawierających informacje o planie miejsc. 
     * Położenie to jest określane względem katalogu głównego aplikacji.
     * @param seatingPlansLocation Położenie plików zawierajacych informacje o planie miejsc.
     */
    public void setSeatingPlansLocation(String seatingPlansLocation) {
        this.seatingPlansLocation = seatingPlansLocation;
    }

    /**
     * Ustawia nazwę widoku zwracanego przez kontroler.
     * @param viewName Nazwa widoku zwracanego przez ten kontroler.
     */
    public void setViewName(String viewName) {
        this.viewName = viewName;
    }

    /**
     * Sprawdza czy w sesji jest jakaś rezerwacja. Jeśli jest, to rezerwacja ta zostaje 
     * anulowana i usunięta z sesji.
     * Czynności te są wykonywane w celu usunięcia rezerwacji wykonanej przeaz użytkownika, który
     * nie doszedł do etapu przeprowadzenia płatności i zdecycowałał się na wybranie innej daty 
     * lub grupy cenowej biletów.
     * @param request
     */
    protected void cancelBookingIfOneExists(HttpServletRequest request) {
        HttpSession session = request.getSession(true);
        Reservation reservation = (Reservation)session.getAttribute("reservation");
        if (reservation == null) {
            return;
        }
        session.removeAttribute("reservation");
        session.removeAttribute("reservationRequest");
        Booking booking = reservation.getBooking();
        boxOffice.cancelBooking(booking);
    }

    // zwraca adres URL pliku zawierającego informacje o tym przedstawieniu.
    private String getInfoUrl(Show show) {
        return showInfosLocation + URL_PATH_SEPERATOR + show.getId() + STATIC_CONTENT_SUFFIX;
    }

    // zwraca adres URL pliku z informacjami o planie miejsc dla tego przedstawienia.
    private String getSeatingPlanUrl(Show show) {
        return seatingPlansLocation + URL_PATH_SEPERATOR + show.getSeatingPlan().getId() + STATIC_CONTENT_SUFFIX;
    }

    // zwraca listę wszystkich dostępnych kategorii miejsc.
    private SeatClass[] getSeatClasses(PerformanceWithAvailability[] performances) {
        Set seatClasses = new HashSet();
        for (int i=0; i<performances.length; i++) {
            PerformanceWithAvailability performance = performances[i];
            List priceBands = performance.getPriceBandWithAvailability();
            for (Iterator iter = priceBands.iterator(); iter.hasNext();) {
                PriceBandWithAvailability priceBand = (PriceBandWithAvailability)iter.next();
                seatClasses.add(priceBand.getSeatClass());
            }
        }
        return (SeatClass[])seatClasses.toArray(new SeatClass[seatClasses.size()]);
    }
}
