package data.bins;

import java.awt.image.BufferedImage;
import java.awt.image.DataBuffer;
import java.awt.image.DataBufferByte;
import java.awt.image.DataBufferInt;
import java.awt.image.WritableRaster;
import java.util.ArrayList;
import java.util.Arrays;

import util.simpleIO.Out;

import data.bins.quantizer.*;
import data.bins.quantizer.AbstractQuantizer.TYPE_QUANTIZER;


/**
 * Class for handling bin-color information.
 * @author Krisi
 */
public class Bins {

	/** type of bin values **/
	public enum TYPE_BIN_VALUES {
		none,log10,sqrt,pow,relative1,relativeMax
	};
	public static int relativeMaxMult = 255;
	public TYPE_BIN_VALUES valueType = TYPE_BIN_VALUES.none;

	public AbstractQuantizer quantizer;
	//private TYPE_QUANTIZER qType;
	private float[] bins;
	private int nrOfBins;
	//private int maxNrOfBins;

	public static boolean fastQuantization = true;

	/**
	 * Constructor
	 * @param nrOfBins
	 */
	public Bins(AbstractQuantizer quantizer) {
		//this.maxNrOfBins = maxNrOfBins;
		//this.qType = qType;
		this.quantizer = quantizer; //AbstractQuantizer.factory(qType, maxNrOfBins);
		this.nrOfBins = quantizer.getNrOfBins();
		this.bins = new float[this.nrOfBins];
	}

	/**
	 * Add picture to bins
	 * @param image
	 */
	public void add(BufferedImage image) {

		boolean hasAlpha = image.getColorModel().hasAlpha();	// test if alpha channel exists
		if(hasAlpha)											// ignore alpha
			quantizer.damnYouAlpha();							// -

		// pixel data is in the raster
		WritableRaster raster = image.getRaster();

		// --- FastQuantizer --- --------------------
		if(fastQuantization && quantizer instanceof IF_FastQuantizer){

			// get DataBuffer
			DataBuffer dataBuffer = raster.getDataBuffer();

			// use DataBuffer

			if(dataBuffer.getDataType()==DataBuffer.TYPE_INT){
				//Out.pl("Integer Data.");
				int[] pic = ((DataBufferInt) dataBuffer).getData();
				((IF_FastQuantizer)quantizer).quantizeFast(pic, bins);
			}
			else if(dataBuffer.getDataType()==DataBuffer.TYPE_BYTE){
				//Out.pl("Byte Data.");
				byte[] pic = ((DataBufferByte) dataBuffer).getData();
				((IF_FastQuantizer)quantizer).quantizeFast(pic, bins);
			}
			else{
				throw new RuntimeException("Unknown Data Buffer.");
			}
		}

		// --- Any Quantizer --- --------------------
		else {

			// create an object for the pixel data:
			int[] pixel;
			if(hasAlpha)				// needed to avoid exceptions
				pixel = new int[4];
			else
				pixel = new int[3];

			//Out.pl(">>> Colors: "+pixel.length);
			int w = raster.getWidth();
			int h = raster.getHeight();

			// access pixel data:
			for (int y = 0; y < h; y++) {
				for (int x = 0; x < w; x++) {
					// add pixel data here ...
					raster.getPixel(x, y, pixel);
					add(pixel);
					//Out.p("> add pixel "+x+"/"+y+" ...");
				}
			}
		}


	}

	/**
	 * Add pixel to histogram/bins.
	 * @param pixel
	 */
	public void add(int[] pixel) {

		ArrayList<QBin> qBins = quantizer.quantize(pixel);		// quantize the pixel

		for (QBin qBin : qBins) {
			int binNr = qBin.binNr;
			float factor = qBin.factor;
			bins[binNr] += factor;				// count++ in bin
			//bins[binNr] = bins[binNr] +1;  // edit niklas
			//Out.pl("Added pixel in bin '"+(qBin.binNr+1)+"'");
		}
	}

	/**
	 * Alternative call
	 */
	public void add(int r, int g, int b) {
		add(new int[]{r, g, b});
	}

	/**
	 * @return the bins
	 */
	public float[] getBinsDirect() {
		return bins;
	}

