/*
	Generated image output in one of the following few formats.

	(1) RLE format. 

	(2) RAW : Binary file.
                Has a header of 5 bytes.
           Header Content:
                color_channels : 1 byte
                cols : 2 bytes
                rows : 2 bytes
           Followed by Pixel data in the following format.
           For each row
                For each channel
                        one row of pixel value.
                        (Each pixel value is a byte 0-255.)
        (3) TEXT : Readable file
                Header line:
                        color_channels cols rows
                Followed by
                For each row
                        for each channel
                                one row of integer pixel value
                                 (0 to 255).
	(4) RADIANCE : Radiance output format.
	(5) SUNRASTER format.
------
sumant
*/

#include <stdio.h>
#include <math.h>
#include <malloc.h>

#include "GraphicsGems.h"
#include "data_structure.h"
#include "objects.h"
#include "vox.h"
#include "render.h"
extern int verbose_flag;

#include "raddecl.h"

#include "rle.h"
	rle_pixel **rle_rows=NULL;

#include  "color.h"
#include "resolu.h"
        COLOR   *scanout;

#include "rasterfile.h"
	unsigned char *rowbuffer=NULL;	

static void initialise_output(rows,cols,fl)
int rows,cols;
FILE *fl;
{
	if (output_format==Rle){
		rle_dflt_hdr.ncolors=color_channels;
		rle_dflt_hdr.xmax = cols-1;
        	rle_dflt_hdr.ymax = rows-1;
        	rle_dflt_hdr.rle_file = fl;
		rle_put_setup(&rle_dflt_hdr);
		(void)fflush(rle_dflt_hdr.rle_file);
		if (rle_rows==NULL)
		if (rle_row_alloc(&rle_dflt_hdr, &rle_rows) < 0)
			error("Unable to allocate image memory.");
	}
	else if (output_format==Radiance){
        	fputformat(COLRFMT, fl);
        	putc('\n', fl);
        	fprtresolu(cols, rows, fl);
        	scanout = (COLOR *)malloc(cols*sizeof(COLOR));
        	if (scanout == NULL)
                error("out of memory to allocate scanline");
	}
	else if (output_format==Sunraster){
		struct rasterfile header;
		rowbuffer=(unsigned char *)malloc(
			sizeof(unsigned char)*cols*3);
                if (rowbuffer == NULL)
                error("out of memory to allocate scanline");
		header.ras_magic=RAS_MAGIC;
		header.ras_width=cols;
		header.ras_height=rows;
		header.ras_depth=24;
		header.ras_length=cols*rows;
		header.ras_type=RT_STANDARD;
		header.ras_maptype=RMT_RAW;
		header.ras_maplength=0;

		fwrite(&header,sizeof(unsigned char),sizeof(header),fl);
	}
	else if (output_format==Raw){
	/* 
	   Header of 5 bytes.
	   Content:
		color_channels : 1 byte
		cols : 2 bytes
		rows : 2 bytes
	*/
		unsigned char b;
		b = color_channels; fwrite(&b,1,1,fl);
		b = cols;fwrite(&b,1,1,fl);
			b = cols>>8;fwrite(&b,1,1,fl);
		b = rows;fwrite(&b,1,1,fl); 
			b = rows>>8;fwrite(&b,1,1,fl);
	}
	else fprintf(fl,"%d %d %d\n",color_channels,cols,rows);
}

extern double brightness_mult_factor;
        /*
                brightness_mult_factor: (default = 1.0)
                A mechanism to brighten up less bright area, with
                the understainding that any intensity above 1 will
                clamped to 1.
        */
static unsigned char quantise(f)
double f;
{
	int c = (int)(f * brightness_mult_factor * 255);
	if (c > 255) c = 255;
	else if (c < 0) c = 0;
	return((unsigned char)c);
}

