package eu.dnetlib.enabling.datasources;

import java.io.IOException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.google.common.base.Splitter;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import eu.dnetlib.rmi.datasource.DatasourceConstants;
import eu.dnetlib.rmi.datasource.DatasourceDesc;
import eu.dnetlib.rmi.datasource.DatasourceManagerServiceException;
import eu.dnetlib.rmi.datasource.IfaceDesc;
import org.antlr.stringtemplate.StringTemplate;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Node;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;

public class DatasourceFunctions {

	private static final Log log = LogFactory.getLog(DatasourceFunctions.class); // NOPMD by marko on 11/24/08 5:02 PM

	private static Resource ifaceTemplate = new ClassPathResource("/eu/dnetlib/enabling/datasources/repo_interface.st");

	public static String buildInterface(final String infopackage) {
		final IfaceDesc iface = generateIfaceDesc(infopackage);

		if (iface != null) {
			final StringTemplate i = getTemplate(ifaceTemplate);
			i.setAttribute("ifc", iface);

			return i.toString();
		} else {
			return "";
		}
	}

	public static IfaceDesc generateIfaceDesc(final String infopackage) {
		if (infopackage == null || infopackage.trim().isEmpty()) { return null; }

		final IfaceDesc iface = new IfaceDesc();
		iface.setId(getValueBetween(infopackage, null, "<==1==>"));
		iface.setTypology(getValueBetween(infopackage, "<==1==>", "<==2==>"));
		iface.setCompliance(getValueBetween(infopackage, "<==2==>", "<==3==>"));
		iface.setContentDescription(getValueBetween(infopackage, "<==3==>", "<==4==>"));
		iface.setAccessProtocol(getValueBetween(infopackage, "<==4==>", "<==5==>"));
		iface.setActive(Boolean.parseBoolean(getValueBetween(infopackage, "<==5==>", "<==6==>")));
		iface.setRemovable(Boolean.parseBoolean(getValueBetween(infopackage, "<==6==>", "<==7==>")));

		final Map<String, String> accessParams = Maps.newHashMap();
		final Map<String, String> extraParams = Maps.newHashMap();

		for (String param : parseParams(getValueBetween(infopackage, "<==7==>", null))) {

			final String[] arr = param.split("###");

			if (arr.length == 3) {
				final boolean accessParam = Boolean.parseBoolean(arr[0].trim());
				final String paramName = arr[1].trim();
				final String paramValue = arr.length > 2 ? arr[2].trim() : "";

				if (accessParam) {
					if (paramName.equals(DatasourceParams.baseUrl.toString())) {
						iface.setBaseUrl(paramValue);
					} else {
						accessParams.put(paramName, paramValue);
					}
				} else {
					extraParams.put(paramName, paramValue);
				}
			} else {
				log.debug("Invalid Datasource Parameter");
			}
		}

		iface.setAccessParams(accessParams);
		iface.setExtraFields(extraParams);

		return iface;
	}

	private static String getValueBetween(final String s, final String pre, final String post) {
		if (pre == null && post == null) {
			return StringEscapeUtils.escapeXml(s);
		} else if (pre == null) {
			return StringEscapeUtils.escapeXml(StringUtils.substringBefore(s, post));
		} else if (post == null) {
			return StringEscapeUtils.escapeXml(StringUtils.substringAfter(s, pre));
		} else {
			return StringEscapeUtils.escapeXml(StringUtils.substringBetween(s, pre, post));
		}
	}

	private static Iterable<String> parseParams(final String s) {
		return Splitter.on("@@@").omitEmptyStrings().trimResults().split(s);
	}

	private static StringTemplate getTemplate(final Resource res) {
		try {
			return new StringTemplate(IOUtils.toString(res.getInputStream()));
		} catch (IOException e) {
			log.error("unable to get template", e);
			throw new RuntimeException(e);
		}
	}

	public static String asSqlValue(final String s) {
		return s == null ? "NULL" : "'" + StringUtils.replace(s, "'", "''") + "'";
	}

	public static String asSqlValue(final Boolean b) {
		return b == null ? "NULL" : b.toString();
	}

	private static String asSqlValue(final Double d) {
		return d == null ? "NULL" : d.toString();
	}

