package com.packtpub.hibernatesearch.startup;

import java.util.Arrays;
import java.util.HashSet;

import javax.servlet.ServletContextEvent;

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cfg.Configuration;
import org.hibernate.search.FullTextSession;
import org.hibernate.search.Search;
import org.hibernate.service.ServiceRegistry;
import org.hibernate.service.ServiceRegistryBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.packtpub.hibernatesearch.domain.App;
import com.packtpub.hibernatesearch.domain.CustomerReview;
import com.packtpub.hibernatesearch.domain.Device;

/**
 * Klasa pomocnicza dla instancji uruchamianej jako węzeł nadrzędny w klastrze. Wyłącznie węzeł nadrzędny może bezpośrednio 
 * aktualizować nadrzędne indeksy Lucene.  Węzły podrzędne mogą aktualizować indeksy wyłącznie przesyłając wiadomości przez
 * JMS do węzła nadrzędnego, gdzie są następnie przetwarzane.
 * 
 * Dzięki implementacji interfejsu ServletContextListener, kontener servletów autoamtycznie wywoła metodę "contextInitialized" podczas
 * startu.  Wewątrz metody utworzymy i zindeksujemy około połowy danych testowych (tak aby zademonstować utworzenie drugiej połowy 
 * przez węzeł podrzędny).  Klasa tworzy osobny wątek monitorujący kolejkę JMS i wykonujący aktualizacje Lucene na podstawie 
 * wiadomości przesyłanych przez węzły podrzędne. 
 * 
 * Poprzednie wersje tej klasy (nazywane "StartupDataLoader") miały adnotację @WebListener. Niemniej jednak w tej wersji 
 * polegamy na pliku "web.xml" definiującym właściwy listener... ponieważ nie jest wskazane, żeby aplikacja jednocześnie 
 * uruchomiła MasterNodeInitializer *oraz* SlaveNodeInitializer.
 */
public class MasterNodeInitializer implements javax.servlet.ServletContextListener {
	
	Logger logger = LoggerFactory.getLogger(MasterNodeInitializer.class);
	
	/**
	 * Nie powinna być używana bezpośrednio. Użyj openSession().
	 */
	private static SessionFactory sessionFactory;
	
	/**
	 * Konstruowanie nowej SessionFactory Hibernate dla każdego żądania HTTP powodowałoby problemy wydajnościowe. Ponieważ
	 * servlety Javy muszą być współbieżne, nie możemy zadeklarować by SessionFactory była publiczną statyczną zmienną.
	 * Ta metoda udostępnia współbieżny dostęp do SessionFactory, dzięki czemu metoda zapełniająca bazę danych przykładowymi danymi 
	 * i servlet wyszukujący mogą otwierać połączenia do bazy danych bardziej wydajnie. 
	 * 
	 * @return Session
	 */
	public static synchronized Session openSession() {
		if(sessionFactory == null) {
			Configuration configuration = new Configuration();
			configuration.configure();
			ServiceRegistry serviceRegistry = new ServiceRegistryBuilder().applySettings(configuration.getProperties()).buildServiceRegistry();
			sessionFactory = configuration.buildSessionFactory(serviceRegistry);			
		}
		return sessionFactory.openSession();
	}
	
