
package railo.runtime.img;
import java.awt.image.BufferedImage;
import java.awt.image.ColorModel;

import railo.runtime.exp.ExpressionException;
import railo.runtime.img.interpolation.Bessel;
import railo.runtime.img.interpolation.Blackman;
import railo.runtime.img.interpolation.Hamming;
import railo.runtime.img.interpolation.Hanning;
import railo.runtime.img.interpolation.Hermite;
import railo.runtime.img.interpolation.Interpolation;
import railo.runtime.img.interpolation.Lanczos;
import railo.runtime.img.interpolation.Mitchell;
import railo.runtime.img.interpolation.Quadratic;
import railo.runtime.img.interpolation.Triangle;


public class ImageResizer	{
    
    private static class ContributionInfo {
    	int pixel;
    	double weight;
	
	
	public void setPixel(int pixel) {
	    this.pixel = pixel;
	}
	
	public int getPixel() {
	    return pixel;
	}
	
	public void setWeight(double weight) {
	    this.weight = weight;
	}
	
	public double getWeight() {
	    return weight;
	}
    }
    
    private static int horizontal(BufferedImage source, BufferedImage destination, double xFactor,Interpolation ip, double blur, ContributionInfo[] contribution) {
	if (source.getWidth() == destination.getWidth()
	    && source.getHeight() == destination.getHeight()) {
	    destination.setData(source.getData());
	    return destination.getWidth();
	}
	double scale = blur * Math.max(1.0 / xFactor, 1.0);
	double support = Math.max(scale * ip.getSupport(), 0.5);
	if (support <= 0.5) {
	    support = 0.500000000001;
	    scale = 1.0;
	}
	ColorModel cm = ColorModel.getRGBdefault();
	for (int x = 0; x < destination.getWidth(); x++) {
	    double center = x / xFactor;
	    int start = (int) Math.max(center - support + 0.5, 0.0);
	    int end = (int) Math.min(center + support + 0.5,
				     source.getWidth());
	    int n = setContributionWeight(start, end, contribution, ip,
					  center, scale);
	    int sourceWidth = (contribution[n - 1].getPixel()
			       - contribution[0].getPixel() + 1);
	    int[] sourcePixels = new int[sourceWidth * source.getHeight()];
	    int[] destPixels = new int[destination.getHeight()];
	    sourcePixels = source.getRGB(contribution[0].getPixel(), 0,
					 sourceWidth, source.getHeight(),
					 sourcePixels, 0, sourceWidth);
	    int destIndex = 0;
	    for (int y = 0; y < destination.getHeight(); y++) {
		double blue = 0.0;
		double green = 0.0;
		double red = 0.0;
		double opacity = 0.0;
		int[] c = new int[4];
		for (int i = 0; i < n; i++) {
		    int j = (y * (contribution[n - 1].getPixel()
				  - contribution[0].getPixel() + 1)
			     + (contribution[i].getPixel()
				- contribution[0].getPixel()));
		    c = cm.getComponents(sourcePixels[j], c, 0);
		    red += contribution[i].getWeight() * c[0];
		    green += contribution[i].getWeight() * c[1];
		    blue += contribution[i].getWeight() * c[2];
		    opacity += contribution[i].getWeight() * c[3];
		}
		red = red < 0.0 ? 0.0 : red > 255.0 ? 255.0 : red + 0.5;
		green
		    = green < 0.0 ? 0.0 : green > 255.0 ? 255.0 : green + 0.5;
		blue = blue < 0.0 ? 0.0 : blue > 255.0 ? 255.0 : blue + 0.5;
		opacity = (opacity < 0.0 ? 0.0 : opacity > 255.0 ? 255.0
			   : opacity + 0.5);
		destPixels[destIndex]
		    = cm.getDataElement(new int[] { (int) red, (int) green,
						    (int) blue,
						    (int) opacity },
					0);
		destIndex++;
	    }
	    destination.setRGB(x, 0, 1, destination.getHeight(), destPixels, 0,
			       1);
	}
	return destination.getWidth();
    }
    
    private static int setContributionWeight(int start, int end, ContributionInfo[] contribution, Interpolation interpolation, double center, double scale) {
		int n = 0;
		double density = 0.0;
		for (int i = start; i < end; i++) {
		    contribution[n].setPixel(i);
		    contribution[n].setWeight(interpolation.f((i - center + 0.5D)
						       / scale) / scale);
		    density += contribution[n].getWeight();
		    n++;
		}
		density = density == 0.0 ? 1.0 : 1.0 / density;
		for (int i = 0; i < n; i++)
		    contribution[i].setWeight(contribution[i].getWeight() * density);
		return n;
    }
    
