package org.hibernate.auction.model;

import org.hibernate.auction.exceptions.*;

import java.io.Serializable;
import java.util.*;

/**
 * Sprzedawany przedmiot.
 *
 * @author Christian Bauer <christian@hibernate.org>
 *
 */
public class Item implements Serializable, Comparable, Auditable {

	private Long id = null;
	private int version;
	private String name;
	private User seller;
	private String description;
	private MonetaryAmount initialPrice;
	private MonetaryAmount reservePrice;
	private Date startDate;
	private Date endDate;
	private Set categorizedItems = new HashSet();
	private Collection bids = new ArrayList();
	private Bid successfulBid;
	private ItemState state;
	private User approvedBy;
	private Date approvalDatetime;
	private Date created = new Date();

	/**
	 * Konstruktor bezparametrowy dla narzędzi JavaBean.
	 */
	Item() {}

	/**
	 * Pełny konstruktor.
	 */
	public Item(String name, String description, User seller,
				MonetaryAmount initialPrice, MonetaryAmount reservePrice,
				Date startDate, Date endDate,
				Set categories, List bids, Bid successfulBid) {
		this.name = name;
		this.seller = seller;
		this.description = description;
		this.initialPrice = initialPrice;
		this.reservePrice = reservePrice;
		this.startDate = startDate;
		this.endDate = endDate;
		this.categorizedItems = categories;
		this.bids = bids;
		this.successfulBid = successfulBid;
		this.state = ItemState.DRAFT;
	}

	/**
	 * Prostszy konstruktor.
	 */
	public Item(String name, String description, User seller,
				MonetaryAmount initialPrice, MonetaryAmount reservePrice,
				Date startDate, Date endDate) {
		this.name = name;
		this.seller = seller;
		this.description = description;
		this.initialPrice = initialPrice;
		this.reservePrice = reservePrice;
		this.startDate = startDate;
		this.endDate = endDate;
		this.state = ItemState.DRAFT;
	}

	// ********************** Metody dostępowe ********************** //

	public Long getId() { return id; }

	public String getName() { return name; }
	public User getSeller() { return seller; }

	public String getDescription() { return description; }
	public void setDescription(String description) { this.description = description; }

	public MonetaryAmount getInitialPrice() { return initialPrice; }

	public MonetaryAmount getReservePrice() { return reservePrice; }

	public Date getStartDate() { return startDate; }

	public Date getEndDate() { return endDate; }

	public Set getCategorizedItems() { return categorizedItems; }
	public void addCategorizedItem(CategorizedItem catItem) {
		if (catItem == null)
			throw new IllegalArgumentException("Nie mogę dodać CategorizedItem o wartości null.");
		this.getCategorizedItems().add(catItem);
	}

	public Collection getBids() { return bids; }
	public void addBid(Bid bid) {
		if (bid == null)
			throw new IllegalArgumentException("Nie mogę dodać Bid o wartości null.");
		this.getBids().add(bid);
	}

	public Bid getSuccessfulBid() { return successfulBid; }
	public void setSuccessfulBid(Bid successfulBid) { this.successfulBid = successfulBid; }

	public ItemState getState() { return state; }

	public User getApprovedBy() { return approvedBy; }
	public void setApprovedBy(User approvedBy) { this.approvedBy = approvedBy; }

	public Date getApprovalDatetime() { return approvalDatetime; }
	public void setApprovalDatetime(Date approvalDatetime) { this.approvalDatetime = approvalDatetime; }

	public Date getCreated() { return created; }

	// ********************** Inne typowe metody ********************** //

	public boolean equals(Object o) {
		if (this == o) return true;
		if (!(o instanceof Item)) return false;

		final Item item = (Item) o;

		if (!created.equals(item.created)) return false;
		if (name != null ? !name.equals(item.name) : item.name != null) return false;

		return true;
	}

	public int hashCode() {
		int result;
		result = (name != null ? name.hashCode() : 0);
		result = 29 * result + created.hashCode();
		return result;
	}

	public String toString() {
		return  "Item ('" + getId() + "'), " +
				"Nazwa: '" + getName() + "' " +
				"Cena początkowa: '" + getInitialPrice()+ "'";
	}

	public int compareTo(Object o) {
		if (o instanceof Item) {
			return this.getCreated().compareTo( ((Item)o).getCreated() );
		}
		return 0;
	}

	// ********************** Metody biznesowe ********************** //

	/**
	 * Umieść ofertę przy zachowaniu reguł biznesowych.
	 *
	 * Metoda może zgłosić wyjątek BusinessException, jeśli jeden z wymogów
	 * umieszczenia oferty nie został spełniony (na przykład aukcja już się zakończyła).
	 *
	 * @param bidder
	 * @param bidAmount
	 * @param currentMaxBid  oferta o najwyższej kwocie
	 * @param currentMinBid  oferta o najniższej kwocie
	 * @return
	 * @throws BusinessException
	 */
	public Bid placeBid(User bidder, MonetaryAmount bidAmount,
	                    Bid currentMaxBid, Bid currentMinBid)
		throws BusinessException {

		// Sprawdź najwyższą ofertę (można zastosować i inny sposób obsługi).
		if (currentMaxBid != null && currentMaxBid.getAmount().compareTo(bidAmount) > 0) {
			throw new BusinessException("Oferta za niska.");
		}

		// Czy aukcja aktywna?
		if ( !state.equals(ItemState.ACTIVE) )
			throw new BusinessException("Aukcja jeszcze nie jest aktywna.");

		// Czy aukcja trwa?
		if ( this.getEndDate().before( new Date() ) )
			throw new BusinessException("Aukcja już się zakończyła.");

		// Utwórz nową ofertę.
		Bid newBid = new Bid(bidAmount, this, bidder);

		// Umieść ofertę w obiekcie Item.
		this.addBid(newBid);

		return newBid;
	}

	/**
	 * Każdy może ustawić aukcję w stan oczekiwania na zatwierdzenie.
	 */
	public void setPendingForApproval() {
		state = ItemState.PENDING;
	}

	/**
	 * Zatwierdź aukcje i uaktywnij ją.
	 *
	 * @param byUser
	 * @throws BusinessException
	 */
	public void approve(User byUser) throws BusinessException {

		if ( !byUser.isAdmin() )
			throw new PermissionException("Nie jesteś administratorem.");

		if ( !state.equals(ItemState.PENDING) )
			throw new IllegalStateException("To tylko szkic aukcji przedmiotowej.");

		state = ItemState.ACTIVE;
		approvedBy = byUser;
		approvalDatetime = new Date();
	}

}
