/*
 * $Id: ColorSchemeFilter.java,v 1.2 2008/05/31 04:16:12 kirillcool Exp $
 *
 * Dual-licensed under LGPL (Sun and Romain Guy) and BSD (Romain Guy).
 *
 * Copyright 2005 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * Copyright (c) 2006 Romain Guy <romain.guy@mac.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.jvnet.substance.utils.filters;

import java.awt.Color;
import java.awt.image.BufferedImage;
import java.util.*;

import org.jvnet.lafwidget.Resettable;
import org.jvnet.substance.api.SubstanceColorScheme;
import org.jvnet.substance.utils.*;

/**
 * @author Romain Guy <romain.guy@mac.com>
 * @author Kirill Grouchnikov
 */

public class ColorSchemeFilter extends AbstractFilter {
	private int[] interpolated;

	public static final int MAPSTEPS = 512;

	protected static LazyResettableHashMap<ColorSchemeFilter> filters = new LazyResettableHashMap<ColorSchemeFilter>(
			"ColorSchemeFilter");

	public static ColorSchemeFilter getColorSchemeFilter(SubstanceColorScheme scheme) {
		HashMapKey key = SubstanceCoreUtilities.getHashKey(scheme.getDisplayName());
		if (filters.containsKey(key))
			return filters.get(key);
		ColorSchemeFilter result = new ColorSchemeFilter(scheme);
		filters.put(key, result);
		return result;
	}
	
	/**
	 * @throws IllegalArgumentException
	 * 		if <code>scheme</code> is null
	 */
	private ColorSchemeFilter(SubstanceColorScheme scheme) {
		if (scheme == null) {
			throw new IllegalArgumentException("mixColor cannot be null");
		}

		// collect the brightness factors of the color scheme
		Map<Integer, Color> schemeColorMapping = new TreeMap<Integer, Color>();
		schemeColorMapping.put(SubstanceColorUtilities
				.getColorBrightness(scheme.getUltraLightColor().getRGB()),
				scheme.getUltraLightColor());
		schemeColorMapping.put(SubstanceColorUtilities
				.getColorBrightness(scheme.getExtraLightColor().getRGB()),
				scheme.getExtraLightColor());
		schemeColorMapping.put(SubstanceColorUtilities
				.getColorBrightness(scheme.getLightColor().getRGB()), scheme
				.getLightColor());
		schemeColorMapping.put(SubstanceColorUtilities
				.getColorBrightness(scheme.getMidColor().getRGB()), scheme
				.getMidColor());
		schemeColorMapping.put(SubstanceColorUtilities
				.getColorBrightness(scheme.getDarkColor().getRGB()), scheme
				.getDarkColor());
		schemeColorMapping.put(SubstanceColorUtilities
				.getColorBrightness(scheme.getUltraDarkColor().getRGB()),
				scheme.getUltraDarkColor());

		List<Integer> schemeBrightness = new ArrayList<Integer>();
		schemeBrightness.addAll(schemeColorMapping.keySet());
		Collections.sort(schemeBrightness);

		int lowestSchemeBrightness = schemeBrightness.get(0);
		int highestSchemeBrightness = schemeBrightness.get(schemeBrightness
				.size() - 1);

		Map<Integer, Color> stretchedColorMapping = new TreeMap<Integer, Color>();
		for (Map.Entry<Integer, Color> entry : schemeColorMapping.entrySet()) {
			int brightness = entry.getKey();
			int stretched = 255 - 255 * (highestSchemeBrightness - brightness)
					/ (highestSchemeBrightness - lowestSchemeBrightness);
			stretchedColorMapping.put(stretched, entry.getValue());
		}
		schemeBrightness = new ArrayList<Integer>();
		schemeBrightness.addAll(stretchedColorMapping.keySet());
		Collections.sort(schemeBrightness);

		this.interpolated = new int[MAPSTEPS];
		for (int i = 0; i < MAPSTEPS; i++) {
			int brightness = (int) (256.0 * i / MAPSTEPS);
			if (schemeBrightness.contains(brightness)) {
				this.interpolated[i] = stretchedColorMapping.get(brightness)
						.getRGB();
			} else {
				int currIndex = 0;
				while (true) {
					int currStopValue = schemeBrightness.get(currIndex);
					int nextStopValue = schemeBrightness.get(currIndex + 1);
					if ((brightness > currStopValue)
							&& (brightness < nextStopValue)) {
						// interpolate
						Color currStopColor = stretchedColorMapping
								.get(currStopValue);
						Color nextStopColor = stretchedColorMapping
								.get(nextStopValue);
						this.interpolated[i] = SubstanceColorUtilities
								.getInterpolatedRGB(
										currStopColor,
										nextStopColor,
										1.0
												- (double) (brightness - currStopValue)
												/ (double) (nextStopValue - currStopValue));
						break;
					}
					currIndex++;
				}
			}
		}
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public BufferedImage filter(BufferedImage src, BufferedImage dst) {
		if (dst == null) {
			dst = createCompatibleDestImage(src, null);
		}

		int width = src.getWidth();
		int height = src.getHeight();

		int[] pixels = new int[width * height];
		getPixels(src, 0, 0, width, height, pixels);
		mixColor(pixels);
		setPixels(dst, 0, 0, width, height, pixels);

		return dst;
	}

	private void mixColor(int[] pixels) {
		for (int i = 0; i < pixels.length; i++) {
			int argb = pixels[i];

			int brightness = SubstanceColorUtilities.getColorBrightness(argb);
			int pixelColor = interpolated[brightness * MAPSTEPS / 256];
			pixels[i] = (argb & 0xFF000000) | ((pixelColor >> 16) & 0xFF) << 16
					| ((pixelColor >> 8) & 0xFF) << 8 | (pixelColor & 0xFF);
		}
	}

}
