package org.gcube.application.cms.concessioni.plugins;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

import org.bson.Document;
import org.gcube.application.cms.custom.gna.concessioni.model.ProfiledConcessione;
import org.gcube.application.cms.implementations.ImplementationProvider;
import org.gcube.application.cms.implementations.ProjectAccess;
import org.gcube.application.cms.plugins.LifecycleManager;
import org.gcube.application.cms.plugins.faults.IndexingException;
import org.gcube.application.cms.plugins.implementations.Default3PhaseManager;
import org.gcube.application.cms.plugins.reports.EventExecutionReport;
import org.gcube.application.cms.plugins.reports.Report;
import org.gcube.application.cms.plugins.requests.IndexDocumentRequest;
import org.gcube.application.cms.serialization.Serialization;
import org.gcube.application.geoportal.common.model.JSONPathWrapper;
import org.gcube.application.geoportal.common.model.document.Project;
import org.gcube.application.geoportal.common.model.document.relationships.Relationship;
import org.gcube.application.geoportal.common.model.document.relationships.RelationshipNavigationObject;
import org.gcube.application.geoportal.common.model.legacy.report.ConstraintCheck;

import com.vdurmont.semver4j.Semver;

import lombok.extern.slf4j.Slf4j;

@Slf4j
/**
 * Overrides 3 Phases lifecycle with override of default values
 *
 */
public class ConcessioniLifeCycleManager extends Default3PhaseManager implements LifecycleManager {

	private static final String FOLLOWS = "follows";
	private static final String PRECEDES = "precedes";

	public ConcessioniLifeCycleManager() {
		DESCRIPTOR.setId("GNA-CONCESSIONI-LC");
		DESCRIPTOR.setDescription(
				"GNA Concessioni. This plugin supports custom lifecycle management for the GNA Concessioni UseCase.");
		DESCRIPTOR.setVersion(new Semver("1.0.0"));
	}

	@Override
	public EventExecutionReport setDefault(EventExecutionReport currentReport) {
		EventExecutionReport report = super.setDefault(currentReport);
		try {
			report.setResultingDocument(setDefaults(report.getTheRequest().getDocument()).getTheDocument());
		} catch (Throwable t) {
			log.error("Unable to evaluate defaults for concessione "
					+ currentReport.getTheRequest().getDocument().getId(), t);
			log.debug("Object was {} ", report.getTheRequest().getDocument());
			report.setStatus(Report.Status.ERROR);
			report.getMessages().add("Unable to evaluate defaults : " + t.getMessage());
		}
		return report;
	}

	// STATIC ROUTINES

