/*
 * This file is a part of EpnTAPClient.
 * This program aims to provide EPN-TAP support for software clients, like CASSIS spectrum analyzer.
 * See draft specifications: https://voparis-confluence.obspm.fr/pages/viewpage.action?pageId=559861
 * Copyright (C) 2016 Institut de Recherche en Astrophysique et Planétologie.
 *
 * This program is free software: you can
 * redistribute it and/or modify it under the terms of the GNU General Public License as published
 * by the Free Software Foundation, either version 3 of the License, or (at your option) any later
 * version. This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 * PURPOSE. See the GNU General Public License for more details. You should have received a copy of
 * the GNU General Public License along with this program. If not, see
 * <http://www.gnu.org/licenses/>.
 */

package eu.omp.irap.vespa.epntapclient.voresource;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;

import eu.omp.irap.vespa.epntapclient.epntap.service.ServiceCore;
import eu.omp.irap.vespa.epntapclient.voresource.VOResourceException.CantGetVOResourceException;
import eu.omp.irap.vespa.epntapclient.voresource.VOResourceException.CantReadVOResourceException;
import eu.omp.irap.vespa.epntapclient.voresource.VOResourceException.VOResourceIsNotValidException;
import eu.omp.irap.vespa.epntapclient.voresource.model.Resource;
import eu.omp.irap.vespa.votable.utils.CantSendQueryException;
import eu.omp.irap.vespa.votable.utils.Network;
import eu.omp.irap.vespa.votable.utils.StringJoiner;
import eu.omp.irap.vespa.votable.utils.XmlUtils;

/**
 * @author N. Jourdane
 */
public class VOResourceCtrl {

	/** The URL used to get the resources in JSON format. */
	private static final String GET_JSONRESOURCES_URL = "http://voparis-registry.obspm.fr/vo/ivoa/1/voresources/search";

	/** The URL used to get the resources in VOResource (XML) format. */
	private static final String GET_VORESOURCE_URL = "http://voparis-registry.obspm.fr/vo/ivoa/1/voresources.xml";

	/** The logger for the class VOResourceController. */
	private static final Logger LOGGER = Logger.getLogger(VOResourceCtrl.class.getName());

	/** The maximum number of resulting resources. */
	private static final int MAX_VORESOURCES = 100;

	/** The URL of the TR namespace, since it is not present in the dowloaded VOResource. */
	private static final String NAMESPACE_TR = "http://www.ivoa.net/xml/VODataService/v1.1";

	/** The URL of the VS namespace, since it is not present in the dowloaded VOResource. */
	private static final String NAMESPACE_VS = "http://www.ivoa.net/xml/TAPRegExt/v1.0";

	/** The new schema location, since one in the dowloaded VOResource is not compliant. */
	private static final String SCHEMA_LOCATION = "http://www.ivoa.net/xml/RegistryInterface/v1.0 "
			+ "http://www.ivoa.net/xml/RegistryInterface/v1.0 "
			+ "http://www.ivoa.net/xml/VODataService/v1.1 "
			+ "http://www.ivoa.net/xml/TAPRegExt/v1.0";


	/** Private constructor to hide the default public one. */
	private VOResourceCtrl() {
	}

	/**
	 * Get the list of ivoIDs of all resources which implements the specified core.
	 *
	 * @param core The service core (ie. ObsCore, EpnCore, etc.)
	 * @return A list of ivoIDs
	 * @throws VOResourceException Can not get the VOResource.
	 */
	public static List<String> getIvoidResources(ServiceCore core) throws VOResourceException {
		List<String> ivoidResources;
		Map<String, String> parameters = new HashMap<>();

		parameters.put("keywords", "datamodel:\"" + core.toString() + "\"");
		parameters.put("max", String.valueOf(MAX_VORESOURCES));
		String query = Network.buildGetRequest(GET_JSONRESOURCES_URL, parameters);
		try {
			ivoidResources = parseIvoidResources(Network.readJson(query));
		} catch (CantSendQueryException e) {
			throw new CantGetVOResourceException(GET_VORESOURCE_URL, e);
		}
		LOGGER.info("Got resources: " + StringJoiner.join(ivoidResources));
		return ivoidResources;
	}

