package net.returnvoid.tools;

import net.returnvoid.color.ColorHelper;
import net.returnvoid.color.ColorSpace;
import net.returnvoid.color.HSBColor;
import net.returnvoid.color.LCHColor;
import net.returnvoid.color.LabColor;
import net.returnvoid.color.RGBColor;
import net.returnvoid.color.RColor;
import net.returnvoid.color.XYZColor;
import processing.core.PVector;

/*
 * This code is copyright (c) Diana Lange 2017
 *
 * The library is published under the Creative Commons license NonCommercial 4.0.
 * Please check https://creativecommons.org/licenses/by-nc/4.0/ for more information.
 * 
 * This program is distributed in the hope that it will be useful, but without any warranty.
 */

/**
 * A class for mathematical operations.
 * 
 * @author Diana Lange
 *
 */
public class RMath {

	/**
	 * float of Math.PI ~ 3.1416.
	 */
	public final static float PI = (float) Math.PI;

	/**
	 * float of 2 * Math.PI ~ 6.2832.
	 */
	public final static float TWO_PI = (float) (2 * Math.PI);

	/**
	 * No objects of this class are allowed.
	 */
	private RMath() {
	}

	/**
	 * Converts polar coordinates (angle and radius) to Cartesian coordinates
	 * (x, y).
	 * 
	 * @param a
	 *            The angle.
	 * @param r
	 *            The radius.
	 * @return The Cartesian coordinates.
	 */
	static public PVector polarToCartesian(Float a, float r) {
		return new PVector((float) (Math.cos(a) * r), (float) (Math.sin(a) * r));
	}

	/**
	 * Converts polar coordinates (angle and radius) to Cartesian coordinates
	 * (x, y). The coordinates will be translated by the vector <b>base</b>.
	 * 
	 * @param base
	 *            The base (translation vector).
	 * @param a
	 *            The angle.
	 * @param r
	 *            The radius.
	 * @return The Cartesian coordinates.
	 */
	static public PVector polarToCartesian(PVector base, float a, float r) {
		return new PVector((float) (base.x + Math.cos(a) * r), (float) (base.y + Math.sin(a) * r));
	}

	/**
	 * Converts polar coordinates (angle and radius) to Cartesian coordinates
	 * (x, y). The coordinates will be translated by the vector <b>base</b>.
	 * 
	 * @param basex
	 *            The x coordinate of a translation vector.
	 * @param basey
	 *            The y coordinate of a translation vector.
	 * @param a
	 *            The angle.
	 * @param r
	 *            The radius.
	 * @return The Cartesian coordinates.
	 */
	static public PVector polarToCartesian(float basex, float basey, float a, float r) {
		return new PVector((float) (basex + Math.cos(a) * r), (float) (basey + Math.sin(a) * r));
	}

	/**
	 * Constrains a float value to the give min and max. min and max are both
	 * included in the range. If the input value is smaller that min, the output
	 * will be min. If the input value is higher than max, the output will be
	 * max.
	 * 
	 * @param input
	 *            The value that should be constrained to a range.
	 * @param min
	 *            The minimum of the range (value is included in the range). min
	 *            < max.
	 * @param max
	 *            The maximum of the range (value is included in the range). min
	 *            < max.
	 * @return The constrained value which will be in the range of [min, max].
	 */
	static public float constrain(float input, float min, float max) {
		return input >= min && input <= max ? input : (input < min ? min : max);
	}

	/**
	 * Constrains a float value to the give min and max. min and max are both
	 * included in the range. If the input value is smaller that min, the output
	 * will be min. If the input value is higher than max, the output will be
	 * max.
	 * 
	 * @param input
	 *            The value that should be constrained to a range.
	 * @param min
	 *            The minimum of the range (value is included in the range). min
	 *            < max.
	 * @param max
	 *            The maximum of the range (value is included in the range). min
	 *            < max.
	 * @return The constrained value which will be in the range of [min, max].
	 */
	static public Integer constrain(int input, int min, int max) {
		return input >= min && input <= max ? input : (input < min ? min : max);
	}