	static final Project setDefaults(Project document) throws IOException {
		log.info("Concessione ID {}, setting defaults..", document.getId());
		log.debug("Full concessione is {}", document);
		ProfiledConcessione c = Serialization.convert(document, ProfiledConcessione.class);
		Document doc = c.getTheDocument();
		doc.putIfAbsent(ProfiledConcessione.SOGGETTO, new String[] { "Research Excavation", "Archaeology" });

		doc.putIfAbsent(ProfiledConcessione.DESCRIZIONE_CONTENUTO,
				"Relazione di fine scavo e relativo abstract; selezione di immagini rappresentative;"
						+ " posizionamento topografico dell'area indagata, pianta di fine scavo.");

		// Super Section
		// TODO read from UCD
		c.getInfo().getAccess()
				.setLicense(ConstraintCheck.defaultFor(c.getInfo().getAccess().getLicense(), "CC0-1.0").evaluate());

		// RELAZIONE
		Document rel = doc.containsKey(ProfiledConcessione.RELAZIONE_SCAVO)
				? Serialization.convert(doc.get(ProfiledConcessione.RELAZIONE_SCAVO), Document.class)
				: new Document();
		log.debug("Concessione {}, managing relazione {}", document.getId(), rel);
		rel.putIfAbsent(ProfiledConcessione.Sections.TITOLO,
				doc.getString(ProfiledConcessione.NOME) + " relazione di scavo");
		rel.putIfAbsent(ProfiledConcessione.SOGGETTO, doc.get(ProfiledConcessione.SOGGETTO));
		doc.put(ProfiledConcessione.RELAZIONE_SCAVO, rel);

		// ABSTRACT Relazione
		Document abs = doc.containsKey(ProfiledConcessione.ABSTRACT_RELAZIONE)
				? Serialization.convert(doc.get(ProfiledConcessione.ABSTRACT_RELAZIONE), Document.class)
				: new Document();
		log.debug("Concessione {}, managing abstract relazione {}", document.getId(), abs);
		abs.putIfAbsent(ProfiledConcessione.Sections.TITOLO,
				doc.getString(ProfiledConcessione.NOME) + " abstract relazione di scavo");
		doc.put(ProfiledConcessione.ABSTRACT_RELAZIONE, abs);

		// Posizionamento scavo
		if (doc.containsKey(ProfiledConcessione.POSIZIONAMENTO_SCAVO)) {
			Document pos = Serialization.convert(doc.get(ProfiledConcessione.POSIZIONAMENTO_SCAVO), Document.class);
			log.debug("Concessione {}, managing posizionamento scavo {}", document.getId(), pos);
			pos.putIfAbsent(ProfiledConcessione.Sections.TITOLO,
					doc.getString(ProfiledConcessione.NOME) + " posizionamento scavo");
			pos.putIfAbsent(ProfiledConcessione.Sections.ABSTRACT,
					"Posizionamento topografico georeferenziato dell’area interessata dalle indagini");
			pos.putIfAbsent(ProfiledConcessione.Layers.TOPIC, "Society");
			pos.putIfAbsent(ProfiledConcessione.Layers.SUB_TOPIC, "Archeology");

			pos.putIfAbsent(ProfiledConcessione.PAROLE_CHIAVE_LIBERE,
					doc.get(ProfiledConcessione.PAROLE_CHIAVE_LIBERE));
			pos.putIfAbsent(ProfiledConcessione.PAREOLE_CHIAVE_ICCD, doc.get(ProfiledConcessione.PAREOLE_CHIAVE_ICCD));

			// BBOX in registered filesets

			pos.put(ProfiledConcessione.RESPONSABILE, doc.get(ProfiledConcessione.RESPONSABILE));
			doc.put(ProfiledConcessione.POSIZIONAMENTO_SCAVO, pos);
		}

		// IMMAGINI RAPPRESENTATIVE

		JSONPathWrapper wrapper = new JSONPathWrapper(Serialization.write(doc));
		if (doc.containsKey(ProfiledConcessione.IMMAGINI_RAPPRESENTATIVE)) {
			List imgs = wrapper.getByPath("$." + ProfiledConcessione.IMMAGINI_RAPPRESENTATIVE, List.class).get(0);
			log.debug("Concessione {}, found imgs {}", document.getId(), imgs.size());
			for (int i = 0; i < imgs.size(); i++) {
				Document imgDoc = Serialization.asDocument(imgs.get(i));
				imgDoc.putIfAbsent(ProfiledConcessione.SOGGETTO, doc.get(ProfiledConcessione.SOGGETTO));

				wrapper.setElement("$." + ProfiledConcessione.IMMAGINI_RAPPRESENTATIVE + "[" + i + "]", imgDoc);
			}
		}

		// Layers
		if (doc.containsKey(ProfiledConcessione.PIANTE_FINE_SCAVO)) {
			List piante = wrapper.getByPath("$." + ProfiledConcessione.PIANTE_FINE_SCAVO, List.class).get(0);
			log.debug("Concessione {}, found piante {}", document.getId(), piante.size());
			for (int i = 0; i < piante.size(); i++) {
				Document piantaDoc = Serialization.asDocument(piante.get(i));
				log.debug("Concessione {}, managing pianta {}", document.getId(), piantaDoc);
				piantaDoc.putIfAbsent(ProfiledConcessione.Sections.TITOLO,
						doc.getString(ProfiledConcessione.NOME) + " pianta fine scavo");
				piantaDoc.putIfAbsent(ProfiledConcessione.Sections.ABSTRACT,
						"Planimetria georeferenziata dell'area indagata al termine delle attività");
				piantaDoc.putIfAbsent(ProfiledConcessione.Layers.TOPIC, "Society");
				piantaDoc.putIfAbsent(ProfiledConcessione.Layers.SUB_TOPIC, "Archeology");

				piantaDoc.putIfAbsent(ProfiledConcessione.PAROLE_CHIAVE_LIBERE,
						doc.get(ProfiledConcessione.PAROLE_CHIAVE_LIBERE));
				piantaDoc.putIfAbsent(ProfiledConcessione.PAREOLE_CHIAVE_ICCD,
						doc.get(ProfiledConcessione.PAREOLE_CHIAVE_ICCD));

				// BBOX in registered filesets

				piantaDoc.put(ProfiledConcessione.RESPONSABILE, doc.get(ProfiledConcessione.RESPONSABILE));

				wrapper.setElement("$." + ProfiledConcessione.PIANTE_FINE_SCAVO + "[" + i + "]", piantaDoc);
			}
		}

		c.setTheDocument(Serialization.read(wrapper.getValueCTX().jsonString(), Document.class));
		return c;
	}

