/*
 * 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.votable.votable;

import java.util.SortedMap;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;

import eu.omp.irap.vespa.epntapclient.votable.model.Info;
import eu.omp.irap.vespa.epntapclient.votable.model.Table;
import eu.omp.irap.vespa.epntapclient.votable.model.VOTABLE;
import eu.omp.irap.vespa.votable.Consts;
import eu.omp.irap.vespa.votable.gui.AcquireTable;
import eu.omp.irap.vespa.votable.gui.AcquireTableInterface;
import eu.omp.irap.vespa.votable.utils.CantSendQueryException;
import eu.omp.irap.vespa.votable.utils.Network;
import eu.omp.irap.vespa.votable.votable.VOTableException.CantGetVOTableException;
import eu.omp.irap.vespa.votable.votable.VOTableException.VOTableIsNotValidException;
import eu.omp.irap.vespa.votable.votabledata.VOTableData;
import eu.omp.irap.vespa.votable.votabledata.VOTableDataParser;

/**
 * @author N. Jourdane
 */
public class VOTableCtrl implements AcquireTableInterface {

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

	/** The VOTable model */
	private VOTABLE voTable;

	/** The query sent to get the VOTable. */
	protected String query;

	/** The target URL of the registry or service used to get the VOTable. */
	protected String targetUrl;

	/** A VOTableData object, representing data stored in the VOTable. */
	protected VOTableData voTableData;

	/** The path of the local VOTable file. */
	protected String voTablePath;

	/** The Swing Worker to acquire the list of services. */
	private AcquireTable acquireTable;


	/**
	 * Constructor of VOTableController
	 */
	public VOTableCtrl() {
		voTableData = null;
		targetUrl = null;
		query = null;
		voTable = null;
		voTablePath = null;
	}

	/**
	 * Check the VOTable body.
	 *
	 * @param voTable The VOTable
	 * @throws VOTableIsNotValidException There is an error on the VOTable body.
	 */
	private static void checkVOTable(VOTABLE voTable) throws VOTableIsNotValidException {
		if (voTable.getRESOURCE().isEmpty()) {
			throw new VOTableIsNotValidException("The VOTable do not have any resource. "
					+ "See the local file for more informations.");
		}

		if (voTable.getRESOURCE().size() > 1) {
			throw new VOTableIsNotValidException(
					"VOTable with more than one resource are not yet supported.");
		}

		for (Info info : voTable.getRESOURCE().get(0).getINFO()) {
			if ("ERROR".equals(info.getValueAttribute())) {
				throw new VOTableIsNotValidException("There is an error in the VOTable:\n"
						+ info.getValue() + "\nPlease check your ADQL query. ");
			}
		}

		if (voTable.getRESOURCE().get(0).getLINKAndTABLEOrRESOURCE().size() > 1) {
			throw new VOTableIsNotValidException(
					"VOTable with more than one table are not yet supported. "
							+ "See the local file for more informations.");
		}

	}

	/**
	 * Parse a VOTable and override the result to the old one.
	 *
	 * @param newVOTablePath The path of the new VOTable.
	 */
	public void acquireVOTable(String newVOTablePath) {
		voTableData = null;
		voTablePath = newVOTablePath;
		targetUrl = null;
		query = null;

		if (acquireTable != null && !acquireTable.isDone()) {
			acquireTable.cancel(true);
		}
		acquireTable = new AcquireTable(this);
		acquireTable.execute();
	}

	/**
	 * Parse a VOTable and override the result to the old one, in another thread.
	 *
	 * @param newTargetUrl The new target URL of the registry or service used to get the VOTable.
	 * @param newQuery The new query to ask to the registry or service.
	 */
	public void acquireVOTable(String newTargetUrl, String newQuery) {
		targetUrl = newTargetUrl;
		query = newQuery;
		voTablePath = null;

		if (acquireTable != null && !acquireTable.isDone()) {
			acquireTable.cancel(true);
		}
		acquireTable = new AcquireTable(this);
		acquireTable.execute();
	}

	/**
	 * Parse a VOTable and override the result to the old one, in current thread, so it is a
	 *  blocking call.
	 *
	 * @param newTargetUrl The new target URL of the registry or service used to get the VOTable.
	 * @param newQuery The new query to ask to the registry or service.
	 */
	public void acquireVOTableBlocking(String newTargetUrl, String newQuery) {
		targetUrl = newTargetUrl;
		query = newQuery;
		voTablePath = null;
		acquireVOTableBlocking();
	}

