package eu.dnetlib.openaire.directindex.api;

import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

import org.apache.commons.lang.StringUtils;
import org.springframework.web.client.RestClientException;
import org.springframework.web.client.RestTemplate;

import com.google.common.collect.Sets;

import eu.dnetlib.miscutils.functional.hash.Hashing;
import eu.dnetlib.openaire.directindex.objects.DatasourceEntry;
import eu.dnetlib.openaire.directindex.objects.DsmSearchRequest;
import eu.dnetlib.openaire.directindex.objects.DsmSearchResponse;
import eu.dnetlib.openaire.directindex.objects.ZenodoContextList;

/**
 * Created by michele on 15/01/16.
 */
public class OpenAIRESubmitterUtils {

	private static final org.apache.commons.logging.Log log = org.apache.commons.logging.LogFactory.getLog(OpenAIRESubmitterUtils.class);
	private static final String ZENODO_COMMUNITY = "zenodo.org/communities/";
	private final String community_api;
	private final String dsm_api;

	public OpenAIRESubmitterUtils(final String community_api, final String dsm_api) {
		this.community_api = community_api;
		this.dsm_api = dsm_api;
	}

	public DatasourceEntry findDatasource(final String dsId) {
		final RestTemplate rt = new RestTemplate();
		final String url = dsm_api + "/searchdetails/0/1?requestSortBy=id&order=ASCENDING";
		try {
			final DsmSearchResponse res = rt.postForObject(url, new DsmSearchRequest(dsId), DsmSearchResponse.class);
			return res.getDatasourceInfo()
				.stream()
				.map(ds -> new DatasourceEntry(ds.getId(), ds.getOfficialname(), ds.getNamespaceprefix()))
				.findFirst()
				.orElse(null);
		} catch (final RestClientException rce) {
			log.error("Unable to get object for " + url);
			return null;
		}
	}

	public Map<String, String> calculateProjectInfo(final String link) {
		final Map<String, String> info = new HashMap<>();
		final String[] arr = link.split("/");
		// info:eu-repo/grantAgreement/EC/FP7/244909/EU/Making Capabilities Work/WorkAble

		if (arr.length > 4) {
			final String acronym = arr.length > 7 ? arr[7] : "";
			final String title = arr.length > 6 ? StringUtils.isNotBlank(arr[6]) ? arr[6] : acronym : "";
			final String jurisdiction = arr.length > 5 ? arr[5] : "";
			final String funderId = calculateFunderId(arr[2], arr[3]);
			final String funderShortName = fixFunderShortName(arr[2]);
			final String fundingName = fixFundingName(funderShortName, arr[3]);
			info.put("id", calculateProjectId(arr[2], arr[3], arr[4]));
			info.put("funderShortName", fixFunderShortName(arr[2]));
			info.put("fundingName", fundingName);
			info.put("code", unescape(arr[4]));
			info.put("jurisdiction", jurisdiction);
			info.put("title", title);
			info.put("acronym", acronym);
			info.put("funderId", funderId);
			info.put("funderName", calculateFunderName(arr[2]));
			if (StringUtils.isNotBlank(arr[3])) {
				info.put("fundingId", funderId + "::" + fundingName);
			}
		}
		return info;
	}

	// TODO: remove me when Zenodo ingests the good UKRI projects
	protected String fixFunderShortName(final String funderShortName) {
		switch (funderShortName) {
		case "RCUK":
			return "UKRI";
		default:
			return funderShortName;
		}
	}

	protected String calculateFunderPrefix(final String funderShortName, final String funding) {
		switch (funderShortName.toLowerCase()) {
		case "chist-era":
			return "chistera____::";
		case "conicyt":
			return "conicytf____::";
		case "dfg":
			return "dfgf________::";
		case "ec":
			switch (funding.toLowerCase()) {
			case "fp7":
				return "corda_______::";
			case "h2020":
				return "corda__h2020::";
			default:
				return "corda_____he::";
			}
		case "eea":
			return "euenvagency_::";
		case "hrzz":
		case "mzos":
			return "irb_hr______::";
		case "tara":
			return "taraexp_____::";
		case "tubitak":
			return "tubitakf____::";
		case "rcuk":
			return "ukri________::";
		default:
			String prefix = funderShortName.toLowerCase();
			// ensure we have 12 chars
			while (prefix.length() < 12) {
				prefix += "_";
			}
			return prefix + "::";
		}
	}

	protected String calculateProjectId(final String funderShortName, final String funding, final String code) {
		final String suffix = Hashing.md5(unescape(code));
		final String funderPrefix = calculateFunderPrefix(funderShortName, funding);
		return funderPrefix + suffix;
	}

	private String unescape(final String code) {
		return code.replace("%2F", "/");
	}

	protected String calculateFunderId(final String funderShortName, final String funding) {
		switch (funderShortName.toLowerCase()) {
		case "ec":
			return "ec__________::EC";
		default:
			final String fixedFunderShortName = fixFunderShortName(funderShortName);
			final String prefix = calculateFunderPrefix(fixedFunderShortName, funding);
			return prefix + fixedFunderShortName.toUpperCase();
		}
	}

