/**
 * $Id$
 *
 * ***** BEGIN GPL LICENSE BLOCK *****
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version. 
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * The Original Code is Copyright (C) 2006 Blender Foundation.
 * All rights reserved.
 *
 * The Original Code is: all of this file.
 *
 * Contributor(s): Bob Holcomb.
 *
 * ***** END GPL LICENSE BLOCK *****
 */

#include "../CMP_util.h"


/* **************** LEVELS ******************** */
static bNodeSocketType cmp_node_view_levels_in[]= {
	{	SOCK_RGBA, 1, "Image", 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
	{	-1, 0, ""	}
};

static bNodeSocketType cmp_node_view_levels_out[]={
	{SOCK_VALUE, 0,"Mean",0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
	{SOCK_VALUE, 0,"Std Dev",0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f},
	{-1,0,""}
};

static void rgb_tobw(float r, float g, float b, float* out)
{
	*out= r*0.35f + g*0.45f + b*0.2f;
}

static void fill_bins(bNode* node, CompBuf* in, int* bins)
{
	float value[4];
	int ivalue=0;
	int x,y;

	/*fill bins */
	for(y=0; y<in->y; y++) {
		for(x=0; x<in->x; x++) {

			/* get the pixel */
			qd_getPixel(in, x, y, value);

			if(value[3] > 0.0) { /* don't count transparent pixels */
				switch(node->custom1) {
					case 1: { /* all colors */
						rgb_tobw(value[0],value[1],value[2], &value[0]);
						value[0]=value[0]*255; /* scale to 0-255 range */
						ivalue=(int)value[0];
						break;
					}
					case 2: { /* red channel */
						value[0]=value[0]*255; /* scale to 0-255 range */
						ivalue=(int)value[0];
						break;
					}
					case 3:  { /* green channel */
						value[1]=value[1]*255; /* scale to 0-255 range */
						ivalue=(int)value[1];
						break;
					}
					case 4: /*blue channel */
					{
						value[2]=value[2]*255; /* scale to 0-255 range */
						ivalue=(int)value[2];
						break;
					}
					case 5: /* luminence */
					{
						rgb_to_yuv(value[0],value[1],value[2], &value[0], &value[1], &value[2]);
						value[0]=value[0]*255; /* scale to 0-255 range */
						ivalue=(int)value[0];
						break;
					}
				} /*end switch */

				/*clip*/
				if(ivalue<0) ivalue=0;
				if(ivalue>255) ivalue=255;

				/*put in the correct bin*/
				bins[ivalue]+=1;
			} /*end if alpha */
		}
	}	
}

static float brightness_mean(bNode* node, CompBuf* in)
{
	float sum=0.0;
	int numPixels=0.0;
	int x,y;
	float value[4];

	for(x=0; x< in->x; x++) {
		for(y=0; y < in->y; y++) {
			
			/* get the pixel */
			qd_getPixel(in, x, y, value);

			if(value[3] > 0.0) { /* don't count transparent pixels */
				numPixels++;
				switch(node->custom1)
				{
				case 1:
					{
						rgb_tobw(value[0],value[1],value[2], &value[0]);
						sum+=value[0];
						break;
					}
				case 2:
					{
						sum+=value[0];
						break;
					}
				case 3:
					{
						sum+=value[1];
						break;
					}
				case 4:
					{
						sum+=value[2];
						break;
					}
				case 5:
					{
						rgb_to_yuv(value[0],value[1],value[2], &value[0], &value[1], &value[2]);
						sum+=value[0];
						break;
					}
				}
			}
		}
	}

	return sum/numPixels;
}

static float brightness_standard_deviation(bNode* node, CompBuf* in, float mean)
{
	float sum=0.0;
	int numPixels=0.0;
	int x,y;
	float value[4];

	for(x=0; x< in->x; x++) {
		for(y=0; y < in->y; y++) {
			
			/* get the pixel */
			qd_getPixel(in, x, y, value);

			if(value[3] > 0.0) { /* don't count transparent pixels */
				numPixels++;
				switch(node->custom1)
				{
				case 1:
					{
						rgb_tobw(value[0],value[1],value[2], &value[0]);
						sum+=(value[0]-mean)*(value[0]-mean);
						break;
					}
				case 2:
					{
						sum+=value[0];
						sum+=(value[0]-mean)*(value[0]-mean);
						break;
					}
				case 3:
					{
						sum+=value[1];
						sum+=(value[1]-mean)*(value[1]-mean);
						break;
					}
				case 4:
					{
						sum+=value[2];
						sum+=(value[2]-mean)*(value[2]-mean);
						break;
					}
				case 5:
					{
						rgb_to_yuv(value[0],value[1],value[2], &value[0], &value[1], &value[2]);
						sum+=(value[0]-mean)*(value[0]-mean);
						break;
					}
				}
			}
		}
	}


	return sqrt(sum/(float)(numPixels-1));
}

static void draw_histogram(bNode *node, CompBuf *out, int* bins)
{
	int x,y;
	float color[4]; 
	float value;
	int max;

	/* find max value */
	max=0;
	for(x=0; x<256; x++) {
		if(bins[x]>max) max=bins[x];
	}

	/*draw histogram in buffer */
	for(x=0; x<out->x; x++) {
		for(y=0;y<out->y; y++) {

			/* get normalized value (0..255) */
			value=((float)bins[x]/(float)max)*255.0; 

			if(y < (int)value) { /*if the y value is below the height of the bar for this line then draw with the color */
				switch (node->custom1) {
					case 1: { /* draw in black */
						color[0]=0.0; color[1]=0.0; color[2]=0.0; color[3]=1.0;
						break;
					}
					case 2: { /* draw in red */
						color[0]=1.0; color[1]=0.0; color[2]=0.0; color[3]=1.0;
						break;
					}
					case 3: { /* draw in green */
						color[0]=0.0; color[1]=1.0; color[2]=0.0; color[3]=1.0;
						break;
					}
					case 4: { /* draw in blue */
						color[0]=0.0; color[1]=0.0; color[2]=1.0; color[3]=1.0;
						break;
					}
					case 5: { /* draw in white */
						color[0]=1.0; color[1]=1.0; color[2]=1.0; color[3]=1.0;
						break;
					}
				}
			}
			else{
				color[0]=0.8; color[1]=0.8; color[2]=0.8; color[3]=1.0;
			}

			/* set the color */
			qd_setPixel(out, x, y, color);
		}
	}
}

static void node_composit_exec_view_levels(void *data, bNode *node, bNodeStack **in, bNodeStack **out)
{
	CompBuf* cbuf;
	CompBuf* histogram;
	float mean, std_dev;
	int bins[256];
	int x;

	if(in[0]->hasinput==0)  return;
	if(in[0]->data==NULL) return;

	histogram=alloc_compbuf(256, 256, CB_RGBA, 1);	
	cbuf=typecheck_compbuf(in[0]->data, CB_RGBA);	
		
	/*initalize bins*/
	for(x=0; x<256; x++) {
		bins[x]=0;
	}
	
	/*fill bins */
	fill_bins(node, in[0]->data, bins);

	/* draw the histogram chart */
	draw_histogram(node, histogram, bins);

	/* calculate the average brightness and contrast */
	mean=brightness_mean(node, in[0]->data);
	std_dev=brightness_standard_deviation(node, in[0]->data, mean);

	/*  Printf debuging ;) 
	printf("Mean: %f\n", mean);
	printf("Std Dev: %f\n", std_dev);
	*/

	if(out[0]->hasoutput)
			out[0]->vec[0]= mean;
	if(out[1]->hasoutput)
			out[1]->vec[0]= std_dev;

	generate_preview(data, node, histogram);

	if(cbuf!=in[0]->data)
		free_compbuf(cbuf);
	free_compbuf(histogram);
}

static void node_composit_init_view_levels(bNode* node)
{
   node->custom1=1; /*All channels*/
}

bNodeType cmp_node_view_levels= {
	/* *next,*prev */	NULL, NULL,
	/* type code   */	CMP_NODE_VIEW_LEVELS,
	/* name        */	"Levels",
	/* widthrange */	140, 100, 320,
	/* classopts  */	NODE_CLASS_OUTPUT, NODE_OPTIONS|NODE_PREVIEW,
	/* input sock  */	cmp_node_view_levels_in,
	/* output sock */	cmp_node_view_levels_out,
	/* storage     */	"ImageUser",
	/* execfunc    */	node_composit_exec_view_levels,
	/* butfunc     */	NULL,
	/* initfunc    */	node_composit_init_view_levels,
	/* freestoragefunc    */	NULL,
	/* copystoragefunc    */	NULL,
	/* id          */	NULL
	
};