	/**
	 * Parse the specified VOTable, in another thread.
	 *
	 * @param newVOTable The VOTable to parse, in its VOTABLE form.
	 */
	public void acquireVOTable(VOTABLE newVOTable) {
		voTable = newVOTable;
		targetUrl = null;
		query = null;
		voTablePath = null;

		if (acquireTable != null && !acquireTable.isDone()) {
			acquireTable.cancel(true);
		}
		acquireTable = new AcquireTable(this);
		acquireTable.execute();
	}

	/**
	 * Parse the specified VOTable, in current thread, so it is a blocking call.
	 *
	 * @param newVOTable The VOTable to parse, in its VOTABLE form.
	 */
	public void acquireVOTableBlocking(VOTABLE newVOTable) {
		voTable = newVOTable;
		targetUrl = null;
		query = null;
		voTablePath = null;
		acquireVOTableBlocking();
	}

	/**
	 * Download if needed, parse the result as XML, then parse it a VOTableData.
	 * This call return only when finished: he is blocking.
	 */
	private void acquireVOTableBlocking() {
		if (voTablePath == null && targetUrl != null) {
			try {
				LOGGER.info("call downloadVOTable()");;
				downloadVOTable();
				
			} catch (CantGetVOTableException e) {
				displayError("Can not download VOTable", e);
				return;
			}
		}
		if (voTable == null) {
			try {
				LOGGER.info("call parseVOTableXML()");;
				parseVOTableXML();
			} catch (VOTableException e) {
				displayError("Can not parse VOTable.", e);
				return;
			}
		}
		try {
			LOGGER.info("call parseVOTableData()");;
			parseVOTableData();
		} catch (VOTableException e) {
			displayError("Can not parse VOTable data.", e);
		}
	}

	@Override
	public void displayError(String message, Exception error) {
		LOGGER.log(Level.SEVERE, message, error);
	}

	@Override
	public void displayInfo(String shortMessage, String detailledMessage) {
		LOGGER.info(shortMessage + (detailledMessage == null ? "" : ": " + detailledMessage));
	}

	@Override
	public void displayTable() {
		// Nothing to fill in CLI mode
	}

	@Override
	public void downloadVOTable() throws CantGetVOTableException {
		if (voTablePath == null) {
			String url = targetUrl + "/sync";
			SortedMap<String, String> parameters = new TreeMap<>();
			parameters.put("REQUEST", Consts.QUERY_REQUEST);
			parameters.put("LANG", Consts.QUERY_LANG);
			parameters.put("FORMAT", Consts.QUERY_FORMAT);
			parameters.put("QUERY", query);

			String fullURL = Network.buildGetRequest(url, parameters);
			VOTableCtrl.LOGGER.info("Sending query at " + targetUrl + ": '" + fullURL + "'");

			try {
				voTablePath = Network.saveQuery(fullURL);
			} catch (CantSendQueryException e) {
				throw new CantGetVOTableException(e.getMessage(), e);
			}
			VOTableCtrl.LOGGER.fine("VOTable downloaded, stored in " + voTablePath);
		}
	}

	@Override
	public VOTABLE getVOTable() {
		return voTable;
	}

	/**
	 * @return The VOTableData object representing the data stored in the VOTable.
	 */
	public VOTableData getVOTableData() {
		return voTableData;
	}

	@Override
	public String getVOTablePath() {
		return voTablePath;
	}

	@Override
	public void parseVOTableData() throws VOTableException {
		checkVOTable(voTable);

		Table table = (Table) voTable.getRESOURCE().get(0).getLINKAndTABLEOrRESOURCE().get(0);

		VOTableDataParser dataCtrl;
		dataCtrl = new VOTableDataParser(voTablePath, table);
		dataCtrl.parseData();

		if (voTableData == null) {
			voTableData = dataCtrl.getData();
		} else {
			voTableData.append(dataCtrl.getData());
		}
	}

	@Override
	public void parseVOTableXML() throws VOTableException {
		voTable = VOTableXMLParser.parse(voTablePath);
	}

	@Override
	public void setWaitMode(boolean enableWaitcursor) {
		// No cursor in CLI mode.
	}

	/**
	 * @see eu.omp.irap.vespa.votable.gui.AcquireTableInterface#isUrlSet()
	 */
	@Override
	public boolean isUrlSet() {
		return targetUrl != null;
	}

}
