// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.elevation;

import java.awt.Color;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;

import org.openstreetmap.josm.tools.CheckParameterUtil;

/**
 * Provides a set of color maps to map an elevation value to a color.
 * @author Olli
 *
 */
public class ColorMap {
	private List<ColorMapEntry> colorList;
	private String name;
	private static HashMap<String, ColorMap> colorMaps;

	static {
		colorMaps = new HashMap<>();
	}

	// Private ctor to enforce use of create
	private ColorMap() {
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * Gets the color according to the given elevation value.
	 *
	 * @param elevation the elevation
	 * @return the color
	 */
	public Color getColor(int elevation) {
		// empty color map?
		if (colorList == null || colorList.size() == 0) {
			return Color.white;
		}

		// out of range?
		if (elevation < colorList.get(0).ele) {
			return colorList.get(0).getColor();
		}

		int last = colorList.size() - 1;
		if (elevation > colorList.get(last).ele) {
			return colorList.get(last).getColor();
		}

		// find elevation section
		for (int i = 0; i < last; i++) {
			ColorMapEntry e1 = colorList.get(i);
			ColorMapEntry e2 = colorList.get(i + 1);

			// elevation within range?
			if (e1.getEle() <= elevation && e2.getEle() >= elevation) {

				// interpolate color between both
				double val = (elevation - e1.getEle()) / (double)(e2.getEle() - e1.getEle());
				return interpolate(e1.getColor(), e2.getColor(), val);
			}
		}

		// here we should never end!
		throw new RuntimeException("Inconsistent color map - found no entry for elevation " + elevation);
	}


	/**
	 * Gets the color map with the given name.
	 *
	 * @param name the name
	 * @return the map or <code>null</code>, if no such map exists
	 */
	public static ColorMap getMap(String name) {
		if (colorMaps.containsKey(name)) {
			return colorMaps.get(name);
		}
		return null;
	}

	/**
	 * Gets the number of available color maps.
	 *
	 * @return the int
	 */
	public static int size() {
		return colorMaps != null ? colorMaps.size() : 0;
	}


	/**
	 * Gets the available color map names.
	 *
	 * @param name the name
	 * @return the map or <code>null</code>, if no such map exists
	 */
	public static String[] getNames() {
		return colorMaps.keySet().toArray(new String[size()]);
	}

	private static void registerColorMap(ColorMap newMap) {
		CheckParameterUtil.ensureParameterNotNull(newMap);
		colorMaps.put(newMap.getName(), newMap);
	}

	public static void unregisterColorMap(String name) {
		if (colorMaps.containsKey(name)) {
			colorMaps.remove(name);
		}
	}

	public static Color interpolate(java.awt.Color c1, java.awt.Color c2, double ratio) {
		double r1 = 1 -ratio;
		// clip
		if (r1 < 0) r1 = 0d;
		if (r1 > 1) r1 = 1d;
		double r2 = 1 - r1;

		int r = (int) Math.round((r1 * c1.getRed()) + (r2 * c2.getRed()));
		int g = (int) Math.round((r1 * c1.getGreen()) + (r2 * c2.getGreen()));
		int b = (int) Math.round((r1 * c1.getBlue()) + (r2 * c2.getBlue()));
		return new Color(r, g, b);
	}

	/**
	 * Creates a color map using the given colors/elevation values.
	 * Both arrays must have same length.
	 *
	 * @param name the name of the color map
	 * @param colors the array containing the colors
	 * @param ele the elevation values
	 * @return the color map
	 */
	public static ColorMap create(String name, Color[] colors, int[] ele) {
		CheckParameterUtil.ensureParameterNotNull(colors);
		CheckParameterUtil.ensureParameterNotNull(ele);

		if (colors.length != ele.length) {
			throw new IllegalArgumentException("Arrays colors and ele must have same length: " + colors.length + " vs " + ele.length);
		}

		ColorMap map = new ColorMap();
		map.colorList = new ArrayList<>();
		map.name = name;
		for (int i = 0; i < ele.length; i++) {
			map.colorList.add(map.new ColorMapEntry(colors[i], ele[i]));
		}

		// sort by elevation
		Collections.sort(map.colorList);

		registerColorMap(map);
		return map;
	}


	class ColorMapEntry implements Comparable<ColorMapEntry> {
		private final int ele; // limit
		private final Color color;

		public ColorMapEntry(Color color, int ele) {
			super();
			this.color = color;
			this.ele = ele;
		}

		public int getEle() {
			return ele;
		}

		public Color getColor() {
			return color;
		}

		@Override
		public int compareTo(ColorMapEntry o) {
			return this.ele - o.ele;
		}
	}
}