	/**
	 * New method implemented by Francesco
	 */
	@Override
	protected Document evaluateAdditionalIndexParameters(IndexDocumentRequest request) throws IndexingException {
		Document toReturn = super.evaluateAdditionalIndexParameters(request);
		if (toReturn == null)
			toReturn = new Document();
		Project indexingProject = request.getDocument();

		// Evaluate to display project IDs
		log.info("Evaluating relationship chain. Current ID is {}", indexingProject.getId());
		try {
			ArrayList<Project> projects = new ArrayList<>();
			ProjectAccess access = ImplementationProvider.get().getProvidedObjectByClass(ProjectAccess.class);
			// get Last ID in relation chain
			projects.add(indexingProject);
			if (!indexingProject.getRelationshipsByName(PRECEDES).isEmpty())
				scanRelation(projects, access
						.getRelations(indexingProject.getProfileID(), indexingProject.getId(), PRECEDES, true).get(0),
						false);
			if (!indexingProject.getRelationshipsByName(FOLLOWS).isEmpty())
				scanRelation(projects, access
						.getRelations(indexingProject.getProfileID(), indexingProject.getId(), FOLLOWS, true).get(0),
						false);

			log.debug("Produced full chain [size : {}] from {}, evaluating last available for PHASE {} ",
					projects.size(), indexingProject.getId(), indexingProject.getLifecycleInformation().getPhase());

			log.info("projects are: " + projects.size());

			List<String> toDisplayId = new ArrayList<>();
			List<String> toHideIds = new ArrayList<>();

			// Reverse Order means from the last FOLLOW to the first one (temporal reverse
			// order)
			Collections.sort(projects, Collections.reverseOrder(new ProjectRelationComparator()));

			if (log.isInfoEnabled()) {
				int j = 0;
				log.info("Printing project reverse order....");
				for (Project theProject : projects) {
					log.info(++j + ") " + theProject.getId() + " data inizio: "
							+ theProject.getTheDocument().get("dataInizioProgetto"));
				}
			}

			log.trace("Checking from LAST.. ");
			for (int i = 0; i < projects.size(); i++) {

				Project p = projects.get(i);
				String phase = p.getLifecycleInformation().getPhase();

				// IS TO DISPLAY EMPTY? Step into only once.
				// Indexing the first project that is in PENDING APPROVAL or PUBLISHED or
				// updating the phase of the indexing (selected) project
				if (toDisplayId.isEmpty()) {
					// IF PHASE IS PENDING APPROVAL OR PUBLISHED adding toDisplayId
					if ((phase.equals(Phases.PENDING_APPROVAL) || phase.equals(Phases.PUBLISHED))) {
						toDisplayId.add(p.getId());
					} else if (p.getId().compareTo(indexingProject.getId()) == 0) {
						// Means that the operation has been called on the indexing project from DRAFT
						// phase to PENDING_APPROVAL or PUBLISH
						toDisplayId.add(p.getId());
					}

				} else {
					// Hides the centroid regardless of project phase
					toHideIds.add(p.getId());
				}
			}

			toReturn.put("_toHideIds", toHideIds);
			toReturn.put("_toDisplayIds", toDisplayId);

			log.info(
					"Indexing request for " + indexingProject.getProfileID()
							+ " [ID {}] with to _toHideIds {} and _toDisplayIds {} ",
					indexingProject.getId(), toHideIds, toDisplayId);
			return toReturn;
		} catch (Exception e) {
			log.error("Unable to evaluate to Hide and Display Ids ", e);
			throw new IndexingException("Unable to evaluate chain ids to hide / display", e);
		}

	}

	private static void scanRelation(ArrayList chain, RelationshipNavigationObject obj, boolean putBefore) {
		if (putBefore)
			chain.add(0, obj.getTarget());
		else
			chain.add(obj.getTarget());
		if (obj.getChildren() != null)
			obj.getChildren().forEach(r -> scanRelation(chain, r, putBefore));
	}

	public static class ProjectRelationComparator implements Comparator<Project> {
		@Override
		public int compare(Project p1, Project p2) {

			if (p1 == null)
				return 1;
			if (p2 == null)
				return -1;

			Integer compareResult = null;
			compareResult = compareProjectAgainstRelations(p1, p2);
			log.trace("p1 & p2, comparator result: {}", compareResult);
			if (compareResult == null) {
				log.debug("No relations beetween p1 & p2, checking inverted relations");
				compareResult = compareProjectAgainstRelations(p2, p1);
				log.trace("p2 & p1, comparator result: {}", compareResult);

				if (compareResult == null) {
					log.trace("p1 & p2, are not comparable, returning 0");
					compareResult = 0;
				}
			}

			log.debug("p1 & p2, comparator result, returns: {}", compareResult);
			return compareResult;
		}
	}

	public static Integer compareProjectAgainstRelations(Project source, Project target) {
		log.debug("comparing source {} and target {}", source.getId(), target.getId());
		List<Relationship> listRel = source.getRelationships();
		log.debug("relationships of {} are : {}", source.getId(), listRel);
		if (listRel != null) {
			String targetId = target.getId();
			for (Relationship relationship : listRel) {
				String relTargetId = relationship.getTargetID();
				if (targetId.compareTo(relTargetId) == 0) {
					String relationName = relationship.getRelationshipName();
					if (relationName.equals(PRECEDES)) {
						log.debug("source {} [rel {} ] target {}, so source < target ", source.getId(), PRECEDES,
								target.getId());
						// source < target
						return -1;
					} else if (relationName.equals(FOLLOWS)) {
						log.debug("source {} [rel {} ] target {}, so source > target ", source.getId(), FOLLOWS,
								target.getId());
						// source > target
						return 1;
					}
				}
			}
		}
		log.debug("No relations beetween a & b");
		return null;
	}
}
