package com.packtpub.hibernatesearch.domain;

import java.io.Serializable;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.ManyToMany;

import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;
import org.hibernate.search.annotations.Analyze;
import org.hibernate.search.annotations.Field;
import org.hibernate.search.annotations.Fields;
import org.hibernate.search.annotations.Indexed;
import org.hibernate.search.annotations.IndexedEmbedded;

/**
 * Prosta klasa encji reprezentująca aplikację komputerową ... z nazwą, długim opisem 
 * i nazwą pliku użytego jako ikona. Klasa posiada flagę "active" określającą, czy dana aplikacja
 * ma być wyszukiwalna.
 * 
 * Adnotacja @Entity nakazuje Hibernate by zmapowało klasę do tabeli w bazie danych, podczas gdy adnotacja @Indexed 
 * nakazuje narzędziu Hibernate Search by zmapowało ją do indeksu Lucene. 
 */
@Entity
@Indexed
public class App implements Serializable {

	private static final long serialVersionUID = 1L;

	/**
	 * Klucz główny, skonfigurowany tak, by był generowany automatycznie podczas tworzenia nowej instancji
	 */
	@Id
	@GeneratedValue
	private Long id;

	/**
	 * Nazwa aplikacji zrozumiała dla ludzi. Adnotacja @Column informuje Hibernate by zmapował to pole do 
	 * kolumny w bazie danych.
	 * 
	 * Obecnie używamy dwóch adnotacji @Field w celu dwukrotnego indeksowania... Pierwsze z przetworzonymi (rozbitymi) 
	 * wartościami dla łatwiejszego wyszukiwania, drugie nieprzetworzone (nie rozbite) aby na podstawie ich wartości 
	 * sortować wyniki.
	 */
	@Column
	@Fields({
		@Field,
		@Field(name="sorting_name", analyze=Analyze.NO)
	})
	private String name;

	/**
	 * Dłuższy, bardziej szczegółowy opis aplikacji. Adnotacja @Column informuje Hibernate by zmapował to pole do 
	 * kolumny w bazie danych, natomiast adnotacja @Field nakazuje Hibernate Search by zmapował je jako pole  
	 * indeksie Lucene.
	 */
	@Column(length = 1000)
	@Field
	private String description;

	/**
	 * Nazwa pliku obrazka mającego być powiązanym z aplikacją. Spodziewamy się, że plik będzie istniał w podkatalogu 
	 * "images/apps/<obrazek>".  To pole nie ma adnotacji @Field, ponieważ nie planujemy przeszukiwania aplikacji pod  
	 * kątem nazw plików obrazków.
	 */
	@Column
	private String image;
	
	/**
	 * Flaga określająca, czy aplikacja ma być wyszukiwalna.
	 */
	@Column
	private boolean active;

	/**
	 * Kolekcja powiązanych encji typu Device, reprezentujących urządzenia na których można uruchomić aplikację.
	 * 
	 * Adnotacja @ManyToMany informuje Hibernate, że aplikacja może być uruchamiana na wielu urządzeniach, oraz że
	 * jedno urządzenie może uruchamiać wiele aplikacji  
	 * 
	 *      Element "fetch" ma ustawioną wartość "eager" (zamiast standardowej "lazy") aby wszystkie interesujące nas obiekty typu 
	 *      Device zostały pobrane jednocześnie dopóki sesja Hibernate jest otwarta. W innym przypadku pojawiłyby się błędy gdyby 
	 *      warstwa widoku spróbowała dostać się do tych pól po zamknięciu sesji Hibernate. Zazwyczaj chciwe pobieranie jest mniej  
	 *      wydajne od leniwego. W większej aplikacji powinieneś się zastanowić nad innym podejściem, np. użyciem wzorca DAO. 
	 * 
	 *      Element "cascade" zapewnia, że zmiany po dowolnej stronie relacji App-Device będą poprawnie odzwierciedlone w indeksach 
	 *      Lucene obu encji.
	 * 
	 * Adnotacja @IndexedEmbedded informuje Hibernate Search by indeksował powiązane encje.  
	 * Klasa Device może (ale nie musi) posiadać indeks Lucene (w zależności od tego czy klasa jest adnotowana @Indexed), 
	 * ale i tak instancje Device powiązane z aplikacją będą przechowywane w indeksie Lucene klasy App. 
	 */
	@ManyToMany(fetch=FetchType.EAGER, cascade = { CascadeType.ALL })
	@IndexedEmbedded(depth=1)
	private Set<Device> supportedDevices;
	
