package com.armundia.core.startup;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.enterprise.context.ApplicationScoped;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.Query;

import com.armundia.arm_am.entity.ApDom;
import com.armundia.arm_am.entity.ApDomRole;
import com.armundia.arm_am.entity.ApLang;
import com.armundia.arm_am.services.ApLangApp;
import com.armundia.arm_am.services.ApLangCache;
import com.armundia.arm_am.services.DominiApp;
import com.armundia.arm_am.services.DominiCache;
import com.armundia.arm_am.services.DominiCache.DomainContainer;
import com.armundia.arm_am.services.Dominio;
import com.armundia.arm_am.services.DominioGroup;
import com.armundia.arm_am.services.IOpzioni;
import com.armundia.arm_am.services.OpzioniApp;
import com.armundia.arm_am.services.i18n.I18N;
import com.armundia.generic.util.DateUtil;
import com.armundia.v2.shared.datetime.TempoDiElaborazione;

@ApplicationScoped
public class DominiCacheMaintenance {
	// DominiCacheMaintenance deve essere caricato DOPO ApLangCacheMaintenance
	// OpzioniCacheMaintenance deve essere caricato DOPO DominiCacheMaintainer

	// Ordine:
	// 1 ApLangCacheMaintenance
	// 2 DominiCacheMaintenance
	// 3 OpzioniCacheMaintenance

	private static Logger log = Logger.getLogger(DominiCacheMaintenance.class.getName());

	@PersistenceContext
	protected EntityManager em;

	private Date timeCheck;

	// Stringhe ripetute (correzione sonar)
	public static final String ANAG = "RS_ANAG";

	@PostConstruct
	public void initialize() {
		loadApDomRoles();
		loadAllDomains();
	}

	public void reload() {
		// la sostituzione dovrebbe essere atomica
		// FIXME eventuiali domini cancellati rimangono in memoria.
		// DominiApp.getInstance().getCachedDomains().clear();
		initialize();
	}

	public void updateCacher() {
		Date actualTimeCheck = timeCheck;

		if (actualTimeCheck == null) {
			actualTimeCheck = DateUtil.addHours(new Date(), -6);
		}

		log.info("UPDATE CACHE DOMINI " + actualTimeCheck);

		Query q = em.createNamedQuery("ApDom.getChange");
		q.setParameter("timeStampCheck", actualTimeCheck);
		q.setMaxResults(5000);

		// Lista con codici dominio aggiornati
		// [0] = codice del dominio
		// [1] = dataagg
		@SuppressWarnings("unchecked")
		List<Object[]> retObj = q.getResultList();

		// invalido gli oggetti non buoni

		if (!retObj.isEmpty()) {
			log.log(Level.WARNING, "RIMNUOVO: " + retObj.size() + " DOMINI DALLA CACHE");

			initialize();

			for (Object[] op : retObj) {
				if (((Date) op[1]).compareTo(actualTimeCheck) > 0) {
					actualTimeCheck = (Date) op[1];
				}
			}
		}

		q = em.createNamedQuery("ApDomRole.getChange");
		q.setParameter("timeStampCheck", actualTimeCheck);

		Date d = (Date) q.getSingleResult();
		if (d != null) {
			loadApDomRoles();
		}

		actualTimeCheck = DateUtil.addSeconds(actualTimeCheck, 1);

		timeCheck = actualTimeCheck;
	}

	public void loadAllDomains() {
		log.info("loadAllDomains loading .... ");
		Date dataInizio = new Date();

		// rel. 3.0.2
		// determino le lingue configurate su AP_DOM ...
		Query q = em.createQuery("select (d.id.valCampo) from ApDom d where d.id.codCampo='LANG'");
		@SuppressWarnings("unchecked")
		List<String> lingue = q.getResultList();

		log.info("LINGUE trovate in AP_DOM " + lingue.size());

		if (lingue.size() == 0) {
			lingue.add(I18N.getDefaultLang());
		}

		for (String lingua : lingue) {
			loadDomain(lingua);
		}
		log.info("loadAllDomains [Tempo di Elaborazione]: "
				+ TempoDiElaborazione.calcolaTempoDiElaborazione(dataInizio, new Date()));

	}