    private static int vertical(BufferedImage source, BufferedImage destination, double y_factor, Interpolation ip, double blur, ContributionInfo[] contribution) {
	/*if (source.getWidth() == destination.getWidth()
	    && source.getHeight() == destination.getHeight()) {
	    destination.setData(source.getData());
	    return destination.getWidth();
	}*/
	double scale = blur * Math.max(1.0 / y_factor, 1.0);
	double support = Math.max(scale * ip.getSupport(), 0.5);
	if (support <= 0.5) {
	    support = 0.500000000001;
	    scale = 1.0;
	}
	ColorModel cm = ColorModel.getRGBdefault();
	for (int y = 0; y < destination.getHeight(); y++) {
	    double center =  y / y_factor;
	    int start = (int) Math.max(center - support + 0.5, 0.0);
	    int end = (int) Math.min(center + support + 0.5,
				     source.getHeight());
	    int n = setContributionWeight(start, end, contribution, ip,
					  center, scale);
	    int sourceHeight = (contribution[n - 1].getPixel()
				- contribution[0].getPixel() + 1);
	    int[] sourcePixels = new int[source.getWidth() * sourceHeight];
	    int[] destPixels = new int[destination.getWidth()];
	    sourcePixels = source.getRGB(0, contribution[0].getPixel(),
					 source.getWidth(), sourceHeight,
					 sourcePixels, 0, source.getWidth());
	    int destIndex = 0;
	    for (int x = 0; x < destination.getWidth(); x++) {
		double blue = 0.0;
		double green = 0.0;
		double red = 0.0;
		double opacity = 0.0;
		int[] c = new int[4];
		for (int i = 0; i < n; i++) {
		    int j = ((contribution[i].getPixel()
			      - contribution[0].getPixel()) * source.getWidth()
			     + x);
		    c = cm.getComponents(sourcePixels[j], c, 0);
		    red += contribution[i].getWeight() * c[0];
		    green += contribution[i].getWeight() * c[1];
		    blue += contribution[i].getWeight() * c[2];
		    opacity += contribution[i].getWeight() * c[3];
		}
		red = red < 0.0 ? 0.0 : red > 255.0 ? 255.0 : red + 0.5;
		green
		    = green < 0.0 ? 0.0 : green > 255.0 ? 255.0 : green + 0.5;
		blue = blue < 0.0 ? 0.0 : blue > 255.0 ? 255.0 : blue + 0.5;
		opacity = (opacity < 0.0 ? 0.0 : opacity > 255.0 ? 255.0
			   : opacity + 0.5);
		destPixels[destIndex]
		    = cm.getDataElement(new int[] { (int) red, (int) green,
						    (int) blue,
						    (int) opacity },
					0);
		destIndex++;
	    }
	    destination.setRGB(0, y, destination.getWidth(), 1, destPixels, 0,
			       destination.getWidth());
	}
	return destination.getHeight();
    }
    

    
    public static BufferedImage resize(BufferedImage image, int columns, int rows, int interpolation, double blur) throws ExpressionException {
    	if (columns == 0 || rows == 0)
    		throw new ExpressionException("invalid size for image");
		
    	BufferedImage resizeImage = ImageUtil.createBufferedImage(image, columns, rows);
		
		Interpolation inter = getInterpolation(interpolation);
		double xFactor = (double) columns / (double) image.getWidth();
		double scale = blur * Math.max(1.0 / xFactor, 1.0);
		double xSupport = Math.max(scale * inter.getSupport(), 0.5);
		double yFactor = (double) rows / (double) image.getHeight();
		scale = blur * Math.max(1.0 / yFactor, 1.0);
		double ySupport = Math.max(scale * inter.getSupport(), 0.5);
		double support = Math.max(xSupport, ySupport);
		if (support < inter.getSupport())
		    support = inter.getSupport();
		ContributionInfo[] contribution = new ContributionInfo[(int) support * 2 + 3];
		
		for (int cloop = 0; cloop < (int) support * 2 + 3; cloop++)
		    contribution[cloop] = new ContributionInfo();
		
		int status;
		if (columns * (image.getHeight() + rows) < rows * (image.getWidth() + columns)) {
		    BufferedImage sourceImage = ImageUtil.createBufferedImage(image, columns, image.getHeight());
		    status = horizontal(image, sourceImage, xFactor, inter, blur, contribution);
		    status |= vertical(sourceImage, resizeImage, yFactor,inter, blur, contribution);
		} 
		else {
		    BufferedImage sourceImage = ImageUtil.createBufferedImage(image, image.getWidth(), rows);
		    status = vertical(image, sourceImage, yFactor, inter, blur, contribution);
		    status |= horizontal(sourceImage, resizeImage, xFactor, inter, blur, contribution);
		}
		
		if (status == 0) throw new ExpressionException("can't resize image");
		return resizeImage;
    }
    
   
    
   
    
    private static Interpolation getInterpolation(int interpolation) throws ExpressionException {
		switch (interpolation) {
		
			case 0:	return new Triangle();
			case Image.IP_HERMITE:	return new Hermite();
			case Image.IP_HANNING:	return new Hanning();
			case Image.IP_MEDIUMQUALITY:
			case Image.IP_HIGHPERFORMANCE:
			case Image.IP_HAMMING:	return new Hamming();
			case Image.IP_BLACKMAN:	return new Blackman();
			case Image.IP_QUADRATIC:return new Quadratic();
			case Image.IP_HIGHQUALITY:
			case Image.IP_MEDIUMPERFORMANCE:
			case Image.IP_MITCHELL:	return new Mitchell();
			case Image.IP_HIGHESTQUALITY:
			case Image.IP_LANCZOS:	return new Lanczos();
			case Image.IP_BESSEL:	return new Bessel();
			default:	throw new ExpressionException("invalid interpolation definition");
		}
    }
    
    
}