	/**
	 * Maps (interpolates) the given input value, which has a known range of
	 * <b>inputStart</b> and <b>inputEnd</b>, to another range. If the input is
	 * out of bounds to the input range, the output will be out of bounds to the
	 * output range as well. But the input value will still be interpolated.
	 * 
	 * @param input
	 *            The value that should be interpolated.
	 * @param inputStart
	 *            The first value that describes the input range (inputStart <=
	 *            inputEnd or inputStart >= inputEnd). If inputStart = inputEnd,
	 *            <b>outputStart</b> will be returned.
	 * @param inputEnd
	 *            The second value that describes the input range (inputStart <=
	 *            inputEnd or inputStart >= inputEnd).
	 * @param outputStart
	 *            The first value that describes the output range (outputStart
	 *            <= outputEnd or outputStart >= outputEnd). If outputStart =
	 *            outputEnd, <b>outputStart</b> will be returned.
	 * @param outputEnd
	 *            The second value that describes the output range (outputStart
	 *            <= outputEnd or outputStart >= outputEnd). If outputStart =
	 *            outputEnd, <b>outputStart</b> will be returned.
	 * @return The mapped value.
	 */
	static public float map(float input, float inputStart, float inputEnd, float outputStart, float outputEnd) {
		if (inputEnd - inputStart == 0 || outputEnd - outputStart == 0) {
			return outputStart;
		}

		float x = (input - inputStart) / (inputEnd - inputStart);

		return outputStart + x * (outputEnd - outputStart);
	}

	/**
	 * Returns a random float value with range of [0, max).
	 * 
	 * @param max
	 *            The maximum of the range. max will be not included for 0 <
	 *            max. if max < 0, the output will have a range of [max, 0).
	 * @return The random float value.
	 */
	static public float random(float max) {
		return RMath.random(0, max);
	}

	/**
	 * Returns a random float value with range of [min, max).
	 * 
	 * @param min
	 *            The minimum of the range. min will be included for min < max.
	 *            if max < min, the output will have a range of [max, min).
	 * @param max
	 *            The maximum of the range. max will be not included for min <
	 *            max. if max < min, the output will have a range of [max, min).
	 * @return The random float value.
	 */
	static public float random(float min, float max) {
		if (min > max) {
			float tmp = min;
			min = max;
			max = tmp;
		}

		return (float) (min + Math.random() * (max - min));
	}

	/**
	 * Returns a random int value with range of [0, max).
	 * 
	 * @param max
	 *            The maximum of the range. max will be not included for 0 <
	 *            max. if max < 0, the output will have a range of [max, 0).
	 * @return The random int value.
	 */
	static public int random(int max) {
		return (int) RMath.random((float) 0, (float) max);
	}

	/**
	 * Returns a random int value with range of [min, max).
	 * 
	 * @param min
	 *            The minimum of the range. min will be included for min < max.
	 *            if max < min, the output will have a range of [max, min).
	 * @param max
	 *            The maximum of the range. max will be not included for min <
	 *            max. if max < min, the output will have a range of [max, min).
	 * @return The random int value.
	 */
	static public int random(int min, int max) {
		return (int) RMath.random((float) min, (float) max);
	}

	/**
	 * Returns the absolute value of the input.
	 * 
	 * @param val
	 *            Any float value.
	 * @return The absolute float value of the input.
	 */
	static public float abs(float val) {
		return val < 0 ? val * -1 : val;
	}

	/**
	 * Returns the absolute value of the input.
	 * 
	 * @param val
	 *            Any int value.
	 * @return The absolute int value of the input.
	 */
	static public int abs(int val) {
		return val < 0 ? val * -1 : val;
	}

