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

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

import eu.omp.irap.vespa.votable.Consts;

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

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

	/** The user agent used for the requests. */
	private static final String USER_AGENT = "Mozilla/5.0";


	/** Private constructor to hide the implicit public one. */
	private Network() {
	}

	/**
	 * Build a GET request from a target URL and parameters.
	 *
	 * @param url The target URL.
	 * @param parameters A map representing the parameters to put in the query, as <name, value>.
	 * @return A string containing the full request.
	 */
	public static String buildGetRequest(String url, Map<String, String> parameters) {
		StringJoiner paramJoiner = new StringJoiner("&");
		for (Map.Entry<String, String> param : parameters.entrySet()) {
			try {
				String value = URLEncoder.encode(param.getValue(), Consts.ENCODING)
						.replace("+", "%20")
						.replace(".", "%2E").replace("-", "%2D").replace("*", "%2A")
						.replace("_", "%5F");
				paramJoiner.add(param.getKey() + "=" + value);
			} catch (UnsupportedEncodingException e) {
				throw new IllegalArgumentException("Unsupported Encoding on key=" + param.getKey()
						+ " and value=" + param.getValue(), e);
			}
		}
		return url + "?" + paramJoiner;
	}

	/**
	 * Send a request, download the returned JSon file, then parse it and returns the corresponding
	 * JSon object.
	 *
	 * @param request The request sent to get the JSon file.
	 * @return The JSon object corresponding to the JSon text returned by the query.
	 * @throws CantSendQueryException The query can not be sent.
	 */
	public static JsonObject readJson(String request) throws CantSendQueryException {
		try (BufferedReader reader = new BufferedReader(
				new InputStreamReader(new URL(request).openStream()))) {
			StringBuilder buffer = new StringBuilder();
			int read;
			char[] chars = new char[1024];
			while ((read = reader.read(chars)) != -1) {
				buffer.append(chars, 0, read);
			}
			return new JsonParser().parse(buffer.toString()).getAsJsonObject();
		} catch (IOException e) {
			throw new CantSendQueryException("Can not send the Json query " + request, e);
		}
	}

	/**
	 * Send a query and save the result in a temporary file.
	 *
	 * @param url The URL of the query
	 * @return The path of the downloaded file.
	 * @throws CantSendQueryException The query can not be sent.
	 */
	public static String saveQuery(String url) throws CantSendQueryException {
		File lOutTmpFile = null;
		try {
			lOutTmpFile = File.createTempFile("epnTapLib_", ".xml");
			StringBuilder result = Network.sendGet(url);

			try (PrintWriter writer = new PrintWriter(lOutTmpFile, Consts.ENCODING)) {
				writer.println(result);
			} catch (FileNotFoundException | UnsupportedEncodingException e) {
				throw new CantSendQueryException(
						"Can not save query on the file " + lOutTmpFile.getAbsolutePath(), e);
			}

		} catch (IOException e) {
			throw new CantSendQueryException("Cannot create temp file: " + e.getMessage(), e);
		}

		return lOutTmpFile.getAbsolutePath();
	}

	/**
	 * @param request The request, containing the target URL and the parameters.
	 * @return A string builder containing the result of the query.
	 * @throws CantSendQueryException The query can not be sent.
	 */
	private static StringBuilder sendGet(String request) throws CantSendQueryException {
		String inputLine;
		StringBuilder response = new StringBuilder();
		URL obj;

		try {
			obj = new URL(request);
		} catch (java.net.MalformedURLException e) {
			throw new CantSendQueryException("Malformated URL: " + request, e);
		}

		HttpURLConnection con;

		try {
			con = (HttpURLConnection) obj.openConnection();
			con.setRequestMethod("GET");
		} catch (IOException e) {
			throw new CantSendQueryException(request, e);
		}

		con.setRequestProperty("User-Agent", Network.USER_AGENT);
		int code = -1;

		try {
			code = con.getResponseCode();
		} catch (IOException e) {
			throw new CantSendQueryException(
					"Can not get the response code for the query " + request,
					e);
		}

		if (code != HttpURLConnection.HTTP_OK && code != HttpURLConnection.HTTP_BAD_REQUEST) {
			throw new CantSendQueryException(
					"Bad response code (" + code + ") for the query " + request + ".");
		}

		try (BufferedReader in = new BufferedReader(new InputStreamReader(con.getInputStream()))) {
			while ((inputLine = in.readLine()) != null) {
				response.append(inputLine);
			}
		} catch (IOException e1) {
			Network.LOGGER.log(Level.WARNING, "Can not get input stream of the query " + request,
					e1);
			try (BufferedReader in = new BufferedReader(
					new InputStreamReader(con.getErrorStream()))) {
				while ((inputLine = in.readLine()) != null) {
					response.append(inputLine);
				}
			} catch (IOException e2) {
				throw new CantSendQueryException(
						"Can not get the error stream of the query " + request,
						e2);
			}
		}

		return response;
	}
}
