package eu.dnetlib.enabling.ui.server;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringReader;
import java.net.URL;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.context.ResourceLoaderAware;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.ResourcePatternUtils;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import eu.dnetlib.enabling.is.lookup.rmi.ISLookUpService;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryException;
import eu.dnetlib.enabling.is.registry.rmi.ISRegistryService;
import eu.dnetlib.enabling.tools.ServiceLocator;
import eu.dnetlib.enabling.ui.common.beans.DHNInfo;
import eu.dnetlib.enabling.ui.common.beans.RepositoryDetailsInfo;
import eu.dnetlib.enabling.ui.common.services.MyGwtException;
import eu.dnetlib.enabling.ui.common.services.RegistryService;
import eu.dnetlib.enabling.ui.server.util.ResourceSubmitter;

public class RegistryServlet extends RemoteServiceServlet implements RegistryService, ResourceLoaderAware {

	private static final long serialVersionUID = 8541042711174183040L;
	private ServiceLocator<ISRegistryService> isRegistryLocator;
	private ServiceLocator<ISLookUpService> isLookUpLocator;
	private ResourceSubmitter resourceSubmitter;

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

	/**
	 * resource loader.
	 */
	private ResourceLoader resourceLoader;

	public RegistryServlet() {
		super();
	}

	public ServiceLocator<ISRegistryService> getIsRegistryLocator() {
		return isRegistryLocator;
	}

	@Required
	public void setIsRegistryLocator(final ServiceLocator<ISRegistryService> isRegistryLocator) {
		this.isRegistryLocator = isRegistryLocator;
	}

	public ServiceLocator<ISLookUpService> getIsLookUpLocator() {
		return isLookUpLocator;
	}

	@Required
	public void setIsLookUpLocator(final ServiceLocator<ISLookUpService> isLookUpLocator) {
		this.isLookUpLocator = isLookUpLocator;
	}

	@Override
	public Boolean deleteProfile(final String id) {
		try {
			return isRegistryLocator.getService().deleteProfile(id);
		} catch (final Exception e) {
			return false;
		}
	}

	@Override
	public Boolean removeAllProfiles(final List<String> list) {
		final ISRegistryService registry = isRegistryLocator.getService();
		for (final String profId : list) {
			try {
				registry.deleteProfile(profId);
			} catch (final Exception e) {
			}
		}
		return true;
	}

	@Override
	public String invalidateProfile(final String id) {
		try {
			return isRegistryLocator.getService().invalidateProfile(id);
		} catch (final Exception e) {
			return null;
		}
	}

	@Override
	public String validateProfile(final String id) {
		try {
			return isRegistryLocator.getService().validateProfile(id);
		} catch (final Exception e) {
			return null;
		}
	}

	@Override
	public String importProfiles(final String path) throws MyGwtException {
		log.info("importing profiles in " + path);

		int cOk = 0;
		int cTotal = 0;

		try {
			final File dirDone = new File(path + "/done_" + UUID.randomUUID());
			boolean doneDirCreated = false;

			if (!(new File(path)).isDirectory())
				return "ERROR: invalid directory";

			for (final Resource res : ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources("file://" + path + "/*")) {
				if (res.getFile().isFile()) {
					cTotal++;
					if (resourceSubmitter.registerFile(res)) {
						if (!doneDirCreated)
							doneDirCreated = dirDone.mkdirs();

						// move the file only if we could create the dest dir
						if (doneDirCreated)
							res.getFile().renameTo(new File(dirDone, res.getFile().getName()));
						cOk++;
					}
				}
			}
		} catch (final IOException e) {
			log.info("got some exception: " + e);
			throw new MyGwtException("cannot find profiles to import", e.toString());
		}

		return cOk + "/" + cTotal;
	}

	@Override
	public Boolean uploadDefaultSchemas() {
		return resourceSubmitter.submitDefaultSchemas();
	}

	@Override
	public Boolean updateDHN(final DHNInfo info) throws MyGwtException {
		final String textProps = "<SERVICE_PROPERTIES>" + "<PROPERTY key='name' value='" + info.getName() + "'/>" + "<PROPERTY key='latitude' value='"
				+ info.getLat() + "'/>" + "<PROPERTY key='longitude' value='" + info.getLng() + "'/>" + "<PROPERTY key='timezone' value='"
				+ info.getTimezone() + "'/>" + "</SERVICE_PROPERTIES>";

		try {
			return isRegistryLocator.getService().updateProfileNode(info.getIdentifier(), "//SERVICE_PROPERTIES", textProps);
		} catch (final ISRegistryException e) {
			throw new MyGwtException(e.getMessage(), "");
		}
	}

	@Override
	public String addRepository(RepositoryDetailsInfo repo) throws MyGwtException {

		try {
			SAXReader reader = new SAXReader();
			Document doc = reader.read(getClass().getResourceAsStream("repository-tmpl.xml"));

			applyRepositoryDetails(doc, repo);

			return isRegistryLocator.getService().insertProfileForValidation("RepositoryServiceResourceType", doc.asXML());
		} catch (Exception e) {
			log.error("Error adding repository", e);
			throw new MyGwtException(e.getMessage(), "");
		}
	}

	@Override
	public void updateRepository(String id, RepositoryDetailsInfo repo) throws MyGwtException {
		try {
			String profileSource = isLookUpLocator.getService().getResourceProfile(id);
			SAXReader reader = new SAXReader();
			Document doc = reader.read(new StringReader(profileSource));

			applyRepositoryDetails(doc, repo);

			isRegistryLocator.getService().updateProfile(id, doc.asXML(), "RepositoryServiceResourceType");
		} catch (Exception e) {
			log.error("Error adding repository", e);
			throw new MyGwtException(e.getMessage(), "");
		}
	}