	public void contextInitialized(ServletContextEvent event) {
		
		event.getServletContext().setAttribute("mode", "master");

		// W celach demonstracyjnych utworzomy połowę danych podczas startu węzła nadrzędnego... a drugą połowę podczas startu węzła 
		// podrzędnego.  Po kilku sekundach oba węzły odświeżą lokalne kopie swoich indeksów używając nadrzędnego indeksu...  
		// i wszystkie encje App będą wyszukiwalne na wszystkich węzłach.  
		FullTextSession fullTextSession = Search.getFullTextSession( openSession() );
		fullTextSession.beginTransaction();
		
		//
		// Utwórz 5 urządzeń
		//
		Device xPhone = new Device("Orange", "xPhone", null);
		Device xTablet = new Device("Orange", "xTablet", null);
		Device solarSystem = new Device("Song-Sung", "Solar System Phone", null);
		Device flame = new Device("Jungle", "Flame Book Reader", null);
		Device pc = new Device(null, "Pecet", null);
		
		//
		// Utwórz i zapisz 6 z 12 aplikacji razem z ich urządzeniami i komentarzami
		//
		App theCloud = new App(
				"Chmura!", 
				"cloud.jpg", 
				"Aplikacja Chmura! jest miejscem, w którym dzieją się cuda. Firmy działają w chmurach. Programiści nie potrzebują administratorów, jedzenia i picia w zasadzie też nie. Możesz oglądać telewizję na tablecie, siedzać na kanapie; nie musisz w ogóle patrzeć na telewizor! Ściągnij aplikację Chmura! z chmury i doświadcz tej cudownej potęgi!",
				"Biznes",
				7.99f);
		theCloud.setSupportedDevices( new HashSet<Device>(Arrays.asList(new Device[] { xPhone, xTablet })) );
		CustomerReview theCloudReview1 = new CustomerReview("fanboy1984", 5, "Ta aplikacja sprawia, że mój xPhone jest jeszcze bardziej stylowy i trendy!");
		CustomerReview theCloudReview2 = new CustomerReview("anty.hipster", 1, "Nie rozumiem o co chodzi z tą 'Chmurą'.  Dla mnie to raczej marketingowy bełkot...");
		theCloud.setCustomerReviews( new HashSet<CustomerReview>(Arrays.asList(new CustomerReview[] { theCloudReview1, theCloudReview2 })) );
		fullTextSession.save(theCloud);
		logger.info("Zapisuję " + theCloud.getName());

		App salesCloser = new App(
				"Zamykacz transakcji", 
				"pointing.jpg", 
				"Wysokoenergetyczna aplikacja wspomagająca energicznych sprzedawców. Twórz pełne energii kalendarze i arkusze. Gdy jesteś w mieście i energicznie nawiązujesz kontakty, chcesz prezentować swe naenergetyzowane foldery, pokazujące, że też masz w sobie mnóstwo energii.",
				"Biznes",
				5.99f);
		salesCloser.setSupportedDevices( new HashSet<Device>(Arrays.asList(new Device[] { xPhone, solarSystem })) );
		CustomerReview salesCloserReview = new CustomerReview("GdzieJestKasa", 5, "Świetna aplikacja!  Jeżeli używałeś kiedyś 'Sales Commandera 2000', szybko połapiesz się w tym interfejsie.");
		salesCloser.setCustomerReviews( new HashSet<CustomerReview>(Arrays.asList(new CustomerReview[] { salesCloserReview })) );
		fullTextSession.save(salesCloser);
		logger.info("Zapisuję " + salesCloser.getName());

		App football = new App(
				"Międzynarodowy turniej piłkarski", 
				"ball.jpg", 
				"Ta mała aplikacja oferuje Ci radość gry w piłkę nożną, poza tym, że grasz na ekranie dotykowym zamiast biegać i kopać albo w ogóle się ruszać. Poza tym daje Ci podobne wrażenia.",
				"Gry",
				1.99f);
		football.setSupportedDevices( new HashSet<Device>(Arrays.asList(new Device[] { xTablet, flame })) );
		CustomerReview footballReview = new CustomerReview("PrawdziwyAmerykanin", 2, "Podpucha... To nie jest amerykański futbol!");
		football.setCustomerReviews( new HashSet<CustomerReview>(Arrays.asList(new CustomerReview[] { footballReview })) );
		fullTextSession.save(football);
		logger.info("Zapisuję " + football.getName());
		
		App crystal = new App(
				"Kolejna gra o kryształach", 
				"brilliant.jpg", 
				"Olśniewająca aplikacja, w której łączysz kryształy tego samego koloru, by zniknęły. Coś jak Tetris. W sumie przypomina tuzin innych gier, w których łączysz kryształy tego samego koloru.",
				"Gry",
				0.99f);
		crystal.setSupportedDevices( new HashSet<Device>(Arrays.asList(new Device[] { flame, pc })) );
		CustomerReview crystalReview = new CustomerReview("KolejnyGracz", 3, "czemu ta gra jest dostępna tylko na dwóch urządzeniach?  Z tuzin klonów tej gry jest dostępny dla wszystkich urządzeń.  Powinniście deaktywować tą aplikację do czasu, gdy wszystkie urządzenia będą wspierane...");
		crystal.setCustomerReviews( new HashSet<CustomerReview>(Arrays.asList(new CustomerReview[] { crystalReview })) );
		crystal.setActive(false);
		fullTextSession.save(crystal);
		logger.info("Zapisuję " + crystal.getName());
		
		App pencilSharpener = new App(
				"Temperówka", 
				"pencil.jpg", 
				"Ostrz ołówki, wkładając je do gniazda Bluetooth i wciskając przycisk. Ta aplikacja wyciska ze sprzętu wszystko!",
				"Biznes",
				2.99f);
		pencilSharpener.setSupportedDevices( new HashSet<Device>(Arrays.asList(new Device[] { xPhone, solarSystem })) );
		CustomerReview pencilSharpenerReview1 = new CustomerReview("brakuje.cyferek", 1, "Ta aplikacja jest niebezpieczna!  Myślę o pozwie.");
		CustomerReview pencilSharpenerReview2 = new CustomerReview("Prawnik", 5, "@brakuje.cyferek  Napisz do mnie.  Pogadamy...");
		pencilSharpener.setCustomerReviews( new HashSet<CustomerReview>(Arrays.asList(new CustomerReview[] { pencilSharpenerReview1, pencilSharpenerReview2 })) );
		fullTextSession.save(pencilSharpener);
		logger.info("Zapisuję " + pencilSharpener.getName());
		
		App staplerTracker = new App(
				"Namierzacz zszywaczy", 
				"stapler.jpg", 
				"Czy ktoś ciągle pożycza sobie Twój zszywacz? To typowy problem w biurach. Nasza aplikacja biznesowa sprawi, że już nigdy nie będziesz musiał się zastanawiać, gdzie podziewa się Twój zszywacz.",
				"Biznes",
				0.99f);
		staplerTracker.setSupportedDevices( new HashSet<Device>(Arrays.asList(new Device[] { pc })) );
		CustomerReview staplerTrackerReview = new CustomerReview("mike.bolton", 3, "'PC LOAD LETTER'?  O co w ogóle chodzi?!?");
		staplerTracker.setCustomerReviews( new HashSet<CustomerReview>(Arrays.asList(new CustomerReview[] { staplerTrackerReview })) );
		fullTextSession.save(staplerTracker);
		logger.info("Zapisuję " + staplerTracker.getName());
		
		//
		// Zamknij i wyczyść sesję Hibernate
		//
		fullTextSession.getTransaction().commit();
		fullTextSession.close();
		
		//
		// Utwórz wątek by śledzić zawartość kolejki JMS  Klasa "QueueMonitor" jest odpowiedzialna za monitorowanie 
		// i odbieranie wiadomości, podczas gdy klasa "QueueController" odpowiada za wykonywanie akutalizacji indeksów 
		// Lucene na podstawie danych w wiadomości.
		//
		QueueController queueController = new QueueController(sessionFactory.openSession());
		Thread queueMonitor = new Thread(new QueueMonitor(queueController)); 
		queueMonitor.start();
			
	} 

	/**
	 * Ta metoda jest wywoływana gdy mechanizm servletów jest wyłączany. Zamyka Hibernate SessionFactory jeżeli jeszcze jest otwarta.
	 */
	public void contextDestroyed(ServletContextEvent event) {
		if(!sessionFactory.isClosed()) {
			sessionFactory.close();
		}
	}
	
}