	protected String calculateFunderName(final String funderShortName) {

		switch (funderShortName.toLowerCase()) {
		case "aff":
		case "aka":
			return "Academy of Finland";
		case "anr":
			return "French National Research Agency (ANR)";
		case "arc":
			return "Australian Research Council (ARC)";
		case "asap":
			return "Aligning Science Across Parkinson's";
		case "chist-era":
			return "CHIST-ERA";
		case "cihr":
			return "Canadian Institutes of Health Research";
		case "conicyt":
			return "Comisión Nacional de Investigación Científica y Tecnológica";
		case "dfg":
			return "Deutsche Forschungsgemeinschaft";
		case "ec":
			return "European Commission";
		case "eea":
			return "European Environment Agency";
		case "fct":
			return "Fundação para a Ciência e a Tecnologia, I.P.";
		case "fwf":
			return "Austrian Science Fund (FWF)";
		case "gsrt":
			return "General Secretariat of Research and Technology (GSRT)";
		case "hrzz":
			return "Croatian Science Foundation (CSF)";
		case "innoviris":
			return "INNOVIRIS";
		case "mestd":
			return "Ministry of Education, Science and Technological Development of Republic of Serbia";
		case "miur":
			return "Ministero dell'Istruzione dell'Università e della Ricerca";
		case "mzos":
			return "Ministry of Science, Education and Sports of the Republic of Croatia (MSES)";
		case "nhmrc":
			return "National Health and Medical Research Council (NHMRC)";
		case "nih":
			return "National Institutes of Health";
		case "nsf":
			return "National Science Foundation";
		case "nserc":
			return "Natural Sciences and Engineering Research Council of Canada";
		case "nwo":
			return "Netherlands Organisation for Scientific Research (NWO)";
		case "rcuk":
		case "ukri":
			return "UK Research and Innovation";
		case "rif":
		case "rpf":
			return "Research and Innovation Foundation";
		case "rsf":
			return "Russian Science Foundation";
		case "sfi":
			return "Science Foundation Ireland";
		case "sgov":
			return "Gobierno de España";
		case "snsf":
			return "Swiss National Science Foundation";
		case "sshrc":
			return "Social Sciences and Humanities Research Council";
		case "tara":
			return "Tara Expeditions Foundation";
		case "tubitak":
			return "Türkiye Bilimsel ve Teknolojik Araştırma Kurumu";
		case "wt":
			return "Wellcome Trust";
		default:
			log.error("Funder short name '" + funderShortName + "' not managed");
			return "";
		}
	}

	protected String fixFundingName(final String funderShortName, final String fundingName) {
		switch (funderShortName) {
		case "EC":
			if (fundingName.toLowerCase().startsWith("horizon 2020")) { return "H2020"; }
			if (fundingName.toLowerCase().startsWith("horizon europe")) { return "HE"; }
		default:
			return fundingName;
		}
	}

	public Collection<String> translateZenodoCommunity(final String community) {
		if (community.contains(ZENODO_COMMUNITY)) {
			final String context = community.substring(community.lastIndexOf("/") + 1);
			final RestTemplate rt = new RestTemplate();
			try {
				return new HashSet<>(rt.getForObject(community_api + context + "/openairecommunities", ZenodoContextList.class)
					.getOpenAirecommunitylist());
			} catch (final RestClientException rce) {
				log.error("Unable to get object for " + community_api + context + "/openairecommunities");
				log.error(rce.getMessage());
				return new HashSet<>();
			}
		} else {
			return Sets.newHashSet(community);
		}
	}

	private ContextInfo createContextInfo(final String[] arr, final int pos, final Map<String, String> labelMap) {
		final StringWriter id = new StringWriter();
		id.write(arr[0]);
		for (int i = 0; i < pos; i++) {
			id.write("::");
			id.write(arr[i + 1]);
		}
		final String label = labelMap.get(id.toString());
		final String elem = pos == 0 ? "context" : pos == 1 ? "category" : "concept";
		final ContextInfo info = new ContextInfo(elem, id.toString(), label);
		if (pos + 1 < arr.length) {
			info.getChildren().add(createContextInfo(arr, pos + 1, labelMap));
		}
		return info;
	}

	public List<ContextInfo> processContexts(final List<String> zenodoCommunities, final Map<String, String> labelMap) {
		return zenodoCommunities.stream()
			.map(c -> translateZenodoCommunity(c))
			.flatMap(coll -> coll.stream())
			.map(ctx -> createContextInfo(ctx.split("::"), 0, labelMap))
			.filter(info -> StringUtils.isNotBlank(info.getLabel()))
			.collect(Collectors.toList());
	}

	public class ContextInfo {

		private String elem;
		private String id;
		private String label;
		private List<ContextInfo> children = new ArrayList<>();

		public ContextInfo(final String elem,
			final String id, final String label) {
			this.elem = elem;
			this.id = id;
			this.label = label;
		}

		public String getElem() {
			return elem;
		}

		public void setElem(final String elem) {
			this.elem = elem;
		}

		public String getId() {
			return id;
		}

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

		public List<ContextInfo> getChildren() {
			return children;
		}

		public void setChildren(final List<ContextInfo> children) {
			this.children = children;
		}

		public boolean isRoot() {
			return elem.equals("context");
		}

		public String getLabel() {
			return label;
		}

		public void setLabel(final String label) {
			this.label = label;
		}
	}

}