static void dump_row(fl,cols,row_pixels)
FILE *fl;
int cols;
Pixel *row_pixels;
{
	int i,j;
	if (output_format==Rle){
		for(j=0;j<color_channels;j++)
			for(i=0;i<cols;i++)
			rle_rows[j][i]=quantise(row_pixels[i].color[j]);
		rle_putrow( rle_rows,cols,&rle_dflt_hdr);
	}
	else if (output_format==Radiance){
        	int components=color_channels;
        	if (components>3) components=3;
        	for(i=0;i<cols;i++){
                	for(j=0;j<components;j++)
                        	scanout[i][j]=row_pixels[i].color[j]*
					brightness_mult_factor;
                	for(j=components;j<3;j++) scanout[i][j]=0.;
        	}
        	if (fwritescan(scanout, cols, fl) < 0)
                	error("error writing Radiance picture");
        }
	else if (output_format==Sunraster){
			int components=color_channels;
			if (components>3) components=3;
			for(i=0;i<cols;i++){
					for(j=0;j<components;j++)
							rowbuffer[3*i+j]=
					   quantise(row_pixels[i].color[j]);
				for(j=components;j<3;j++) rowbuffer[3*i+j]=0;
			}
			if (fwrite(rowbuffer, sizeof(unsigned char),3*cols, fl)
			< 0)
					error("error writing SunRaster picture");
	}
	else if (output_format==Raw)
		for(j=0;j<color_channels;j++)
			for(i=0;i<cols;i++){
				unsigned char b;
				b = quantise(row_pixels[i].color[j]);
		   		fwrite(&b,1,1,fl);
			}
	else
	for(j=0;j<color_channels;j++){
		for(i=0;i<cols;i++)
		   fprintf(fl,"%d ",quantise(row_pixels[i].color[j]));
		fprintf(fl,"\n");
	}

}

static void terminate_frame(fl)
FILE *fl;
{
	if (output_format==Rle) {
		rle_puteof(&rle_dflt_hdr);
		fclose(fl);
	}
	else if (output_format==Radiance){
			free((char *)scanout);
			fclose(fl);
	}
	else if (output_format==Sunraster){
		free((char *)rowbuffer);
		fclose(fl);
	}
	else fclose(fl);
}

static void terminate_output(fl)
FILE *fl;
{
	if (output_format==Rle)
		if (rle_rows != NULL){
			rle_row_free(&rle_dflt_hdr,rle_rows);
			rle_rows = NULL;
		}
}

static void color_from_diffuse_surface(ray,n,t,u1,v1,s)
/*
	Returns the computed view independent intensity.
*/
Ray *ray;
int n;
double t,u1,v1;
Pixel *s;
{
	int i;
	double Uindex=grid_u(n,u1);
	double Vindex=grid_v(n,v1);
	int uindex=(int)Uindex;
	int vindex=(int)Vindex;

	if ((view.shading_type == FLAT)||
		((object[n].grid_h_reso==1)&&(object[n].grid_v_reso==1))){
		int gindex=gridindex(n,uindex,vindex);
		double *scolor=object[n].grid[gindex].normalized_flux_density;
		for(i=0;i<color_channels;i++) s->color[i]=scolor[i];
	}
	else{/* (view.shading_type == GOURAUD) */
		/* 
			Bilinear Interpolation. 
			-----------------------
			           |             |
			           |             |
			(P7)       |     (P8)    |    (P9)
			           |             |
			        P01|             |P11
			--------------------------------------
			           |             |
			           | (Unindex,   |
			(P4)       |    Vindex)  |    (P6)
			           |     *       |
			           |             |
			       P00 |             | P10
			----uindex,vindex---------------------
			           |             |
			           |             |
			(P1)       |    (P2)     |    (P3)
			           |             |
			           |             |
		*/
		double P00,P10,P11,P01;
		double u=Uindex-uindex,v=Vindex-vindex;
		double *P1,*P2,*P3,*P4,*P5,*P6,*P7,*P8,*P9;
		int i,j;
		i=uindex-1;j=vindex-1;
		P1=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i++;
		P2=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i++;
		P3=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i=uindex-1;j=vindex;
		P4=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i++;
		P5=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i++;
		P6=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i=uindex-1;j=vindex+1;
		P7=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i++;
		P8=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		i++;
		P9=object[n].grid[gridindex(n,i,j)].normalized_flux_density;
		for(i=0;i<color_channels;i++){
			P00=(P1[i]+P2[i]+P4[i]+P5[i])/4.0;
			P10=(P2[i]+P3[i]+P5[i]+P6[i])/4.0;
			P11=(P5[i]+P6[i]+P8[i]+P9[i])/4.0;
			P01=(P4[i]+P5[i]+P7[i]+P8[i])/4.0;
			s->color[i]=(1-u)*(P00*(1.0-v)+P01*v)+u*(P10*(1.0-v)+P11*v);
		}
	}
}

static void color_from_specular_surface(ray,objectnum,t,u,v,s)
/*
	There is no view independent information in this surface.
	This surface was used as a path for light transport.
	So the view ray has to be reflected from this surface and the
	process continued till a diffuse surface is reached.
	
	IMPORTANT
	---------
	1.The ray start position and the direction are changed.
	2.As get_color is called with the new ray this leads to RECURSION.
	
*/
Ray *ray;
int objectnum;
double t,u,v;
Pixel *s;
{
	Pixel get_color();
	Vector3 N;
	/* .... set the new ray */
	point_on_line(ray->start,ray->direction,t,ray->start);
	N = ofunc[object[objectnum].surface_geometry_type].
		get_surface_normal(
			object[objectnum].object_specific_structure,u,v
	);
	mirror_reflection(&N,&(ray->direction),&(object[objectnum]));
	(ray->number)++;
	*s=get_color(ray);
}