	/**
	 * @return copy of bins
	 */
	public float[] getBinsCopy() {
		return Arrays.copyOf(bins, bins.length);
	}

	/**
	 * @return copy of bins as double
	 */
	public double[] getBinsCopyDouble() {

		double[] tmp = new double[bins.length];
		//      double[] tmp2 = new double[bins.length-10];
		for (int i = 0; i < bins.length; i++) {
			tmp[i] = bins[i];
		}
		//    for(int i=10; i<tmp2.length; i++){
		//	tmp2[i] = tmp[i];

		//	}

		return tmp;
	}

	/**
	 * 
	 * @param type
	 * @return last type
	 */
	public TYPE_BIN_VALUES valueAction(TYPE_BIN_VALUES type){

		TYPE_BIN_VALUES last = this.valueType;

		switch(type){
		case none: 
			return last;
			//throw new RuntimeException("Not allowed");
			//break;
		case log10: 
			this.valuesLog10();
			break;
		case sqrt: 
			this.valuesSqrt();
			break;
		case pow:
			this.valuesPow(3);
			break;
		case relative1:
			this.valuesRelative1();
			break;
		case relativeMax:
			this.valuesRelativeMax();
			break;
		default:
			throw new RuntimeException("Unknown type.");
		}

		this.valueType = type;
		return last;
	}


	/**
	 * Convert all values to relative values.
	 * Percent, sum of bins = 1 (100%)
	 */
	private void valuesRelative1(){
		//valueType = TYPE_VALUES.relative1;

		float sum=0;
		for (int i = 0; i < bins.length; i++) {		// get sum of all values
			sum += bins[i];
		}
		for (int i = 0; i < bins.length; i++) {		// calculate new values
			bins[i] = bins[i]/sum;
		}

	}

	/**
	 * Convert all values to relative values.
	 * Relative to max value
	 */
	private void valuesRelativeMax(){
		//valueType = TYPE_VALUES.relativeMax;

		assert bins.length>0;

		float max=bins[0];
		for (int i = 0; i < bins.length; i++) {		// get max value
			if( bins[i]>max){
				max = bins[i];
			}
		}
		assert max!=0;
		for (int i = 0; i < bins.length; i++) {		// calculate new values
			bins[i] = bins[i]/max * relativeMaxMult;
		}
	}

	/**
	 * Make values logarithmic (log10)
	 */
	private void valuesLog10(){
		//valueType = TYPE_VALUES.log10;

		for (int i = 0; i < bins.length; i++) {
			float val = (float) Math.log10(bins[i]);
			if(val==Float.NEGATIVE_INFINITY)
				val=0;
			bins[i] = val;
		}
	}

	/**
	 * Make values sqrt
	 */
	private void valuesSqrt(){
		//valueType = TYPE_VALUES.sqrt;

		for (int i = 0; i < bins.length; i++) {
			bins[i] = (float) Math.sqrt(bins[i]);
		}
	}

	/**
	 * pow(bin[], value)
	 */
	private void valuesPow(double value){
		//valueType = TYPE_VALUES.pow;

		for (int i = 0; i < bins.length; i++) {
			bins[i] = (float) Math.pow(bins[i], value);
		}
		valuesRelativeMax();
	}

	/**
	 * @return existing number of bins
	 */
	public int getNrOfBins(){
		return nrOfBins;
	}

	public AbstractQuantizer getQuantizer(){
		return quantizer;
	}

	/**
	 * @return sorted copy of bins
	 */
	public float[] getBinsSorted() {
		float[] tmp = Arrays.copyOf(bins, bins.length);
		Arrays.sort(tmp);
		return tmp;
	}

//	/**
//	 * @return the maxNrOfBins
//	 */
//	public int getMaxNrOfBins() {
//		return maxNrOfBins;
//	}
//
//	/**
//	 * @param maxNrOfBins the maxNrOfBins to set
//	 */
//	public void setMaxNrOfBins(int maxNrOfBins) {
//		this.maxNrOfBins = maxNrOfBins;
//	}

	/**
	 * @return a copy
	 */
	public Bins copy(){
		Bins bins2 = new Bins(quantizer);
		bins2.bins = Arrays.copyOf(bins, bins.length);
		
		return bins2;
	}
}
