/*
 *  Copyright (C) 2002-2004  The DOSBox Team
 *
 *  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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */

/* $Id: render.cpp,v 1.30 2004/08/04 09:12:54 qbix79 Exp $ */

#include <sys/types.h>
#include <dirent.h>
#include <assert.h>
#include <math.h>

#include "dosbox.h"
#include "video.h"
#include "render.h"
#include "setup.h"
#include "mapper.h"
#include "cross.h"
#include "hardware.h"
#include "support.h"

#include "render_scalers.h"

struct PalData {
	struct { 
		Bit8u red;
		Bit8u green;
		Bit8u blue;
		Bit8u unused;
	} rgb[256];
	volatile Bitu first;
	volatile Bitu last;
};

static struct {
	struct {
		Bitu width;
		Bitu height;
		Bitu bpp;
		bool dblw,dblh;
		double ratio;
	} src;
	struct {
		Bitu width;
		Bitu height;
		Bitu pitch;
		GFX_Modes mode;
		RENDER_Operation type;
		RENDER_Operation want_type;
		RENDER_Line_Handler line_handler;
	} op;
	struct {
		Bitu count;
		Bitu max;
	} frameskip;
	PalData pal;
#if (C_SSHOT)
	struct {
		Bitu bpp,width,height,rowlen;
		Bit8u * buffer,* draw;
		bool take,taking;
	} shot;
#endif
	bool active;
	bool aspect;
	bool updating;
} render;

RENDER_Line_Handler RENDER_DrawLine;

#if (C_SSHOT)
#include <png.h>

static void RENDER_ShotDraw(const Bit8u * src) {
	memcpy(render.shot.draw,src,render.shot.rowlen);
	render.shot.draw+=render.shot.rowlen;
	render.op.line_handler(src);
}

