package tim.prune.gui.map;

import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;

import tim.prune.I18nManager;


/**
 * Class to represent any map source, consisting of one or two layers
 */
public abstract class MapSource
{
	/** Name for this source */
	private final String _name;
	/** File extensions */
	private final MapLayer[] _layers = new MapLayer[2];


	/**
	 * Constructor
	 * @param inName name of source
	 */
	protected MapSource(String inName)
	{
		String name = inName;
		if (name == null || name.trim().equals("")) {
			name = I18nManager.getText("mapsource.unknown");
		}
		_name = name;
	}

	/**
	 * @return name
	 */
	public String getName() {
		return _name;
	}

	/**
	 * @return the number of layers used in this source
	 */
	public int getNumLayers() {
		return (_layers[0] == null ? 0 : 1)
			+ (_layers[1] == null ? 0 : 1);
	}

	/** Base url for this source */
	public String getBaseUrl(int inLayerNum) {
		if (inLayerNum < 0 || inLayerNum >= getNumLayers()) {return "";}
		return _layers[inLayerNum].getBaseUrl();
	}

	/** site name without protocol or www. */
	public String getSiteName(int inLayerNum) {
		if (inLayerNum < 0 || inLayerNum >= getNumLayers()) {return "";}
		return _layers[inLayerNum].getSiteName();
	}

	/**
	 * @return the file extension for the specified layer
	 */
	public final String getFileExtension(int inLayerNum) {
		if (inLayerNum < 0 || inLayerNum >= getNumLayers()) {return "";}
		return _layers[inLayerNum].getExtension();
	}

	/**
	 * Add a new layer to the source
	 * @param inBaseUrl base url
	 * @param inExt file extension
	 */
	protected void addLayer(String inBaseUrl, String inExt) {
		if (inBaseUrl != null) {
			_layers[getNumLayers()] = new MapLayer(inBaseUrl, inExt);
		}
	}

	/**
	 * Make the URL to get the specified tile
	 * @param inLayerNum number of layer, from 0 (base) to getNumLayers-1 (top)
	 * @param inZoom zoom level
	 * @param inX x coordinate of tile
	 * @param inY y coordinate of tile
	 * @return URL as string
	 */
	public abstract String makeURL(int inLayerNum, int inZoom, int inX, int inY);

	/**
	 * @return the maximum zoom level for this source
	 */
	public abstract int getMaxZoomLevel();

	/**
	 * Make a relative file path from the base directory including site name
	 * @param inLayerNum layer number
	 * @param inZoom zoom level
	 * @param inX x coordinate
	 * @param inY y coordinate
	 * @return relative file path as String
	 */
	public String makeFilePath(int inLayerNum, int inZoom, int inX, int inY)
	{
		return getSiteName(inLayerNum) + inZoom + "/" + inX + "/" + inY + "." + getFileExtension(inLayerNum);
	}

	/**
	 * Checks the given url for having the right prefix and trailing slash
	 * @param inUrlString url to check
	 * @return validated url with correct prefix and trailing slash, or null
	 */
	public static String fixBaseUrl(String inUrlString)
	{
		if (inUrlString == null || inUrlString.isEmpty()) {
			return null;
		}
		if (hasPlaceholders(inUrlString)) {
			return fixBaseUrlWithPlaceholders(inUrlString);
		}
		String urlstr = inUrlString;

		// check prefix
		URL url = makeUrl(urlstr);
		if (url == null)
		{
			// add the http protocol and try again
			urlstr = "http://" + urlstr;
			url = makeUrl(urlstr);
		}

		// url must be valid and host must contain at least one dot (but not two consecutively)
		if (url == null
			|| url.getHost().indexOf('.') < 0
			|| url.getHost().contains("..")
			|| !placeHoldersOk(url.getPath())) {
			return null;
		}
		// Add trailing slash if not already present
		if (!urlstr.endsWith("/")) {
			urlstr = urlstr + "/";
		}
		return urlstr;
	}

	/** @return true if the given url string contains any curly brackets */
	private static boolean hasPlaceholders(String inUrlString) {
		return inUrlString.contains("{") || inUrlString.contains("}");
	}

	private static String fixBaseUrlWithPlaceholders(String inUrlString)
	{
		if (!placeHoldersOk(inUrlString)) {
			return null;
		}
		String urlstr = inUrlString;
		String urlWithoutPlaceholders = inUrlString.replace("{x}", "-x-")
			.replace("{y}", "-y-").replace("{z}", "-z-");
		URL url = makeUrl(urlWithoutPlaceholders);
		if (url == null)
		{
			// add the http protocol and try again
			urlstr = "http://" + urlstr;
			url = makeUrl("http://" + urlWithoutPlaceholders);
		}
		// url must be valid and host must contain at least one dot (but not two consecutively)
		if (url == null
			|| url.getHost().indexOf('.') < 0
			|| url.getHost().contains("..")
			|| !placeHoldersOk(url.getPath())) {
			return null;
		}
		return urlstr;
	}

	private static URL makeUrl(String urlString)
	{
		try {
			return new URI(urlString.replace('[', 'w').replace(']', 'w')).toURL();
		}
		catch (MalformedURLException | URISyntaxException | IllegalArgumentException e) {
			return null;
		}
	}

	/**
	 * Check that all the required placeholders are present
	 * @param path path to tile
	 * @return true if no placeholders or all placeholders, false if error
	 */
	private static boolean placeHoldersOk(String path)
	{
		if (path.indexOf('{') < 0 && path.indexOf('}') < 0) {
			return true; // no placeholders
		}
		boolean expectLetter = false, expectClose = false;
		boolean foundX = false, foundY = false, foundZ = false;
		for (char c : path.toCharArray())
		{
			if (expectClose) {
				if (c != '}') {return false;}
				expectClose = false;
			}
			else if (expectLetter)
			{
				switch (c) {
					case 'x': foundX = true; break;
					case 'y': foundY = true; break;
					case 'z': foundZ = true; break;
					default: return false;
				}
				expectLetter = false;
				expectClose = true;
			}
			else if (c == '{') {
				expectLetter = true;
			}
			else if (c == '}') {
				return false; // close not expected
			}
		}
		return foundX && foundY && foundZ;
	}


	/**
	 * @return string which can be written to the Config
	 */
	public abstract String getConfigString();

	/**
	 * @return semicolon-separated list of base urls and extensions in order
	 */
	public String getSiteStrings()
	{
		StringBuilder sb = new StringBuilder();
		for (int i=0; i<getNumLayers(); i++)
		{
			String url = getBaseUrl(i);
			if (url != null)
			{
				sb.append(url);
				sb.append(';');
				sb.append(getFileExtension(i));
				sb.append(';');
			}
		}
		return sb.toString();
	}

	public void setDoubleRes(int inLayerNum) {
		if (inLayerNum < 0 || inLayerNum >= getNumLayers()) {return;}
		_layers[inLayerNum].setDoubleRes();
	}
	public boolean isDoubleRes(int inLayerNum) {
		return inLayerNum >= 0 && inLayerNum < getNumLayers() && _layers[inLayerNum].isDoubleRes();
	}
}
