package com.armundia.core.startup;

import java.math.BigDecimal;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
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.ApOpzioni;
import com.armundia.arm_am.entity.ApOpzioniId;
import com.armundia.arm_am.services.IOpzioni;
import com.armundia.arm_am.services.OpzioniApp;
import com.armundia.arm_am.services.OpzioniCache;
import com.armundia.generic.util.DateUtil;
import com.armundia.v2.shared.exception.ArmundiaException;
import com.armundia.v2.shared.exception.ArmundiaUnsupportedLegacyCode;


@ApplicationScoped
public class OpzioniCacheMaintenance {
	private static Logger log = Logger.getLogger(OpzioniCacheMaintenance.class.getName());
	// DominiCacheMaintenance deve essere caricato DOPO ApLangCacheMaintenance
	// OpzioniCacheMaintenance deve essere caricato DOPO DominiCacheMaintainer

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

	public void reload() {
		log.log(Level.INFO, String.format("OpzioniCacheMaintenance [hashCode] %s reload ...", this.hashCode()));

		this.free();
		this.readAll();

		this.initialize();

	}
	
	@PersistenceContext
	protected EntityManager em;

	private Date timeCheck;
	//rel. 3.1.10 - FIX EJB, Enterprise Bean methods must not use thread synchronization primitive
	private static final Object staticObjLock = new Object();

	@PostConstruct
	public void initialize() {
		log.log(Level.FINE, String.format("OpzioniCacheMaintenanceBase [hashCode] %s initializing ...", this.hashCode()));

		Map<ApOpzioniId, ApOpzioni> hmOpzioni = new HashMap<>();
		OpzioniApp.getInstance().setHmOpzioni(hmOpzioni);
		readAll();

		log.log(Level.FINE, String.format("OpzioniCacheMaintenanceBase [hashCode] %s initialization completed.", this.hashCode()));
	}

	public Map<ApOpzioniId, ApOpzioni> hmOpzioni() {
		//rel. 3.1.10 - FIX EJB, Enterprise Bean methods must not use thread synchronization primitive
		synchronized (staticObjLock) {
		return OpzioniApp.getInstance().getHmOpzioni();
	}
	}

	//rel. 3.1.10 - FIX EJB, Enterprise Bean methods must not use thread synchronization primitive
	//public synchronized void free() {
	public void free() {
		this.hmOpzioni().clear();
	}

	@PreDestroy
	public void destroy() {
		OpzioniApp.destroy();
	}
	
	//rel. 3.1.10 - FIX EJB, Enterprise Bean methods must not use thread synchronization primitive
	//public synchronized void readAll() {
	public void readAll() {
		Query q = this.em.createNamedQuery("ApOpzioni.readAll");

		@SuppressWarnings("unchecked")
		List<ApOpzioni> l = q.getResultList();
		for (ApOpzioni opz : l) {
			this.hmOpzioni().put(opz.getId(), opz);
		}

		log.info(String.format("readAll - Trovate %d opzioni", this.hmOpzioni().size()));
	}

	public List<ApOpzioni> getOptionsByListaSezioni(List<String> sezioni) {
		String qlString = " select o from ApOpzioni o ";
		Query q;
		if (sezioni != null && !sezioni.isEmpty()) {
			qlString += "where o.id.sezione in (:sezioni) ";
			q = em.createQuery(qlString);
			q.setParameter("sezioni", sezioni);
		} else {
			q = em.createQuery(qlString);
		}

		@SuppressWarnings("unchecked")
		List<ApOpzioni> res = q.getResultList();
		return res;
	}

	public boolean setOpzione(String opzione, String utente, String sezione, String formato, Object valore) {
		return setOpzione(this.em, opzione, utente, sezione, formato, valore, false);
	}

	//setOpzione(String opzione,String utente,String sezione,String formato,Object valore)
	public boolean setOpzione(String opzione, String sezione, String formato, Object valore) {
		return this.setOpzione(opzione, null, sezione, formato, valore);
	}

	public boolean setOpzione(String opzione, String sezione, Object valore) {
		return this.setOpzione(opzione, null, sezione, null, valore);
	}