	private static Object asSqlValue(final Date date) {
		if (date == null) { return "NULL"; }
		final long millis = date.getTime();
		final java.sql.Date sqlDate = new java.sql.Date(millis);
		return "'" + sqlDate.toString() + "'";
	}

	public static boolean verifyCompliance(final Document doc) {

		for (Object o : doc.selectNodes("//INTERFACE")) {
			final String compliance = ((Node) o).valueOf("./INTERFACE_EXTRA_FIELD[@name='" + DatasourceConstants.OVERRIDING_COMPLIANCE_FIELD + "']");
			if (!StringUtils.isEmpty(compliance)) {
				if (!"unknown".equalsIgnoreCase(compliance) && !"notCompatible".equalsIgnoreCase(compliance)) { return true; }
			} else {
				final String compliance2 = ((Node) o).valueOf("@compliance");
				if (!"unknown".equalsIgnoreCase(compliance2) && !"notCompatible".equalsIgnoreCase(compliance2)) { return true; }
			}
		}

		return false;
	}

	@SuppressWarnings("unchecked")
	public static DatasourceDesc xmlToDatasourceDesc(final Document doc) throws DatasourceManagerServiceException {

		final DatasourceDesc ds = new DatasourceDesc();

		ds.setId(findValue(doc, "id", String.class));
		ds.setOfficialName(findValue(doc, "officialname", String.class));
		ds.setEnglishName(findValue(doc, "englishname", String.class));
		ds.setWebsiteUrl(findValue(doc, "websiteurl", String.class));
		ds.setLogoUrl(findValue(doc, "logourl", String.class));
		ds.setCountryCode(findValue(doc, "countrycode", String.class));
		ds.setCountryName(findValue(doc, "countryname", String.class));
		ds.setOrganization(findValue(doc, "organization", String.class));
		ds.setContactEmail(findValue(doc, "contactemail", String.class));
		ds.setLatitude(findValue(doc, "latitude", Double.class));
		ds.setLongitude(findValue(doc, "longitude", Double.class));
		ds.setTimezone(findValue(doc, "timezone", Double.class));
		ds.setNamespacePrefix(findValue(doc, "namespaceprefix", String.class));
		ds.setOdNumberOfItems(findValue(doc, "od_numberofitems", String.class));
		ds.setOdNumberOfItemsDate(findValue(doc, "od_numberofitemsdate", String.class));
		ds.setOdPolicies(findValue(doc, "od_policies", String.class));
		ds.setOdLanguages(findValue(doc, "od_languages", String.class));
		ds.setOdContentTypes(findValue(doc, "od_contenttypes", String.class));
		ds.setCollectedFrom(findValue(doc, "collectedfrom", String.class));
		ds.setInferred(findValue(doc, "inferred", Boolean.class));
		ds.setDeletedByInference(findValue(doc, "deletedbyinference", Boolean.class));
		ds.setTrust(findValue(doc, "trust", Double.class));
		ds.setInferenceProvenance(findValue(doc, "inferenceprovenance", String.class));
		ds.setDateOfValidation(findValue(doc, "dateofvalidation", Date.class));
		ds.setRegisteredBy(findValue(doc, "registeredby", String.class));
		ds.setDatasourceClass(findValue(doc, "datasourceclass", String.class));
		ds.setProvenanceActionClass(findValue(doc, "provenanceactionclass", String.class));
		ds.setDateOfCollection(findValue(doc, "dateofcollection", Date.class));
		ds.setTypology(findValue(doc, "typology", String.class));
		ds.setActivationId(findValue(doc, "activationid", String.class));
		ds.setMergehomonyms(findValue(doc, "mergehomonyms", Boolean.class));
		ds.setDescription(findValue(doc, "description", String.class));
		ds.setReleaseStartDate(findValue(doc, "releasestartdate", Date.class));
		ds.setReleaseEndDate(findValue(doc, "releaseenddate", Date.class));
		ds.setMissionStatementUrl(findValue(doc, "missionstatementurl", String.class));
		ds.setDataProvider(findValue(doc, "dataprovider", Boolean.class));
		ds.setServiceProvider(findValue(doc, "serviceprovider", Boolean.class));
		ds.setDatabaseAccessType(findValue(doc, "databaseaccesstype", String.class));
		ds.setDataUploadType(findValue(doc, "datauploadtype", String.class));
		ds.setDatabaseAccessRestriction(findValue(doc, "databaseaccessrestriction", String.class));
		ds.setDataUploadRestriction(findValue(doc, "datauploadrestriction", String.class));
		ds.setVersioning(findValue(doc, "versioning", Boolean.class));
		ds.setCitationGuidelineUrl(findValue(doc, "citationguidelineurl", String.class));
		ds.setQualityManagementKind(findValue(doc, "qualitymanagementkind", String.class));
		ds.setPidSystems(findValue(doc, "pidsystems", String.class));
		ds.setCertificates(findValue(doc, "certificates", String.class));
		ds.setAggregator(findValue(doc, "aggregator", String.class));
		ds.setIssn(findValue(doc, "issn", String.class));
		ds.setEissn(findValue(doc, "eissn", String.class));
		ds.setLissn(findValue(doc, "lissn", String.class));
		ds.setInterfaces(findValue(doc, "accessinfopackage", List.class));

		return ds;
	}