	/**
	 * Kolekcja wbudowanych obiektów prezentujących opinie użytkowników o aplikacji. Wbudowane obiekty różnią się od powiązanych encji 
	 * tym, że z chwilą skasowania obiektu, z którym są powiązane one również zostają usunięte. 
	 * 
	 * Adnotacja @ElementCollection informuje Hibernate o powiązaniu z wieloma obiektami wbudowanymi, w podobny sposób co adnotacja 
	 * @ManyToMany użyta dla "supportedDevices".  Element "fetch" ma ustawioną wartość "Eager" z tego samego powodu co w przypadku 
	 * "SupportedDevices".   
	 *
	 * Adnotacja @Fetch instruuje Hibernate by pobrało wbudowane obiekty używając wielu poleceń SELECT zamiast jednego rozbudowanego 
	 * JOIN (domyślnego zachowania).  Takie podejście jest niewydajne dla dużych zbiorów danych ... ale spodziewamy się, że pojedyńcza 
	 * aplikacja nie będzie miała zbyt wielu komentarzy. Dzięki temu podejściu unikniemy zwrócenia przez Hibernate zduplikowanych 
	 * komentarzy (http://stackoverflow.com/questions/1093153/hibernate-collectionofelements-eager-fetch-duplicates-elements). Jak  
	 * wspomniano w komentarzu do "supportedDevices", w większych aplikacjach zastanów się nad refaktoringiem rozwiązania tak, aby 
	 * uniknąć zachłannego pobierania obiektów.
	 * 
	 * Adnotacja @IndexEmbedded ma zdefiniowany opcjonalny element "includePaths" by wskazać, które pola wbudowanych obiektów 
	 * będą indeksowane w encji nadrzędnej.  Innymi słowy pole "comments" będzie jedyną wartością klasy CustomerReview dołączoną
	 * do indeksu Lucene klasy App.    
	 */
	@ElementCollection(fetch=FetchType.EAGER)
	@Fetch(FetchMode.SELECT)
	@IndexedEmbedded(depth=1, includePaths = { "comments" })
	private Set<CustomerReview> customerReviews;

	/**
	 * Domyślny pusty konstruktor.
	 */
	public App() {
	}

	/**
	 * Wygodny konstruktor, ustawiający zawartość wszystkich pól w jednym kroku.  
	 * 
	 * Parametr "active" jest domyślnie ustawiony na wartość "true".  Aby dezaktywować aplikację,
	 * użyj settera dla tego pola. 
	 */
	public App(String name, String image, String description) {
		this.name = name;
		this.image = image;
		this.description = description;
		this.active = true;
	}

	//
	// GETTERY I SETTERY
	//
	
	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String getDescription() {
		return description;
	}

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

	public String getImage() {
		return image;
	}

	public void setImage(String image) {
		this.image = image;
	}

	public Set<Device> getSupportedDevices() {
		return supportedDevices;
	}

	public void setSupportedDevices(Set<Device> supportedDevices) {
		this.supportedDevices = supportedDevices;
	}

	public boolean isActive() {
		return active;
	}

	public void setActive(boolean active) {
		this.active = active;
	}

	public Set<CustomerReview> getCustomerReviews() {
		return customerReviews;
	}

	public void setCustomerReviews(Set<CustomerReview> customerReviews) {
		this.customerReviews = customerReviews;
	}

}