	public void updateCacher() {
		Date actualTimeCheck = timeCheck;

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

		log.fine("UPDATE CACHE OPZIONI " + actualTimeCheck);

		Query q = em.createNamedQuery("ApOpzioni.getChange");
		q.setParameter("timeStampCheck", actualTimeCheck);
		q.setMaxResults(5000);
		@SuppressWarnings("unchecked")
		List<ApOpzioni> retObj = q.getResultList();

		// invalido gli oggetti non buoni        	
		if (!retObj.isEmpty()) {
			log.info("UPDATE CACHE OPZIONI AGGIORNATE " + retObj.size() + " OPZIONI");

			for (ApOpzioni opzione : retObj) {
				ApOpzioni opzioneInCache = this.hmOpzioni().get(opzione.getId());
				if (opzioneInCache == null || !opzioneInCache.isValueForced()) {
					this.hmOpzioni().put(opzione.getId(), opzione);
				}

				actualTimeCheck = opzione.getDataAgg().compareTo(actualTimeCheck) > 0 ? opzione.getDataAgg() : actualTimeCheck;

				checkStopServer(opzione);
			}

			initialize();
		}
		
		actualTimeCheck = DateUtil.addSeconds(actualTimeCheck, 1);
		timeCheck = actualTimeCheck;
	}