	@SuppressWarnings("unchecked")
	private static <T> T findValue(final Document doc, final String elem, final Class<T> clazz) throws DatasourceManagerServiceException {

		if (clazz == List.class) {
			if (elem.equalsIgnoreCase("accessinfopackage")) {
				final List<IfaceDesc> ifaces = Lists.newArrayList();
				for (Object o : doc.selectNodes("//FIELD[@name='accessinfopackage']/ITEM")) {
					final IfaceDesc ifaceDesc = generateIfaceDesc(((Node) o).getText());
					if (ifaceDesc != null) {
						ifaces.add(ifaceDesc);
					}
				}
				return (T) ifaces;
			} else {
				throw new DatasourceManagerServiceException("Invalid List element " + elem);
			}
		} else {
			final String val = doc.valueOf("//FIELD[@name='" + elem + "' and not(@isNull='true')]");

			if (clazz == String.class) {
				return (T) val;
			} else if (val == null || val.isEmpty()) {
				return null;
			} else if (clazz == Boolean.class) {
				return (T) new Boolean(val);
			} else if (clazz == Double.class) {
				return (T) new Double(val);
			} else if (clazz == Date.class) {
				try {
					return (T) new SimpleDateFormat("yyyy-MM-dd").parse(val);
				} catch (ParseException e) {
					throw new DatasourceManagerServiceException("Invalid date: " + val);
				}
			} else {
				throw new DatasourceManagerServiceException("Invalid type " + clazz + " for element " + elem);
			}
		}

	}

