package org.springframework.prospring.ticket.dao.hibernate;

import java.math.*;
import java.util.*;

import org.springframework.orm.hibernate3.support.*;
import org.springframework.prospring.ticket.dao.*;
import org.springframework.prospring.ticket.domain.*;
import org.springframework.prospring.ticket.domain.support.*;
import org.springframework.prospring.ticket.service.*;

/**
 * Implementacja Hibernate BoxOfficeDao.
 */
public class HibernateBoxOfficeDao extends HibernateDaoSupport implements BoxOfficeDao {

    /**
     * Zaznacza podane miejsca jako zarezerwowane (dla danego spektaktu i rezerwacji).
     * @param seats Miejsca które należy zaznaczyć jako zarezerwowane.
     * @param performance Spektakl na jaki miejsca są rezerwowane.
     * @param booking Rezerwacja w ramach której miejsca są rezerwowane.
     * @throws org.springframework.prospring.ticket.service.RequestedSeatNotAvailableException
     *         Zgłaszany jeśli którekolwiek z miejsc nie jest już dostępne, czyli 
     *         jeśli zostały wcześniej zarezerwowane.
     */
    public void reserveSeats(Seat[] seats, Performance performance, Booking booking)
        throws RequestedSeatNotAvailableException {

        getHibernateTemplate().save(booking);
        List seatStats = getHibernateTemplate().findByNamedQueryAndNamedParam(
            "getSeatStatusForSeatsInPerformance",
            new String[] {"perfId", "ids"},
            new Object[] {new Long(performance.getId()), Arrays.asList(seats)}
        );
        for (Iterator iter = seatStats.iterator(); iter.hasNext();) {
            SeatStatus seatStatus = (SeatStatus)iter.next();
            if (seatStatus.getBooking() != null && (seatStatus.getBooking().getPurchase() != null ||
                seatStatus.getBooking().getReservedUntil().after(new Date()))) {

                throw new RequestedSeatNotAvailableException("Miejsce " + seatStatus.getSeat().getName() +
                    " nie są dostępne dla spektaklu ", performance);
            }
            seatStatus.setBooking(booking);
            getHibernateTemplate().update(seatStatus);
        }
    }

    /**
     * Zwraca listę wszystkich spektakli dla danego przedstawienia. Zwrócne spektakle zawierają
     * informacje od dostępnych miejscach.
     * @param show Przedstawienie.
     * @return Lista wszystkich spektakli (wraz z dostępnymi miejscami) danego przedstawienia.
     */
    public PerformanceWithAvailability[] getAvailabilityForPerformances(Show show) {

        Map performanceById = new HashMap();

        List availabilities = getAvailabilityDetails(show.getId());

        for (Iterator iter = availabilities.iterator(); iter.hasNext();) {
            AvailabilityDetail availability = (AvailabilityDetail)iter.next();

            Performance performance = availability.getPerformance();
            PerformanceWithAvailability performanceWithAvailability =
                (PerformanceWithAvailability)performanceById.get(new Long(performance.getId()));
            if (performanceWithAvailability == null) {
                performanceWithAvailability = new PerformanceWithAvailability(performance);
                performanceById.put(new Long(performance.getId()), performanceWithAvailability);
            }

            PriceBand priceBand = availability.getPriceBand();
            PriceBandWithAvailability priceBandWithAvailability =
                new PriceBandWithAvailability(priceBand, availability.getAvailableSeatCount());
            performanceWithAvailability.add(priceBandWithAvailability);
        }

        return (PerformanceWithAvailability[])performanceById.values().toArray(
            new PerformanceWithAvailability[performanceById.size()]);
    }

    // metoda pomocnicza służąca do pobrania szczegółowych informacji o miejscach
    // dostępnych na spektakle danego przedstawienia.
    private List getAvailabilityDetails(long showId) {

        List availabilities = getHibernateTemplate().findByNamedQueryAndNamedParam(
            "getPerformancesWithAvailability",
            new String[] { "showId", "date" },
            new Object[] { new Long(showId), new Date() }
        );

        List details = new ArrayList(availabilities.size());
        for (Iterator iter = availabilities.iterator(); iter.hasNext();) {
            Object[] values = (Object[])iter.next();
            AvailabilityDetail detail = new AvailabilityDetail();
            detail.setAvailableSeatCount(((Integer)values[0]).intValue());
            detail.setPriceBand((PriceBand)values[1]);
            detail.setPerformance((Performance)values[2]);
            details.add(detail);
        }

        return details;
    }


    /**
     * Zwraca listę wszystkich dostępnych miejsc danej kategorii dla podanego spektaklu.
     * @param performance Spektakl.
     * @param seatClass   Kategoria miejsc.
     * @return Lista wszystkich dostępnych miejsc podanej kategorii na wskazany spektakl.
     */
    public Seat[] getAvailableSeats(Performance performance, SeatClass seatClass) {
        List seats = getHibernateTemplate().findByNamedQueryAndNamedParam(
            "getAvailableSeats",
            new String[] { "performance", "seatClass", "date"},
            new Object[] { performance, seatClass, new Date()}
        );

        return (Seat[])seats.toArray(new Seat[seats.size()]);
    }

    /**
     * Zwraca ilość dostępnych miejsc danej kategorii na wskazany spektakl.
     * @param performance Spektakl.
     * @param seatClass Kategoria  miejsc.
     * @return Zwraca ilość dostępnych miejsc konkretnej kategorii na wskazany spektakl.
     */
    public int getAvailableSeatsCount(Performance performance, SeatClass seatClass) {
        List result = getHibernateTemplate().findByNamedQueryAndNamedParam(
            "getAvailableSeatsCount",
            new String[] { "performance", "seatClass", "date"},
            new Object[] { performance, seatClass, new Date()}
        );

        return ((Integer)result.get(0)).intValue();
    }