	private void checkStopServer(ApOpzioni opzione) {
		/**
		 * test su sistema di auto-stop
		 * 
		 * se l'opzione e' variata si rimette a null il valore
		 * e si procede.
		 */
		if (opzione.getId().getSezione().compareTo("WS") == 0 && opzione.getId().getOpzione().compareTo("SRV_STOP") == 0) {
			String curServer = System.getProperty("IDServer");
			if (curServer == null)
				curServer = "SRV_WS";
			if (opzione.getValore() != null && opzione.getValore().compareTo(curServer) == 0) {

				/**
				 * rimetti l'opzione a null
				 */
				Query qUpd = em.createQuery("update ApOpzioni set valore = null, dataAgg = :curDate where sezione = 'WS' and opzione = 'SRV_STOP'");
				qUpd.setParameter("curDate", new Date());
				qUpd.executeUpdate();

				try {
					System.exit(-1);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

	/**
	 * Consente di effettuare l'override di un opzione.
	 * - se il param overridable e' impostato a true si ricerca l'opzione per l'utente; se questa non viene trovata,
	 *   ma e' presente la corrispondente opzione per l'utente DEFAULT - e questa e' impostata con OVERRIDE = 'Y' -
	 *   si crea una nuova opzione per l'utente con valore e utente passati dai parametri.
	 * - se il param overridable e' impostato a false, la ricerca avviene come sempre. Si cerca l'opzione, qualora questa 
	 *  non sia trovata si tenta la ricerca con l'utente DEFAULT, se questa viene trovata si imposta il param valore nell'opzione di Default 
	 * @param em
	 * @param opzione
	 * @param utente
	 * @param sezione
	 * @param formato
	 * @param valore
	 * @param overridable
	 * @return
	 */
	public boolean setOpzioneUT(EntityManager em, String opzione, String utente, String sezione, String formato, Object valore, boolean overridable) {
		boolean ret = false;
		try {
			ret = this.setOpzione(em, opzione, utente, sezione, formato, valore, overridable);
		} catch (Exception e) {
			log.log(Level.SEVERE, "setOpzioneUT", e);
			ret = false;
		}
		return ret;
	}

	public boolean setOpzioneUT(EntityManager em, String opzione, String utente, String sezione, String formato, Object valore) {
		boolean ret = false;
		try {
			ret = this.setOpzione(em, opzione, utente, sezione, formato, valore, false);
		} catch (Exception e) {
			log.log(Level.SEVERE, "setOpzioneUT", e);
			ret = false;
		}
		return ret;
	}

	public boolean setOpzioneUT(EntityManager em, String opzione, String sezione, String formato, Object valore) {
		return this.setOpzioneUT(em, opzione, null, sezione, formato, valore);
	}

	public boolean setOpzioneUT(EntityManager em, String opzione, String sezione, Object valore) {
		return this.setOpzioneUT(em, opzione, null, sezione, null, valore);
	}

	private boolean setOpzione(EntityManager em, String opzione, String utente, String sezione, String formato, Object valore, boolean overridable) {

		boolean ret = true;
		try {
			ApOpzioni op = OpzioniApp.getInstance().getOptionEx(opzione, utente, sezione);
			String tipo = op.getTipo();
			if (tipo == null)
				tipo = IOpzioni.TipiDatiOpzioni.STRING.toString();

			if (tipo.equals(IOpzioni.TipiDatiOpzioni.STRING.toString())) {
				op.setValore((String) valore);
			} else if (tipo.equals(IOpzioni.TipiDatiOpzioni.INTEGER.toString())) {
				Integer i = (Integer) valore;
				op.setValore(String.valueOf(i));
			} else if (tipo.equals(IOpzioni.TipiDatiOpzioni.LONG.toString())) {
				Long i = (Long) valore;
				op.setValore(String.valueOf(i));
			} else if (tipo.equals(IOpzioni.TipiDatiOpzioni.DECIMAL.toString())) {
				BigDecimal b = (BigDecimal) valore;
				op.setValore(b.toPlainString());
			} else if (tipo.equals(IOpzioni.TipiDatiOpzioni.BOOLEAN.toString())) {
				String val = ((String) valore).toUpperCase();
				if (!val.equals("TRUE") && !val.equals(OpzioniCache.FALSE_VAL))
					ret = false;
				else
					op.setValore(val);
			} else if (tipo.equals(IOpzioni.TipiDatiOpzioni.DATA.toString())) {
				op.setValore((String) valore);
			} else if (tipo.equals(IOpzioni.TipiDatiOpzioni.ORA.toString())) {
				throw new ArmundiaUnsupportedLegacyCode();
				//				if (formatOrarioOk((String) valore))
				//					op.setValore((String) valore);
				//				else
				//					ret = false;
			}
			/*controllo l'eventuale override dell'utente*/
			if (overridable && op.getOverride() != null && op.getOverride().compareTo("Y") == 0) {
				if (utente != null && utente.compareTo(op.getId().getUserid()) != 0) {
					/*la modifica della chiave comportera' l'inserimento di una nuova opzione*/
					op.getId().setUserid(utente);
				}
			}

			if (ret) {
				op.setDataAgg(new Date());
				em.merge(op);
			}

		} catch (Exception e) {
			log.log(Level.SEVERE, "Errore", e);
			ret = false;
		}
		return ret;
	}

	protected void readSystemProperty(String propertyName) throws ArmundiaException {

		String valore = System.getProperty(propertyName);
		if (valore == null) {
			//rel 2.0.10
			//|| valore.equals("")) {			
			//quando rilasciamo dai clienti ci possono essere delle proprità che non devono essere valorizzate
			//Ad esmepio le credenziali per SMSTP possono essere vuotre se non serve autenticazione
			throw new ArmundiaException("system-properties " + propertyName + " non è configurato");
		}

		if (valore.endsWith("/")) {
			//per le url rimuovo l'ultimo / 
			//così tutti i path relativi definiti nel codice devono iniziare con/
			valore = valore.substring(0, valore.length() - 1);
		}
		setSystemProperty(propertyName, valore);
	}

	protected void setSystemProperty(String propertyName, String valore) {

		ApOpzioniId key = new ApOpzioniId();
		key.setUserid("SYSTEM");
		key.setSezione("PROPERTY");
		key.setOpzione(propertyName);

		ApOpzioni opz = new ApOpzioni(key);
		opz.setTipo("S"); // Stringa
		opz.setOverride("N");
		opz.setDescrizione("system-properties " + propertyName);
		opz.setValore(valore);

		this.hmOpzioni().put(key, opz);
		log.log(Level.INFO, String.format("setSystemProperty [%s] = %s", propertyName, valore));
	}

}