	/**
	 * Applies all values from a repo details info bean into a repository profile DOM, overwriting existing values but
	 * preserving information not represented in the info bean.
	 * 
	 * @param doc
	 *            repository profile DOM
	 * @param repo
	 *            repo details bean
	 */
	protected void applyRepositoryDetails(final Document doc, final RepositoryDetailsInfo repo) {
		float lng = repo.getLng();
		float lat = repo.getLat();

		if (lat == 0 && lng == 0) {
			try {
				Map<String, Float> geoInfo = obtainGeoGraphichInfoForURL(repo.getBaseUrl());
				if (geoInfo.containsKey("latitude"))
					lat = geoInfo.get("latitude");
				if (geoInfo.containsKey("longitude"))
					lng = geoInfo.get("longitude");
			} catch (Exception e) {
				log.warn("Failed discovery of Geographic Info");
			}
		}

		doc.selectSingleNode("//OFFICIAL_NAME").setText(repo.getName());
		doc.selectSingleNode("//ENGLISH_NAME").setText(repo.getEnglishName());
		doc.selectSingleNode("//REPOSITORY_INSTITUTION").setText(repo.getInstitution());
		doc.selectSingleNode("//ICON_URI").setText(repo.getIcon());
		doc.selectSingleNode("//ADMIN_INFO").setText(repo.getEmail());
		doc.selectSingleNode("//REPOSITORY_WEBPAGE").setText(repo.getUrl());
		doc.selectSingleNode("//TYPOLOGY").setText(repo.getPlatform());
		doc.selectSingleNode("//COUNTRY").setText(repo.getCountry());
		doc.selectSingleNode("//LONGITUDE").setText(Float.toString(lng));
		doc.selectSingleNode("//LATITUDE").setText(Float.toString(lat));
		doc.selectSingleNode("//TIMEZONE").setText(Float.toString(repo.getTimezone()));

		final Element extraNode = (Element) doc.selectSingleNode("//EXTRA_FIELDS");
		for (Map.Entry<String, String> entry : repo.getExtraFields().entrySet()) {
			Element field = (Element) extraNode.selectSingleNode("./FIELD[./key='" + entry.getKey() + "']");
			if (field == null)
				field = extraNode.addElement("FIELD");
			else
				field.clearContent();
			field.addElement("key").addText(entry.getKey());
			field.addElement("value").addText(entry.getValue());
		}

		Element ifcs = ((Element) doc.selectSingleNode("//INTERFACES"));
		ifcs.clearContent();
		Element ifcHarv = ifcs.addElement("INTERFACE");
		

		Element protHarv = ifcHarv.addElement("ACCESS_PROTOCOL");
		protHarv.setText(repo.getHarvProtocol());
		if (repo.getHarvUsername() != null)
			protHarv.addAttribute("username", repo.getHarvUsername());
		if (repo.getHarvPassword() != null)
			protHarv.addAttribute("password", repo.getHarvPassword());
		if (repo.getHarvFilter() != null)
			protHarv.addAttribute("filter", repo.getHarvFilter());
		
		
		ifcHarv.addElement("BASE_URL").setText(repo.getBaseUrl());
		ifcHarv.addElement("FORMATS").addElement("FORMAT").setText(repo.getHarvMetadataFormat());
		Element sets = ifcHarv.addElement("SETS");
		if (repo.getHarvSets() != null)
			for (String set : repo.getHarvSets())
				sets.addElement("SET").addText(set);

		if (repo.getFulltextProtocol() != null) {
			Element ifcFulltext = ifcs.addElement("INTERFACE");
			Element protFulltext = ifcFulltext.addElement("ACCESS_PROTOCOL");
			protFulltext.setText(repo.getFulltextProtocol());
			if (repo.getFulltextUsername() != null)
				protFulltext.addAttribute("username", repo.getFulltextUsername());
			if (repo.getFulltextPassword() != null)
				protFulltext.addAttribute("password", repo.getFulltextPassword());
			if (repo.getFulltextFilter() != null)
				protFulltext.addAttribute("filter", repo.getFulltextFilter());
			ifcFulltext.addElement("BASE_URL").setText(repo.getFulltextUrl());
			ifcFulltext.addElement("FORMATS").addElement("FORMAT").setText("fulltext");
			ifcFulltext.addElement("SETS");
		}

	}

	protected Map<String, Float> obtainGeoGraphichInfoForURL(String address) throws IOException {
		URL geoServerURL = new URL("http://www.datasciencetoolkit.org/ip2coordinates/" + new URL(address).getHost());

		String json = "";
		BufferedReader br = new BufferedReader(new InputStreamReader(geoServerURL.openStream()));
		String str = "";
		while ((str = br.readLine()) != null) {
			json += str;
		}
		br.close();

		Map<String, Float> response = new HashMap<String, Float>();

		for (String s : json.replaceAll(".+\\{", "").replaceAll("\\}.+", "").split(",")) {
			if (s.contains("latitude")) {
				response.put("latitude", Float.valueOf(s.substring(s.indexOf(":") + 1)));
			} else if (s.contains("longitude")) {
				response.put("longitude", Float.valueOf(s.substring(s.indexOf(":") + 1)));
			}
		}
		return response;
	}

	@Override
	public void setResourceLoader(final ResourceLoader resourceLoader) {
		this.resourceLoader = resourceLoader;
	}

	public ResourceSubmitter getResourceSubmitter() {
		return resourceSubmitter;
	}

	@Required
	public void setResourceSubmitter(ResourceSubmitter resourceSubmitter) {
		this.resourceSubmitter = resourceSubmitter;
	}

}