    /**
     * Zwraca koszt konkretnych miejsc na podany spektakl.
     * @param performance Spektakl.
     * @param seats       Miejsca.
     * @return Koszt wskazanych miejsc na konkretny spektakl.
     */
    public BigDecimal getCostOfSeats(Performance performance, Seat[] seats) {
        List price = getHibernateTemplate().findByNamedQueryAndNamedParam(
                "getCostOfSeats",
                new String[] { "performance", "seats"},
                new Object[] { performance , Arrays.asList(seats)}
        );
        return (BigDecimal)price.get(0);
    }

    /**
     * Usuwa wskazaną rezerwację z bazy danych.
     * @param booking Rezerwacja jaką należy usunąć z bazy danych.
     */
    public void removeBooking(Booking booking) {

        // powiązanie konkretnego obiektu rezerwacji z sesja Hibrenate.
        getHibernateTemplate().refresh(booking);

        List seatStats = getHibernateTemplate().findByNamedQueryAndNamedParam(
            "getSeatStatusForBooking",
            new String[] {"booking"},
            new Object[] { booking }
        );

        for (Iterator iter = seatStats.iterator(); iter.hasNext();) {
            SeatStatus seatStatus = (SeatStatus)iter.next();
            seatStatus.setBooking(null);
        }

        getHibernateTemplate().delete(booking);

    }

    /**
     * Zwraca rezerwację skojarzoną z podanym identyfikatorem.
     * @param bookingId Identyfikator poszukiwanej rezerwacji.
     * @return Rezerwacja skojarzona z podanym identyfikatorem.
     */
    public Booking getBooking(long bookingId) {
        return (Booking)getHibernateTemplate().get(Booking.class, new Long(bookingId));
    }

    /**
     * Zwraca miejsca skojarzone z podaną rezerwacją.
     * @param booking Rezerwacja.
     * @return Miejsca skojarzone z podaną rezerwacją.
     */
    public List getSeatsForBooking(Booking booking) {
        return getHibernateTemplate().findByNamedQueryAndNamedParam(
            "getSeatsForBooking",
            new String[] { "booking" },
            new Object[] { booking }
        );
    }

    /**
     * Zwraca spektakl skojarzony z podaną rezerwacją.
     * @param booking Rezerwacja.
     * @return Spektakl skojarzony z podaną rezerwacją.
     */
    public Performance getPerformanceForBooking(Booking booking) {
        List result = getHibernateTemplate().findByNamedQueryAndNamedParam(
            "getPerformanceForBooking",
            new String[] { "booking" },
            new Object[] { booking }
        );
        return (result.isEmpty()) ? null : (Performance)result.get(0);
    }

    /**
     * Sprawdza dostępność miejsc skojarzonych z daną rezerwacją.
     * @param booking Sprawdzana rezerwacja.
     * @return True jeśli miejsca są dostępne, false w przeciwnym przypadku.
     */
    public boolean checkAvailabilityForBooking(Booking booking) {
        List seats = getSeatsForBooking(booking);
        return seats.size() == booking.getSeatCount();
    }

    /**
     * Aktualizuje kod uwierzytelniający dla danego zakupu.
     * @param purchase Aktualizowana rezerwacja.
     */
    public void updatePurchaseAuthorizationCode(Purchase purchase) {
        getHibernateTemplate().update(purchase);
    }

    /**
     * Zwraca wszystkie kategorie przedstawień zapisane w bazie danych.
     *
     * @return Wszystkie kategorie przedstawień zapisane w bazie danych.
     */
    public List getAllGenres() {
        return getHibernateTemplate().find("from " + Genre.class.getName() + " g order by g.name");
    }

    /**
     * Zwraca wszystkie kategorie dla których w bazie są jakieś spektakle.
     * @return Wszystkie kategorie dla których w bazie są jakieś spektakle.
     */
    public List getCurrentGenres() {
        return getHibernateTemplate().findByNamedQuery("currentGenres");
    }

    /**
     * Zwraca przedstawienie skojarzone z podanym identyfikatorem.
     * @param id Identyfikator poszukiwanego przedstawienia.
     * @return Przedtawienie skojarzone z podanym identyfikatorem.
     */
    public Show getShow(long id) {
        return (Show)getHibernateTemplate().get(Show.class, new Long(id));
    }

    /**
     * Zwraca spektakl skojarzony z podanym identyfikatorem.
     * @param id Identyfikator poszukiwanego spektaklu.
     * @return Spektakl skojarzony z podanym identyfikatorem.
     */
    public Performance getPerformance(long id) {
        return (Performance) getHibernateTemplate().load(Performance.class, new Long(id));
    }

    /**
     * Zwraca zakres cen skojarzony z podanym identyfikatorem.
     * @param id Identyfikator poszukiwanego zakresu cen.
     * @return Zakres cen skojarzony z podanym identyfikatorem.
     */
    public PriceBand getPriceBand(long id) {
        return (PriceBand) getHibernateTemplate().load(PriceBand.class, new Long(id));
    }

    /**
     * Zwraca strukturę cen skojarzoną z podanym identyfikatorem.
     * @param id Identyfikator poszukiwanej struktury cen.
     * @return Struktura cen skojarzona z poszukiwanym identyfikatorem.
     */
    public PriceStructure getPriceStructure(long id) {
        return (PriceStructure)getHibernateTemplate().get(PriceStructure.class, new Long(id));
    }

    /**
     * Zapisuje przekazaną rezerwację w bazie danych.
     * @param booking Rezerwacja jaką należy zapisać w bazie.
     */
    public void savePurchaseForBooking(Booking booking) {
        getHibernateTemplate().update(booking);
    }

}