/* Take a screenshot of the data that should be rendered */
static void TakeScreenShot(Bit8u * bitmap) {
	png_structp png_ptr;
	png_infop info_ptr;
	png_bytep * row_pointers;
	png_color palette[256];
	Bitu i;

/* Find a filename to open */
	/* Open the actual file */
	FILE * fp=OpenCaptureFile("Screenshot",".png");
	if (!fp) return;
	/* First try to alloacte the png structures */
	png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL,NULL, NULL);
	if (!png_ptr) return;
	info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
		png_destroy_write_struct(&png_ptr,(png_infopp)NULL);
		return;
    }

	/* Finalize the initing of png library */
	png_init_io(png_ptr, fp);
	png_set_compression_level(png_ptr,Z_BEST_COMPRESSION);
	
	/* set other zlib parameters */
	png_set_compression_mem_level(png_ptr, 8);
	png_set_compression_strategy(png_ptr,Z_DEFAULT_STRATEGY);
	png_set_compression_window_bits(png_ptr, 15);
	png_set_compression_method(png_ptr, 8);
	png_set_compression_buffer_size(png_ptr, 8192);

	if (render.shot.bpp==8) {
		png_set_IHDR(png_ptr, info_ptr, render.shot.width, render.shot.height,
			8, PNG_COLOR_TYPE_PALETTE, PNG_INTERLACE_NONE,
			PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
		for (i=0;i<256;i++) {
			palette[i].red=render.pal.rgb[i].red;
			palette[i].green=render.pal.rgb[i].green;
			palette[i].blue=render.pal.rgb[i].blue;
		}
		png_set_PLTE(png_ptr, info_ptr, palette,256);
	} else {
		png_set_IHDR(png_ptr, info_ptr, render.shot.width, render.shot.height,
			render.shot.bpp, PNG_COLOR_TYPE_RGB, PNG_INTERLACE_NONE,
			PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
	}
	/*Allocate an array of scanline pointers*/
	row_pointers=(png_bytep*)malloc(render.shot.height*sizeof(png_bytep));
	for (i=0;i<render.shot.height;i++) {
		row_pointers[i]=(bitmap+i*render.shot.rowlen);
	}
	/*tell the png library what to encode.*/
	png_set_rows(png_ptr, info_ptr, row_pointers);
	
	/*Write image to file*/
	png_write_png(png_ptr, info_ptr, 0, NULL);

	/*close file*/
	fclose(fp);
	
	/*Destroy PNG structs*/
	png_destroy_write_struct(&png_ptr, &info_ptr);
	
	/*clean up dynamically allocated RAM.*/
	free(row_pointers);
}

static void EnableScreenShot(void) {
	render.shot.take=true;
}

#endif


static void Check_Palette(void) {
	if (render.pal.first>render.pal.last) return;
	Bitu i;
	switch (render.op.mode) {
	case GFX_8:
		GFX_SetPalette(render.pal.first,render.pal.last-render.pal.first+1,(GFX_PalEntry *)&render.pal.rgb[render.pal.first]);
		break;
	case GFX_15:
	case GFX_16:
		for (i=render.pal.first;i<=render.pal.last;i++) {
			Bit8u r=render.pal.rgb[i].red;
			Bit8u g=render.pal.rgb[i].green;
			Bit8u b=render.pal.rgb[i].blue;
			Scaler_PaletteLut.b16[i]=GFX_GetRGB(r,g,b);
		}
		break;
	case GFX_32:
		for (i=render.pal.first;i<=render.pal.last;i++) {
			Bit8u r=render.pal.rgb[i].red;
			Bit8u g=render.pal.rgb[i].green;
			Bit8u b=render.pal.rgb[i].blue;
			Scaler_PaletteLut.b32[i]=GFX_GetRGB(r,g,b);
		}
		break;
	}
	/* Setup pal index to startup values */
	render.pal.first=256;
	render.pal.last=0;
}

static void RENDER_ResetPal(void) {
	render.pal.first=0;
	render.pal.last=255;
}

void RENDER_SetPal(Bit8u entry,Bit8u red,Bit8u green,Bit8u blue) {
	render.pal.rgb[entry].red=red;
	render.pal.rgb[entry].green=green;
	render.pal.rgb[entry].blue=blue;
	if (render.pal.first>entry) render.pal.first=entry;
	if (render.pal.last<entry) render.pal.last=entry;
}

static void RENDER_EmptyLineHandler(const Bit8u * src) {

}

bool RENDER_StartUpdate(void) {
	if (render.updating) return false;
	if (render.frameskip.count<render.frameskip.max) {
		render.frameskip.count++;
		return false;
	}
	render.frameskip.count=0;
	if (render.src.bpp==8) Check_Palette();
	Scaler_Line=0;
	Scaler_Index=Scaler_Data;
	if (!GFX_StartUpdate(Scaler_DstWrite,Scaler_DstPitch)) return false;
	RENDER_DrawLine=render.op.line_handler;
#if (C_SSHOT)
	if (render.shot.take) {
		render.shot.take=false;
		if (render.shot.buffer) {
			free(render.shot.buffer);
		}
		render.shot.width=render.src.width;
		render.shot.height=render.src.height;
		render.shot.bpp=render.src.bpp;
		switch (render.shot.bpp) {
		case 8:render.shot.rowlen=render.shot.width;break;
		case 15:
		case 16:render.shot.rowlen=render.shot.width*2;break;
		case 32:render.shot.rowlen=render.shot.width*4;break;
		}
		render.shot.buffer=(Bit8u*)malloc(render.shot.rowlen*render.shot.height);
		render.shot.draw=render.shot.buffer;
		RENDER_DrawLine=RENDER_ShotDraw;
		render.shot.taking=true;
	}
#endif
	render.updating=true;
	return true;
}

void RENDER_EndUpdate(void) {
	if (!render.updating) return;
#if (C_SSHOT)
	if (render.shot.taking) {
		render.shot.taking=false;
		TakeScreenShot(render.shot.buffer);
		free(render.shot.buffer);
		render.shot.buffer=0;
	}
#endif	/* If Things are added to please check the define */   
	GFX_EndUpdate();
	RENDER_DrawLine=RENDER_EmptyLineHandler;
 	render.updating=false;
}


static Bitu MakeAspectTable(Bitu height,double scaley,Bitu miny) {
	double lines=0;
	Bitu linesadded=0;
	for (Bitu i=0;i<height;i++) {
		lines+=scaley;
		if (lines>=miny) {
			Bitu templines=(Bitu)lines;
			lines-=templines;
			linesadded+=templines;
			Scaler_Data[i]=templines-miny;
		} else Scaler_Data[i]=0;
	}
	return linesadded;
}

void RENDER_ReInit(void) {
	if (render.updating) RENDER_EndUpdate();
	Bitu width=render.src.width;
	Bitu height=render.src.height;
	bool dblw=render.src.dblw;
	bool dblh=render.src.dblh;

	double gfx_scalew=1.0;
	double gfx_scaleh=1.0;
	
	if (render.src.ratio>1.0) gfx_scaleh*=render.src.ratio;
	else gfx_scalew*=(1/render.src.ratio);

	Bitu gfx_flags;
	ScalerBlock * block;
	if (dblh && dblw) {
		render.op.type=render.op.want_type;
	} else if (dblw) {
		render.op.type=OP_Normal2x;
	} else if (dblh) {
		render.op.type=OP_Normal;
		gfx_scaleh*=2;
	} else  {
forcenormal:
		render.op.type=OP_Normal;
	}
	switch (render.op.type) {
	case OP_Normal:block=&Normal_8;break;
	case OP_Normal2x:block=(dblh) ? &Normal2x_8 : &NormalDbl_8;break;
	case OP_AdvMame2x:block=&AdvMame2x_8;break;
	case OP_AdvMame3x:block=&AdvMame3x_8;break;
	case OP_Interp2x:block=&Interp2x_8;break;
	case OP_AdvInterp2x:block=&AdvInterp2x_8;break;
	case OP_TV2x:block=&TV2x_8;break;
	}
	gfx_flags=GFX_GetBestMode(block->flags);
	if (!gfx_flags) {
		if (render.op.type==OP_Normal) E_Exit("Failed to create a rendering output");
		else goto forcenormal;
	}
	/* Special test for normal2x to switch to normal with hardware scaling */
	if (gfx_flags & HAVE_SCALING && render.op.type==OP_Normal2x) {
		if (dblw) gfx_scalew*=2;
		if (dblh) gfx_scaleh*=2;
		block=&Normal_8;
		render.op.type=OP_Normal;
	}
	width*=block->xscale;
	if (gfx_flags & HAVE_SCALING) {
		height=MakeAspectTable(render.src.height,block->yscale,block->miny);
	} else {
		gfx_scaleh*=block->yscale;
		height=MakeAspectTable(render.src.height,gfx_scaleh,block->miny);
	}
/* Setup the scaler variables */
	render.op.mode=GFX_SetSize(width,height,gfx_flags,gfx_scalew,gfx_scaleh,&RENDER_ReInit);;
	if (render.op.mode==GFX_NONE) E_Exit("Failed to create a rendering output");
	render.op.line_handler=block->handlers[render.op.mode];
	render.op.width=width;
	render.op.height=height;
	Scaler_SrcWidth=render.src.width;
	Scaler_SrcHeight=render.src.height;
	RENDER_ResetPal();
	render.active=true;
}

void RENDER_SetSize(Bitu width,Bitu height,Bitu bpp,double ratio,bool dblw,bool dblh) {
	if (!width || !height) { 
		render.active=false;
		return;	
	}
	render.src.width=width;
	render.src.height=height;
	render.src.bpp=bpp;
	render.src.dblw=dblw;
	render.src.dblh=dblh;
	render.src.ratio=render.aspect ? ratio : 1.0;
	RENDER_ReInit();
}

extern void GFX_SetTitle(Bits cycles, Bits frameskip,bool paused);
static void IncreaseFrameSkip(void) {
	if (render.frameskip.max<10) render.frameskip.max++;
	LOG_MSG("Frame Skip at %d",render.frameskip.max);
	GFX_SetTitle(-1,render.frameskip.max,false);
}

static void DecreaseFrameSkip(void) {
	if (render.frameskip.max>0) render.frameskip.max--;
	LOG_MSG("Frame Skip at %d",render.frameskip.max);
	GFX_SetTitle(-1,render.frameskip.max,false);
}

void RENDER_Init(Section * sec) {
	Section_prop * section=static_cast<Section_prop *>(sec);

	render.pal.first=256;
	render.pal.last=0;
	render.aspect=section->Get_bool("aspect");
	render.frameskip.max=section->Get_int("frameskip");
	render.frameskip.count=0;
	render.updating=true;
#if (C_SSHOT)
	MAPPER_AddHandler(EnableScreenShot,MK_f5,MMOD1,"scrshot","Screenshot");
#endif
	const char * scaler;std::string cline;
	if (control->cmdline->FindString("-scaler",cline,false)) {
		scaler=cline.c_str();
	} else {
		scaler=section->Get_string("scaler");
	}
	if (!strcasecmp(scaler,"none")) render.op.want_type=OP_Normal;
	else if (!strcasecmp(scaler,"normal2x")) render.op.want_type=OP_Normal2x;
	else if (!strcasecmp(scaler,"advmame2x")) render.op.want_type=OP_AdvMame2x;
	else if (!strcasecmp(scaler,"advmame3x")) render.op.want_type=OP_AdvMame3x;
	else if (!strcasecmp(scaler,"advinterp2x")) render.op.want_type=OP_AdvInterp2x;
	else if (!strcasecmp(scaler,"interp2x")) render.op.want_type=OP_Interp2x;
	else if (!strcasecmp(scaler,"tv2x")) render.op.want_type=OP_TV2x;
	else {
		render.op.want_type=OP_Normal;
		LOG_MSG("Illegal scaler type %s,falling back to normal.",scaler);
	}
	MAPPER_AddHandler(DecreaseFrameSkip,MK_f7,MMOD1,"decfskip","Dec Fskip");
	MAPPER_AddHandler(IncreaseFrameSkip,MK_f8,MMOD1,"incfskip","Inc Fskip");
	GFX_SetTitle(-1,render.frameskip.max,false);
}