	/**
	 * Get a resource from its identifier.
	 *
	 * @param identifier The resource identifier.
	 * @return The returned resource.
	 * @throws VOResourceException Can not get the VOResource.
	 */
	public static Resource getVOresource(String identifier) throws VOResourceException {
		Map<String, String> parameters = new HashMap<>();
		parameters.put("identifier", identifier);
		String voResourcePath;

		try {
			LOGGER.info("Trying to get VOResource '" + identifier + "'...");
			String query = Network.buildGetRequest(GET_VORESOURCE_URL, parameters);
			voResourcePath = Network.saveQuery(query);
		} catch (CantSendQueryException e) {
			throw new CantGetVOResourceException(GET_VORESOURCE_URL, e);
		}
		LOGGER.info("VOResource downloaded in " + voResourcePath);

		try {
			changeNamespaces(voResourcePath);
		} catch (IOException e) {
			throw new CantReadVOResourceException("The VOResource file can not be modified.", e);
		}

		Resource voResource;
		try {
			File inputStream = new File(voResourcePath);

			Source source = new StreamSource(inputStream);
			JAXBContext jc = JAXBContext.newInstance(Resource.class);
			Unmarshaller unmarshaller = jc.createUnmarshaller();
			voResource = unmarshaller.unmarshal(source, Resource.class).getValue();

		} catch (JAXBException e) {
			throw new VOResourceIsNotValidException(voResourcePath, e);
		}
		return voResource;
	}

	/**
	 * Get a list of Resources from a list of ivoid.
	 *
	 * @param ivoidResources A list of ivoIds
	 * @return A list of Resources corresponding to the ivoIDs.
	 * @throws VOResourceException Can not get the VOResource.
	 */
	public static List<Resource> getVOResources(List<String> ivoidResources)
			throws VOResourceException {
		List<Resource> resources = new ArrayList<>();
		for (String ivoid : ivoidResources) {
			resources.add(getVOresource(ivoid));
		}
		return resources;
	}

	/**
	 * Get the list of ivoIDs of all resources which implements the specified core and match with
	 * the keywords.
	 *
	 * @param core The service core (ie. ObsCore, EpnCore, etc.)
	 * @param keywords A list of keywords to filter the result. The keywords are the subjects of the
	 *            resources, for example, 'Space plasmas'.
	 * @return A list of ivoIDs
	 * @throws CantGetVOResourceException Can not get the VOResource.
	 */
	public static List<String> getVOResources(ServiceCore core, List<String> keywords)
			throws CantGetVOResourceException {
		List<String> ivoidResources;
		Map<String, String> parameters = new HashMap<>();
		String subjects = StringJoiner.join(keywords);
		parameters.put("keywords",
				"datamodel:\"" + core.toString() + "\" subjects:\"" + subjects + "\"");
		parameters.put("max", String.valueOf(MAX_VORESOURCES));

		String query = Network.buildGetRequest(GET_JSONRESOURCES_URL, parameters);
		try {
			ivoidResources = parseIvoidResources(Network.readJson(query));
		} catch (CantSendQueryException e) {
			throw new CantGetVOResourceException(GET_VORESOURCE_URL, e);
		}
		LOGGER.info("Got resources: " + StringJoiner.join(ivoidResources));
		return ivoidResources;
	}

	/**
	 * Change the namespaces of the VOResource to make the XML file valid against its XSD.
	 *
	 * @param voResourcePath The file path of the VOResource.
	 * @throws IOException Can not read or write on the VOResource file.
	 */
	private static void changeNamespaces(String voResourcePath) throws IOException {
		Map<String, String> attributes = new HashMap<>();
		attributes.put("xsi:schemaLocation", SCHEMA_LOCATION);
		attributes.put("xmlns:vs", NAMESPACE_VS);
		attributes.put("xmlns:tr", NAMESPACE_TR);
		XmlUtils.changeRootAttributes(voResourcePath, attributes);
	}

	/**
	 * Parse a JSON query result and returns a list of ivoid.
	 *
	 * @param getIvoidsResult The JSON object result a query. See {@link #GET_JSONRESOURCES_URL}.
	 * @return A list of ivoIDs.
	 */
	private static List<String> parseIvoidResources(JsonObject getIvoidsResult) {
		JsonArray resources = getIvoidsResult.get("resources").getAsJsonArray();
		List<String> ivoidResources = new ArrayList<>();
		for (JsonElement e : resources) {
			ivoidResources.add(e.getAsJsonObject().get("identifier").getAsString());
		}
		return ivoidResources;
	}
}