	/**
	 * Returns the minimal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of float values.
	 * @return The minimal float value of the given set. -1 will be returned if
	 *         the input is empty.
	 */
	static public float min(float... vals) {

		if (vals == null || vals.length == 0) {
			return -1;
		}

		float m = vals[0];

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] < m) {
				m = vals[i];
			}

		}

		return m;
	}

	/**
	 * Returns the minimal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of int values.
	 * @return The minimal int value of the given set. -1 will be returned if
	 *         the input is empty.
	 */
	static public int min(int... vals) {

		if (vals == null || vals.length == 0) {
			return -1;
		}

		int m = vals[0];

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] < m) {
				m = vals[i];
			}

		}

		return m;
	}

	/**
	 * Calculates the the minimal values of all color channels of the given
	 * color set. The color set will be converted to the given color space and
	 * the color space will define which minimal values should be calculated.
	 * Example: If ColorSpace.RGBColor is the given color space then the output
	 * will be the minimal red, green, blue and alpha.
	 * 
	 * @param space
	 *            The color space which defines which minimal values should be
	 *            calculated.
	 * @param vals
	 *            A set of colors.
	 * @return The minimal values of the channels. Depending on the given color
	 *         spaces these are: [r, g, b, alpha], [h, s, b, alpha], [x, y, z,
	 *         alpha], [l, a, b, alpha], [l, c, h, alpha]. null will be returned
	 *         if the input is empty.
	 */
	static public float[] min(ColorSpace space, RColor... vals) {
		float[] mins = new float[4];

		if (vals == null || vals.length == 0) {
			return mins;
		}

		RColor target = ColorHelper.convert(vals[0], space);

		switch (space) {
		case HSBColor:
			mins[0] = ((HSBColor) target).getHue();
			mins[1] = ((HSBColor) target).getSaturation();
			mins[2] = ((HSBColor) target).getBrightness();
			break;
		case XYZColor:
			mins[0] = ((XYZColor) target).getX();
			mins[1] = ((XYZColor) target).getY();
			mins[2] = ((XYZColor) target).getZ();
			break;
		case LabColor:
			mins[0] = ((LabColor) target).getLuminance();
			mins[1] = ((LabColor) target).getA();
			mins[2] = ((LabColor) target).getB();
			break;
		case LCHColor:
			mins[0] = ((LCHColor) target).getLuminance();
			mins[1] = ((LCHColor) target).getChroma();
			mins[2] = ((LCHColor) target).getHue();
			break;
		default:
			mins[0] = ((RGBColor) target).getRed();
			mins[1] = ((RGBColor) target).getGreen();
			mins[2] = ((RGBColor) target).getBlue();
			break;
		}

		mins[2] = target.getAlpha();

		for (int i = 1; i < vals.length; i++) {
			target = ColorHelper.convert(vals[1], space);

			float v1 = 0;
			float v2 = 0;
			float v3 = 0;
			float v4 = target.getAlpha();

			switch (space) {
			case HSBColor:
				v1 = ((HSBColor) target).getHue();
				v2 = ((HSBColor) target).getSaturation();
				v3 = ((HSBColor) target).getBrightness();
				break;
			case XYZColor:
				v1 = ((XYZColor) target).getX();
				v2 = ((XYZColor) target).getY();
				v3 = ((XYZColor) target).getZ();
				break;
			case LabColor:
				v1 = ((LabColor) target).getLuminance();
				v2 = ((LabColor) target).getA();
				v3 = ((LabColor) target).getB();
				break;
			case LCHColor:
				v1 = ((LCHColor) target).getLuminance();
				v2 = ((LCHColor) target).getChroma();
				v3 = ((LCHColor) target).getHue();
				break;
			default:
				v1 = ((RGBColor) target).getRed();
				v2 = ((RGBColor) target).getGreen();
				v3 = ((RGBColor) target).getBlue();
				break;
			}

			if (v1 < mins[0]) {
				mins[0] = v1;
			}

			if (v2 < mins[1]) {
				mins[1] = v2;
			}

			if (v3 < mins[2]) {
				mins[2] = v3;
			}

			if (v4 < mins[3]) {
				mins[3] = v4;
			}

		}

		return mins;
	}

	/**
	 * Returns the minimal color value found in the set of the input.
	 * 
	 * @param channel
	 *            The color channel. Either 'r', 'g' or 'b' when first color of
	 *            vals is a RGBColor, 'h', 's' or 'b' when first color of vals
	 *            is a HSBColor, 'x', 'y' or 'z' if first color of vals is a
	 *            XYZColor, 'l', 'a' or 'b' if first color is a LabColor, 'l',
	 *            'c' or 'h' if the first color is a LCHColor. For any color
	 *            space the parameter 't' will return the minimal transparency.
	 * 
	 * @param vals
	 *            A set of colors. Best case: All colors are members of the same
	 *            color space and this color space correlates to the color
	 *            channel which the minimum should be calculated. Example: All
	 *            colors are RGBColor and the channel is 'r' for minimal red.
	 *            Minimal case: The first color of the set is member of the
	 *            color space which correlates to the given channel. Example:
	 *            First color of vals is a LabColor and the channel is 'l' for
	 *            minimal luminance.
	 * @return The minimal color value of the given set. -1 will be returned if
	 *         the input is empty.
	 */
	static public float min(char channel, RColor... vals) {

		if (vals == null || vals.length == 0) {
			return -1.0f;
		}

		ColorSpace space = vals[0].getColorSpace();
		float m = 0;

		if (channel != 't') {
			switch (space) {

			case HSBColor:
				if (channel == 'h') {
					m = vals[0].toHSB().getHue();
				} else if (channel == 's') {
					m = vals[0].toHSB().getSaturation();
				} else {
					m = vals[0].toHSB().getBrightness();
				}
				break;
			case XYZColor:
				if (channel == 'x') {
					m = vals[0].toXYZ().getX();
				} else if (channel == 'y') {
					m = vals[0].toXYZ().getY();
				} else {
					m = vals[0].toXYZ().getZ();
				}
				break;
			case LabColor:
				if (channel == 'l') {
					m = vals[0].toLab().getLuminance();
				} else if (channel == 'a') {
					m = vals[0].toLab().getA();
				} else {
					m = vals[0].toLab().getB();
				}
				break;
			case LCHColor:
				if (channel == 'l') {
					m = vals[0].toLCH().getLuminance();
				} else if (channel == 'c') {
					m = vals[0].toLCH().getChroma();
				} else {
					m = vals[0].toLCH().getHue();
				}
				break;
			case RGBColor:
			default:
				if (channel == 'r') {
					m = vals[0].toRGB().getRed();
				} else if (channel == 'g') {
					m = vals[0].toRGB().getGreen();
				} else {
					m = vals[0].toRGB().getBlue();
				}
				break;
			}
		} else {
			m = vals[0].getAlpha();
		}

		for (int i = 1; i < vals.length; i++) {

			float tmp = 0;

			if (channel != 't') {
				switch (space) {

				case HSBColor:
					if (channel == 'h') {
						tmp = vals[i].toHSB().getHue();
					} else if (channel == 's') {
						tmp = vals[i].toHSB().getSaturation();
					} else {
						tmp = vals[i].toHSB().getBrightness();
					}
					break;
				case XYZColor:
					if (channel == 'x') {
						tmp = vals[i].toXYZ().getX();
					} else if (channel == 'y') {
						tmp = vals[i].toXYZ().getY();
					} else {
						tmp = vals[i].toXYZ().getZ();
					}
					break;
				case LabColor:
					if (channel == 'l') {
						tmp = vals[i].toLab().getLuminance();
					} else if (channel == 'a') {
						tmp = vals[i].toLab().getA();
					} else {
						tmp = vals[i].toLab().getB();
					}
					break;
				case LCHColor:
					if (channel == 'l') {
						tmp = vals[i].toLCH().getLuminance();
					} else if (channel == 'c') {
						tmp = vals[i].toLCH().getChroma();
					} else {
						tmp = vals[i].toLCH().getHue();
					}
					break;
				case RGBColor:
				default:
					if (channel == 'r') {
						tmp = vals[i].toRGB().getRed();
					} else if (channel == 'g') {
						tmp = vals[i].toRGB().getGreen();
					} else {
						tmp = vals[i].toRGB().getBlue();
					}
					break;
				}
			} else {
				tmp = vals[i].getAlpha();
			}

			if (tmp < m) {
				m = tmp;
			}

		}

		return m;
	}

	/**
	 * Returns the maximal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of float values.
	 * @return The maximal float value of the given set. -1 will be returned if
	 *         the input is empty.
	 */
	static public float max(float... vals) {

		if (vals == null || vals.length == 0) {
			return -1;
		}

		float m = vals[0];

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] > m) {
				m = vals[i];
			}

		}

		return m;
	}

	/**
	 * Returns the maximal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of int values.
	 * @return The maximal int value of the given set. -1 will be returned if
	 *         the input is empty.
	 */
	static public int max(int... vals) {

		if (vals == null || vals.length == 0) {
			return -1;
		}

		int m = vals[0];

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] > m) {
				m = vals[i];
			}

		}

		return m;
	}

	/**
	 * Returns the maximal color value found in the set of the input.
	 * 
	 * @param channel
	 *            The color channel. Either 'r', 'g' or 'b' when first color of
	 *            vals is a RGBColor, 'h', 's' or 'b' when first color of vals
	 *            is a HSBColor, 'x', 'y' or 'z' if first color of vals is a
	 *            XYZColor, 'l', 'a' or 'b' if first color is a LabColor, 'l',
	 *            'c' or 'h' if the first color is a LCHColor. For any color
	 *            space the parameter 't' will return the maximal transparency.
	 * 
	 * @param vals
	 *            A set of colors. Best case: All colors are members of the same
	 *            color space and this color space correlates to the color
	 *            channel which the minimum should be calculated. Example: All
	 *            colors are RGBColor and the channel is 'r' for maximal red.
	 *            Minimal case: The first color of the set is member of the
	 *            color space which correlates to the given channel. Example:
	 *            First color of vals is a LabColor and the channel is 'l' for
	 *            maximal luminance.
	 * @return The maximal color value of the given set. -1 will be returned if
	 *         the input is empty.
	 */
	static public float max(char channel, RColor... vals) {

		if (vals == null || vals.length == 0) {
			return -1.0f;
		}

		ColorSpace space = vals[0].getColorSpace();
		float m = 0;

		if (channel != 't') {
			switch (space) {

			case HSBColor:
				if (channel == 'h') {
					m = vals[0].toHSB().getHue();
				} else if (channel == 's') {
					m = vals[0].toHSB().getSaturation();
				} else {
					m = vals[0].toHSB().getBrightness();
				}
				break;
			case XYZColor:
				if (channel == 'x') {
					m = vals[0].toXYZ().getX();
				} else if (channel == 'y') {
					m = vals[0].toXYZ().getY();
				} else {
					m = vals[0].toXYZ().getZ();
				}
				break;
			case LabColor:
				if (channel == 'l') {
					m = vals[0].toLab().getLuminance();
				} else if (channel == 'a') {
					m = vals[0].toLab().getA();
				} else {
					m = vals[0].toLab().getB();
				}
				break;
			case LCHColor:
				if (channel == 'l') {
					m = vals[0].toLCH().getLuminance();
				} else if (channel == 'c') {
					m = vals[0].toLCH().getChroma();
				} else {
					m = vals[0].toLCH().getHue();
				}
				break;
			case RGBColor:
			default:
				if (channel == 'r') {
					m = vals[0].toRGB().getRed();
				} else if (channel == 'g') {
					m = vals[0].toRGB().getGreen();
				} else {
					m = vals[0].toRGB().getBlue();
				}
				break;
			}
		} else {
			m = vals[0].getAlpha();
		}

		for (int i = 1; i < vals.length; i++) {

			float tmp = 0;

			if (channel != 't') {
				switch (space) {

				case HSBColor:
					if (channel == 'h') {
						tmp = vals[i].toHSB().getHue();
					} else if (channel == 's') {
						tmp = vals[i].toHSB().getSaturation();
					} else {
						tmp = vals[i].toHSB().getBrightness();
					}
					break;
				case XYZColor:
					if (channel == 'x') {
						tmp = vals[i].toXYZ().getX();
					} else if (channel == 'y') {
						tmp = vals[i].toXYZ().getY();
					} else {
						tmp = vals[i].toXYZ().getZ();
					}
					break;
				case LabColor:
					if (channel == 'l') {
						tmp = vals[i].toLab().getLuminance();
					} else if (channel == 'a') {
						tmp = vals[i].toLab().getA();
					} else {
						tmp = vals[i].toLab().getB();
					}
					break;
				case LCHColor:
					if (channel == 'l') {
						tmp = vals[i].toLCH().getLuminance();
					} else if (channel == 'c') {
						tmp = vals[i].toLCH().getChroma();
					} else {
						tmp = vals[i].toLCH().getHue();
					}
					break;
				case RGBColor:
				default:
					if (channel == 'r') {
						tmp = vals[i].toRGB().getRed();
					} else if (channel == 'g') {
						tmp = vals[i].toRGB().getGreen();
					} else {
						tmp = vals[i].toRGB().getBlue();
					}
					break;
				}
			} else {
				tmp = vals[i].getAlpha();
			}

			if (tmp > m) {
				m = tmp;
			}

		}

		return m;
	}

	/**
	 * Calculates the the maximal values of all color channels of the given
	 * color set. The color set will be converted to the given color space and
	 * the color space will define which minimal values should be calculated.
	 * Example: If ColorSpace.RGBColor is the given color space then the output
	 * will be the maximal red, green, blue and alpha.
	 * 
	 * @param space
	 *            The color space which defines which maximal values should be
	 *            calculated.
	 * @param vals
	 *            A set of colors.
	 * @return The maximal values of the channels. Depending on the given color
	 *         spaces these are: [r, g, b, alpha], [h, s, b, alpha], [x, y, z,
	 *         alpha], [l, a, b, alpha], [l, c, h, alpha]. null will be returned
	 *         if the input is empty.
	 */
	static public float[] max(ColorSpace space, RColor... vals) {

		if (vals == null || vals.length == 0) {
			return null;
		}

		float[] maxs = new float[4];

		RColor target = ColorHelper.convert(vals[0], space);

		switch (space) {
		case HSBColor:
			maxs[0] = ((HSBColor) target).getHue();
			maxs[1] = ((HSBColor) target).getSaturation();
			maxs[2] = ((HSBColor) target).getBrightness();
			break;
		case XYZColor:
			maxs[0] = ((XYZColor) target).getX();
			maxs[1] = ((XYZColor) target).getY();
			maxs[2] = ((XYZColor) target).getZ();
			break;
		case LabColor:
			maxs[0] = ((LabColor) target).getLuminance();
			maxs[1] = ((LabColor) target).getA();
			maxs[2] = ((LabColor) target).getB();
			break;
		case LCHColor:
			maxs[0] = ((LCHColor) target).getLuminance();
			maxs[1] = ((LCHColor) target).getChroma();
			maxs[2] = ((LCHColor) target).getHue();
			break;
		default:
			maxs[0] = ((RGBColor) target).getRed();
			maxs[1] = ((RGBColor) target).getGreen();
			maxs[2] = ((RGBColor) target).getBlue();
			break;
		}

		maxs[2] = target.getAlpha();

		for (int i = 1; i < vals.length; i++) {
			target = ColorHelper.convert(vals[1], space);

			float v1 = 0;
			float v2 = 0;
			float v3 = 0;
			float v4 = target.getAlpha();

			switch (space) {
			case HSBColor:
				v1 = ((HSBColor) target).getHue();
				v2 = ((HSBColor) target).getSaturation();
				v3 = ((HSBColor) target).getBrightness();
				break;
			case XYZColor:
				v1 = ((XYZColor) target).getX();
				v2 = ((XYZColor) target).getY();
				v3 = ((XYZColor) target).getZ();
				break;
			case LabColor:
				v1 = ((LabColor) target).getLuminance();
				v2 = ((LabColor) target).getA();
				v3 = ((LabColor) target).getB();
				break;
			case LCHColor:
				v1 = ((LCHColor) target).getLuminance();
				v2 = ((LCHColor) target).getChroma();
				v3 = ((LCHColor) target).getHue();
				break;
			default:
				v1 = ((RGBColor) target).getRed();
				v2 = ((RGBColor) target).getGreen();
				v3 = ((RGBColor) target).getBlue();
				break;
			}

			if (v1 > maxs[0]) {
				maxs[0] = v1;
			}

			if (v2 > maxs[1]) {
				maxs[1] = v2;
			}

			if (v3 > maxs[2]) {
				maxs[2] = v3;
			}

			if (v4 > maxs[3]) {
				maxs[3] = v4;
			}

		}

		return maxs;
	}

	/**
	 * Calculates the range (minimum and maximum values) of the given set.
	 * 
	 * @param vals
	 *            A set of int values.
	 * @return The minimum and maximum values found in the set [min, max]. null
	 *         will be returned if the input is empty.
	 */
	static public int[] range(int... vals) {
		if (vals == null || vals.length == 0) {
			return null;
		}

		int[] minmax = new int[2];

		minmax[0] = vals[0];
		minmax[1] = vals[2];

		for (int i = 1; i < vals.length; i++) {
			int val = vals[i];

			if (val < minmax[0]) {
				minmax[0] = val;
			}

			if (val > minmax[1]) {
				minmax[1] = val;
			}
		}

		return minmax;
	}

	/**
	 * Calculates the range (minimum and maximum values) of the given set.
	 * 
	 * @param vals
	 *            A set of float values.
	 * @return The minimum and maximum values found in the set [min, max]. null
	 *         will be returned if the input is empty.
	 */
	static public float[] range(float... vals) {
		if (vals == null || vals.length == 0) {
			return null;
		}

		float[] minmax = new float[2];

		minmax[0] = vals[0];
		minmax[1] = vals[2];

		for (int i = 1; i < vals.length; i++) {
			float val = vals[i];

			if (val < minmax[0]) {
				minmax[0] = val;
			}

			if (val > minmax[1]) {
				minmax[1] = val;
			}
		}

		return minmax;
	}

	/**
	 * Calculates the range (minimum and maximum values) of all color channels
	 * the given color set. The color set will be converted to the given color
	 * space and the color space will define which minimal values should be
	 * calculated. Example: If ColorSpace.RGBColor is the given color space then
	 * the output will be the minimal red, green, blue and alpha.
	 * 
	 * @param space
	 *            The color space which defines which minimal values should be
	 *            calculated.
	 * @param vals
	 *            A set of colors.
	 * @return The range of the channels. Depending on the given color spaces
	 *         these are: [minR, minG, minB, minAlpha, maxR, maxG, maxB,
	 *         maxAlpha], [minH, minS, minV, minAlpha, maxH, maxS, maxV,
	 *         maxAlpha], [[minX, minY, minZ, minAlpha], [maxX, maxY, maxZ,
	 *         maxAlpha], [minL, minA, minB, minAlpha, maxL, maxA, maxB,
	 *         maxAlpha], [minL, minC, minH, minAlpha, maxL, maxC, maxH,
	 *         maxAlpha]. The first four elements of the output array will
	 *         always be the minimal values, the last for elements will be the
	 *         maximum values. null will be returned if the input is empty.
	 */
	static public float[] range(ColorSpace space, RColor... vals) {

		if (vals == null || vals.length == 0) {
			return null;
		}

		float[] minmax = new float[8];

		RColor target = ColorHelper.convert(vals[0], space);

		float v1 = 0;
		float v2 = 0;
		float v3 = 0;
		float v4 = target.getAlpha();

		switch (space) {
		case HSBColor:
			v1 = ((HSBColor) target).getHue();
			v2 = ((HSBColor) target).getSaturation();
			v3 = ((HSBColor) target).getBrightness();
			break;
		case XYZColor:
			v1 = ((XYZColor) target).getX();
			v2 = ((XYZColor) target).getY();
			v3 = ((XYZColor) target).getZ();
			break;
		case LabColor:
			v1 = ((LabColor) target).getLuminance();
			v2 = ((LabColor) target).getA();
			v3 = ((LabColor) target).getB();
			break;
		case LCHColor:
			v1 = ((LCHColor) target).getLuminance();
			v2 = ((LCHColor) target).getChroma();
			v3 = ((LCHColor) target).getHue();
			break;
		default:
			v1 = ((RGBColor) target).getRed();
			v2 = ((RGBColor) target).getGreen();
			v3 = ((RGBColor) target).getBlue();
			break;
		}

		minmax[0] = minmax[4] = v1;
		minmax[1] = minmax[5] = v2;
		minmax[2] = minmax[6] = v3;
		minmax[3] = minmax[7] = v4;

		for (int i = 1; i < vals.length; i++) {
			target = ColorHelper.convert(vals[i], space);

			v4 = target.getAlpha();

			switch (space) {
			case HSBColor:
				v1 = ((HSBColor) target).getHue();
				v2 = ((HSBColor) target).getSaturation();
				v3 = ((HSBColor) target).getBrightness();
				break;
			case XYZColor:
				v1 = ((XYZColor) target).getX();
				v2 = ((XYZColor) target).getY();
				v3 = ((XYZColor) target).getZ();
				break;
			case LabColor:
				v1 = ((LabColor) target).getLuminance();
				v2 = ((LabColor) target).getA();
				v3 = ((LabColor) target).getB();
				break;
			case LCHColor:
				v1 = ((LCHColor) target).getLuminance();
				v2 = ((LCHColor) target).getChroma();
				v3 = ((LCHColor) target).getHue();
				break;
			default:
				v1 = ((RGBColor) target).getRed();
				v2 = ((RGBColor) target).getGreen();
				v3 = ((RGBColor) target).getBlue();
				break;
			}

			if (v1 < minmax[0]) {
				minmax[0] = v1;
			}

			if (v2 < minmax[1]) {
				minmax[1] = v2;
			}

			if (v3 < minmax[2]) {
				minmax[2] = v3;
			}

			if (v4 < minmax[3]) {
				minmax[3] = v4;
			}

			// -------------------

			if (v1 > minmax[4]) {
				minmax[4] = v1;
			}

			if (v2 > minmax[5]) {
				minmax[5] = v2;
			}

			if (v3 > minmax[6]) {
				minmax[6] = v3;
			}

			if (v4 > minmax[7]) {
				minmax[7] = v4;
			}

		}

		return minmax;
	}

	/**
	 * Calculates the range (minimum x / y and maximum x / y values) of the
	 * given set.
	 * 
	 * @param vals
	 *            A set of PVector values.
	 * @return The minimum and maximum values found in the set [minX, minY,
	 *         maxX, maxY]. null will be returned if the input is empty.
	 */
	static public float[] range(PVector... vals) {

		if (vals == null || vals.length == 0) {
			return null;
		}

		float minX = vals[0].x;
		float maxX = vals[0].x;

		float minY = vals[0].y;
		float maxY = vals[0].y;

		for (int i = 1; i < vals.length; i++) {
			PVector p = vals[i];

			if (p.x > maxX) {
				maxX = p.x;
			}

			if (p.y > maxY) {
				maxY = p.y;
			}

			if (p.x < minX) {
				minX = p.x;
			}

			if (p.y < minY) {
				minY = p.y;
			}

		}

		return new float[] { minX, minY, maxX, maxY };
	}

	/**
	 * Returns the index of the minimal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of int values.
	 * @return The index where the minimal value is found or -1 if input was
	 *         empty. If the input set contains the minimum more than once, the
	 *         first occurrence is returned.
	 */
	static public int minIndex(int... vals) {
		if (vals == null || vals.length == 0) {
			return -1;
		}

		int m = vals[0];
		int index = 0;

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] < m) {
				m = vals[i];
				index = i;
			}

		}

		return index;
	}

	/**
	 * Returns the index of the minimal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of float values.
	 * @return The index where the minimal value is found or -1 if input was
	 *         empty. If the input set contains the minimum more than once, the
	 *         first occurrence is returned.
	 */
	static public int minIndex(float... vals) {
		if (vals == null || vals.length == 0) {
			return -1;
		}

		float m = vals[0];
		int index = 0;

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] < m) {
				m = vals[i];
				index = i;
			}

		}

		return index;
	}

	/**
	 * Returns the index of the maximal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of int values.
	 * @return The index where the maximal value is found or -1 if input was
	 *         empty. If the input set contains the maximum more than once, the
	 *         first occurrence is returned.
	 */
	static public int maxIndex(int... vals) {
		if (vals == null || vals.length == 0) {
			return -1;
		}

		int m = vals[0];
		int index = 0;

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] > m) {
				m = vals[i];
				index = i;
			}

		}

		return index;
	}

	/**
	 * Returns the index of the maximal value found in the set of the input.
	 * 
	 * @param vals
	 *            A set of float values.
	 * @return The index where the maximal value is found or -1 if input was
	 *         empty. If the input set contains the maximum more than once, the
	 *         first occurrence is returned.
	 */
	static public int maxIndex(float... vals) {
		if (vals == null || vals.length == 0) {
			return -1;
		}

		float m = vals[0];
		int index = 0;

		for (int i = 1; i < vals.length; i++) {
			if (vals[i] < m) {
				m = vals[i];
				index = i;
			}

		}

		return index;
	}
}