static void color_from_other_surface(ray,objectnum,t,u,v,s)
Ray *ray;
int objectnum;
double t,u,v;
Pixel *s;
{
	error("Not Implemented.");
}

static void (*get_surface_specific_color[MAX_REFLECTION_TYPE])()={
	color_from_diffuse_surface,
	color_from_specular_surface,
	color_from_other_surface
};

static Pixel get_color(ray)
/*
                                   i
	IMPORTANT
	---------
	If nondiffuse surfaces are present, get_color invokes itself indirectly
	from get_surface_specific_color with modified ray. So the ray.start and
	directions are changed.
*/
Ray *ray;
{
	int i;
	double t_entry,t_exit;
	int HitBoundingBox();
	Pixel pixel;

	/* Initialise */
	for(i=0;i<color_channels;i++)pixel.color[i]=0;

	if (HitBoundingBox(&(volume_grid.extent.min),&(volume_grid.extent.max),
			   &(ray->start),&(ray->direction),&t_entry,&t_exit)){
		int get_nearest_object_in_voxel();
		Vlist *create_vlist();
		Vlist *vlist,*templist;
		int nearest_object= UNDEFINED;
		double t,u,v;
		vlist=templist=create_vlist(
			&(ray->start),&(ray->direction),t_entry,t_exit
		);
		while (templist!=NULL){
			Voxel *vox = volume_grid.voxels+templist->voxnum;
			double tmax=templist->t_far, tmin=templist->t_near;
			/* ASSERT((tmax>=0)&&(tmin>=0)); */
			if(vox->nobjects)
				if((nearest_object=get_nearest_object_in_voxel
					(ray,templist->t_near,templist->t_far,vox,&t,&u,&v))
					!=UNDEFINED) tmax=t;
			if (nearest_object!=UNDEFINED) break;
			templist=templist->next;
		}
		purge_vlist(vlist);
		if (nearest_object!=UNDEFINED){
			/* Add the attenuated surface intensity */
			Pixel from_surface;
			get_surface_specific_color[
				object[nearest_object].surface_reflection_type
			](ray,nearest_object,t,u,v,&from_surface);
			for(i=0;i<color_channels;i++)
				pixel.color[i] += from_surface.color[i];
		}
	}
	return(pixel);
}


long render(flname)
/*
	Using Ray-tracing for rendering using the computed equillibrium
	flux-density on the surfaces.

	Initialises the new ray ray_number to (total_rays+1) so that the mail_box querry 
	is proper.
*/
char *flname;
{
	int i,j,k;
	Pixel get_color();
	Pixel *row_pixels;
	Ray ray;
	char name[100];
	FILE *fl;

	row_pixels = (Pixel *)malloc(sizeof(Pixel)*view.cols);
	ray.number=total_rays;
#if defined (DEBUG)
	ray.intersections=0;
#endif
	ray.path_length= -1;
	for(i=0; i < view.nframes; i++){
		if(view.nframes>1)sprintf(name,"%s.%03d",flname,i);
		else sprintf(name,"%s",flname);
		if (verbose_flag)fprintf(stderr,"Image file %s:",name);
#if defined(__MSDOS__) || defined(__WATCOMC__)
		if ((fl = fopen(name,"wb"))==NULL)
			error("Cannot open Image file.");
#else
		if ((fl = fopen(name,"w"))==NULL)
			error("Cannot open Image file.");
#endif
		initialise_output(view.rows,view.cols,fl);
		for (j=0; j < view.rows; j++){
			Vector3 dir;
			dir=view.view_direction[i];
			for(k=0; k < view.cols; k++){
				ray.start=view.eye_point[i];
				ray.direction=dir;
				V3Normalize(&(ray.direction));
				row_pixels[k]=get_color(&ray);
				(ray.number)++;
				V3Add(&dir,&(view.delta_u[i]),&dir);
			}
			dump_row(fl,view.cols,row_pixels);
			V3Add(&(view.view_direction[i]),&(view.delta_v[i]),
				&(view.view_direction[i]));
			if (verbose_flag)fprintf(stderr,".");
		}
		terminate_frame(fl);
		if (verbose_flag)fprintf(stderr,"\n");
	}
	terminate_output(fl);
	free(row_pixels);
	return(ray.number-total_rays);
}