	public static Map<String, Object> asMapOfSqlValues(final DatasourceDesc ds) {
		final Map<String, Object> map = Maps.newHashMap();
		map.put("id", asSqlValue(ds.getId()));
		map.put("officialName", asSqlValue(ds.getOfficialName()));
		map.put("englishName", asSqlValue(ds.getEnglishName()));
		map.put("websiteUrl", asSqlValue(ds.getWebsiteUrl()));
		map.put("logoUrl", asSqlValue(ds.getLogoUrl()));
		map.put("countryCode", asSqlValue(ds.getCountryCode()));
		map.put("countryName", asSqlValue(ds.getCountryName()));
		map.put("organization", asSqlValue(ds.getOrganization()));
		map.put("contactEmail", asSqlValue(ds.getContactEmail()));
		map.put("latitude", asSqlValue(ds.getLatitude()));
		map.put("longitude", asSqlValue(ds.getLongitude()));
		map.put("timezone", asSqlValue(ds.getTimezone()));
		map.put("namespacePrefix", asSqlValue(ds.getNamespacePrefix()));
		map.put("odNumberOfItems", asSqlValue(ds.getOdNumberOfItems()));
		map.put("odNumberOfItemsDate", asSqlValue(ds.getOdNumberOfItemsDate()));
		map.put("odPolicies", asSqlValue(ds.getOdPolicies()));
		map.put("odLanguages", asSqlValue(ds.getOdLanguages()));
		map.put("odContentTypes", asSqlValue(ds.getOdContentTypes()));
		map.put("collectedFrom", asSqlValue(ds.getCollectedFrom()));
		map.put("inferred", asSqlValue(ds.isInferred()));
		map.put("deletedByInference", asSqlValue(ds.isDeletedByInference()));
		map.put("trust", asSqlValue(ds.getTrust()));
		map.put("inferenceProvenance", asSqlValue(ds.getInferenceProvenance()));
		map.put("dateOfValidation", asSqlValue(ds.getDateOfValidation()));
		map.put("registeredBy", asSqlValue(ds.getRegisteredBy()));
		map.put("datasourceClass", asSqlValue(ds.getDatasourceClass()));
		map.put("provenanceActionClass", asSqlValue(ds.getProvenanceActionClass()));
		map.put("dateOfCollection", asSqlValue(ds.getDateOfCollection()));
		map.put("typology", asSqlValue(ds.getTypology()));
		map.put("activationId", asSqlValue(ds.getActivationId()));
		map.put("mergehomonyms", asSqlValue(ds.isMergehomonyms()));
		map.put("description", asSqlValue(ds.getDescription()));
		map.put("releaseStartDate", asSqlValue(ds.getReleaseStartDate()));
		map.put("releaseEndDate", asSqlValue(ds.getReleaseEndDate()));
		map.put("missionStatementUrl", asSqlValue(ds.getMissionStatementUrl()));
		map.put("dataProvider", asSqlValue(ds.isDataProvider()));
		map.put("serviceProvider", asSqlValue(ds.isServiceProvider()));
		map.put("databaseAccessType", asSqlValue(ds.getDatabaseAccessType()));
		map.put("dataUploadType", asSqlValue(ds.getDataUploadType()));
		map.put("databaseAccessRestriction", asSqlValue(ds.getDatabaseAccessRestriction()));
		map.put("dataUploadRestriction", asSqlValue(ds.getDataUploadRestriction()));
		map.put("versioning", asSqlValue(ds.isVersioning()));
		map.put("citationGuidelineUrl", asSqlValue(ds.getCitationGuidelineUrl()));
		map.put("qualityManagementKind", asSqlValue(ds.getQualityManagementKind()));
		map.put("pidSystems", asSqlValue(ds.getPidSystems()));
		map.put("certificates", asSqlValue(ds.getCertificates()));
		map.put("aggregator", asSqlValue(ds.getAggregator()));
		map.put("issn", asSqlValue(ds.getIssn()));
		map.put("eissn", asSqlValue(ds.getEissn()));
		map.put("lissn", asSqlValue(ds.getLissn()));

		final ArrayList<Map<String, Object>> ifaces = new ArrayList<Map<String, Object>>();

		if (ds.getInterfaces() != null) {

			for (IfaceDesc iface : ds.getInterfaces()) {
				if (iface.getId() != null && !iface.getId().trim().isEmpty()) {
					final Map<String, Object> mapIface = Maps.newHashMap();

					mapIface.put("id", asSqlValue(iface.getId()));
					mapIface.put("typology", asSqlValue(iface.getTypology()));
					mapIface.put("compliance", asSqlValue(iface.getCompliance()));
					mapIface.put("contentDescription", asSqlValue(iface.getContentDescription()));
					mapIface.put("accessProtocol", asSqlValue(iface.getAccessProtocol()));
					mapIface.put("baseUrl", asSqlValue(iface.getBaseUrl()));

					final Map<String, String> extraFields = Maps.newHashMap();
					if (iface.getExtraFields() != null) {
						for (String k : iface.getExtraFields().keySet()) {
							if (k != null && !k.trim().isEmpty()) {
								extraFields.put(asSqlValue(k), asSqlValue(iface.getExtraFields().get(k)));
							}
						}
					}
					mapIface.put("extraFields", extraFields);

					final Map<String, String> accessParams = Maps.newHashMap();
					if (iface.getAccessParams() != null) {
						for (String k : iface.getAccessParams().keySet()) {
							if (k != null && !k.trim().isEmpty()) {
								accessParams.put(asSqlValue(k), asSqlValue(iface.getAccessParams().get(k)));
							}
						}
					}
					mapIface.put("accessParams", accessParams);

					ifaces.add(mapIface);
				}
			}

			map.put("interfaces", ifaces);
		}

		return map;
	}

	public static Map<String, Object> asMapOfSqlValues(final Map<String, String> input) {
		final Map<String, Object> map = Maps.newHashMap();
		for (Map.Entry<String, String> e : input.entrySet()) {
			map.put(asSqlValue(e.getKey()), asSqlValue(e.getValue()));
		}
		return map;
	}

}