	private void loadDomain(String lang) {
		log.info(String.format("loadDomian [LANG] %s", lang));

		Date dataInizio = new Date();

		HashMap<String, DomainContainer> cachedDomains = DominiApp.getInstance().getCachedDomains();

		if (cachedDomains == null) {
			cachedDomains = new HashMap<String, DominiCache.DomainContainer>();
		}

		DomainContainer dc = cachedDomains.get(lang);
		if (dc == null) {
			dc = DominiApp.getInstance().new DomainContainer();
			cachedDomains.put(lang, dc);
		}

		DominioGroup domGrp = new DominioGroup();
		List<Dominio> found = new ArrayList<>();

		// Lista di tutti i codici dominio
		Query q = em.createQuery("select distinct(d.id.codCampo) from ApDom d");
		@SuppressWarnings("unchecked")
		List<String> codici = q.getResultList();

		log.info("TROVATI " + codici.size() + " DOMINI per lingua " + lang);

		for (String codice : codici) {
			domGrp.addDom(codice);
		}

		domGrp.loadDb(em, true, true, true);

		IOpzioni opt = OpzioniApp.getInstance();
		String emptyDomain = opt.getString("EMPTYDOMAIN", ANAG);
		if (emptyDomain == null)
			emptyDomain = "";

		HashMap<String, Dominio> cd = new HashMap<>();

		for (Dominio d : domGrp.getHashDom()) {
			d = translateDomain(d, lang, emptyDomain);
			Collections.sort(d.getListValori(), new ApDomComparator());
			cd.put(d.getCodCampo(), d);
		}
		dc.setCachedDomains(cd);

		for (Dominio d : found) {
			domGrp.addDomain(d.getCodCampo(), d);
		}

		DominiApp.getInstance().setCachedDomains(cachedDomains);
		log.info(String.format("loadDomian completed [LANG] %s [Tempo di Elaborazione] %s", lang,
				TempoDiElaborazione.calcolaTempoDiElaborazione(dataInizio, new Date())));
	}
	
	public static class ApDomComparator implements Comparator<ApDom> {
		public int compare(ApDom d1, ApDom d2) {
			// TODO l'if non funziona: da NullPointerException se uno dei due domini è null
			// if (d1 == null || d2 == null)
			//	return d1.getId().getValCampo().compareTo(d2.getId().getValCampo());
			Short o1 = d1.getOrdVis() == null ? 0 : d1.getOrdVis();
			Short o2 = d2.getOrdVis() == null ? 0 : d2.getOrdVis();
			return o1.compareTo(o2);
		}
	}

	protected void loadApDomRoles() {
		log.info("CARICAMENTO APDOM ROLES");
		HashMap<String, List<ApDomRole>> mapDomainsRoles = new HashMap<>();

		IOpzioni opt = OpzioniApp.getInstance();
		Boolean loadApDomRole = opt.getBoolean("LOAD_AP_DOM_ROLES", ANAG);
		if (loadApDomRole != null && loadApDomRole) {
			Query q = em.createNamedQuery("ApDomRole.getAll");
			@SuppressWarnings("unchecked")
			List<ApDomRole> apdomroles = q.getResultList();
			for (ApDomRole adr : apdomroles) {
				if (mapDomainsRoles.get(adr.getApDomId().getCodCampo()) == null) {
					mapDomainsRoles.put(adr.getApDomId().getCodCampo(), new ArrayList<ApDomRole>());
				}
				List<ApDomRole> rolekeys = mapDomainsRoles.get(adr.getApDomId().getCodCampo());
				rolekeys.add(adr);
			}
		}

		DominiApp.getInstance().setMapDomainsRoles(mapDomainsRoles);
	}

	/**
	 * Traduce il dominio nella lingua impostata ed ordina i suoi campi di
	 * conseguenza.
	 * 
	 * @param d
	 * @param lang
	 * @param emptyDomain
	 * @return
	 */
	public Dominio translateDomain(Dominio d, String lang, String emptyDomain) {
		ApLangCache aplangc = ApLangApp.getInstance();
		for (ApDom apd : d.getListValori()) {
			String codMl = apd.getCodMl();

			if (codMl == null) {
				codMl = apd.getId().getCodCampo() + "@" + apd.getId().getValCampo();
			}

			String newdesc = aplangc.getMsg(codMl, lang);

			if (newdesc != null && apd.getDesDom() != null && emptyDomain.compareTo(apd.getDesDom()) != 0) {

				ApLangApp.getInstance().addLangList(codMl, apd.getDesDom(), ApLang.DOMAIN);

				boolean evidenziaLabelMancanti = OpzioniApp.getInstance().getBoolean("EVIDENZIA_LABEL_MANCANTI", "I18N",
						false);
				if (evidenziaLabelMancanti) {
					newdesc += "*";
				}

			}

			boolean evidenziaI18n = OpzioniApp.getInstance().getBoolean("EVIDENZIA_I18N", "I18N", false);
			if (evidenziaI18n) {
				newdesc += "#";
			}
			if (newdesc != null) {
				apd.setDesDom(newdesc);
			}
		}

		d.setCodLang(lang);

		return d;
	}

	@PreDestroy
	public void destroy() {
		DominiApp.destroy();
	}
}
