#include <clib/alib_protos.h>
#include <pragma/datatypes_lib.h>
#include <pragma/dos_lib.h>
#include <pragma/exec_lib.h>
#include <pragma/graphics_lib.h>
#include <pragma/intuition_lib.h>
#include <pragma/jpeg_lib.h>
#include <pragma/render_lib.h>
#include <pragma/utility_lib.h>
#include <render/renderhooks.h>
#include <cybergraphx/cybergraphics.h>
#include <datatypes/pictureclass.h>
#include <exec/memory.h>
#include <intuition/icclass.h>
#include <string.h>
#include "class.h"

struct Arg1
{
long *mode,*dither,*depth,*dct,*scale,*quality,*smooth,gray,prog,fup;
};

ULONG SaveJPEG(IClass *cl,Object *obj,dtWrite *msg);

extern Library *SuperClassBase;

void ReadPrefs(Data *data)
{
char *var;
data->mode=1;
data->dither=0;
data->depth=8;
data->dct=0;
data->scale=1;
data->quality=75;
data->smooth=0;
data->gray=0;
data->prog=0;
data->fup=0;
if(var=(char *)AllocVec(512,0))
	{
	if(GetVar("ENV:DataTypes/jpeg.prefs",var,512,LV_VAR|GVF_GLOBAL_ONLY)>=0)
		{
		RDArgs *rdargs;
      if(rdargs=(RDArgs *)AllocDosObject(DOS_RDARGS,0))
			{
			RDArgs *args;
			Arg1 para;
			rdargs->RDA_Source.CS_Buffer=var;
			rdargs->RDA_Source.CS_Length=strlen(var);
			rdargs->RDA_Source.CS_CurChr=0;
			memset(&para,0,sizeof(Arg1));
			if(args=ReadArgs("MODE/A/N,DITHER/A/N,DEPTH/A/N,DCT/A/N,SCALE/A/N,QUALITY/A/N,SMOOTH/A/N,GRAY/S,PROGRESSIVE/S,FANCYUP",(long *)&para,rdargs))
				{
				data->mode=*para.mode;
				if(data->mode<0) data->mode=0;
				else if(data->mode>1) data->mode=1;
				data->dither=*para.dither;
				if(data->dither<0) data->dither=0;
				else if(data->dither>1) data->dither=1;
				data->depth=*para.depth;
				if(data->depth<3) data->depth=3;
				else if(data->depth>8) data->depth=8;
				data->dct=*para.dct;
				if(data->dct<0) data->dct=0;
				else if(data->dct>2) data->dct=2;
				data->scale=*para.scale;
				if(data->scale<0) data->scale=0;
				else if(data->scale>3) data->scale=3;
				data->scale=1<<data->scale;
				data->quality=*para.quality;
				if(data->quality<1) data->quality=1;
				else if(data->quality>100) data->quality=100;
				data->smooth=*para.smooth;
				if(data->smooth<0) data->smooth=0;
				else if(data->smooth>100) data->smooth=100;
				data->gray=para.gray;
				data->prog=para.prog;
				data->fup=para.fup;
				FreeArgs(args);
				}
			FreeDosObject(DOS_RDARGS,rdargs);
			}
		}
	FreeVec(var);
	}
if(SuperClassBase->lib_Version<43) data->mode=0;
}

struct JPEGINFO
{
Object *obj;
RastPort *rp,*trp;
UBYTE *buffer;
UWORD d,gray;
};

static void _getline(UBYTE *scanline,ULONG y,ULONG numb,JPEGINFO *info)
{
if(info->gray) WritePixelLine8(info->rp,0,y,numb,scanline,info->trp);
else
	{
	ULONG w=numb/3;
	if(info->d>8) DoMethod(info->obj,PDTM_WRITEPIXELARRAY,scanline,RECTFMT_RGB,numb,0,y,w,1);
	else
		{
		register ULONG i;
		register UBYTE *p1,*p2;
		p1=info->buffer+w*y*4;
		p2=scanline;
		for(i=0;i<w;i++,p1+=4,p2+=3)
			{
			p1[1]=p2[0];
			p1[2]=p2[1];
			p1[3]=p2[2];
			}
		}
	}
}

static ULONG getline(register __a0 UBYTE *scanline,register __d0 ULONG line,register __d1 ULONG numb,register __a1 void *userdata)
{
_getline(scanline,line-1,numb,(JPEGINFO *)userdata);
return 0;
}

ULONG Colors2DT(Object *obj,UBYTE *color)
{
UBYTE *cr;
ULONG *cregs,i,num,error=0;
GetDTAttrs(obj,PDTA_NumColors,&num,PDTA_ColorRegisters,&cr,PDTA_CRegs,&cregs,TAG_END);
if(cr&&cregs)
	{
	if(color)
		{
		for(i=0;i<num;i++,cr+=3,cregs+=3,color+=4)
			{
			cr[0]=color[1];
			cr[1]=color[2];
			cr[2]=color[3];
			cregs[0]=cr[0]<<24;
			cregs[1]=cr[1]<<24;
			cregs[2]=cr[2]<<24;
			}
		}
	else
		{
		for(i=0;i<num;i++,cr+=3,cregs+=3)
			{
			cr[0]=i;
			cr[1]=i;
			cr[2]=i;
			cregs[0]=i<<24;
			cregs[1]=cregs[0];
			cregs[2]=cregs[0];
			}
		}
	}
else error=ERROR_NO_FREE_STORE;
return error;
}

static void FillBMHD(Object *obj,BitMapHeader *bh,ULONG w,ULONG h,ULONG depth)
{
bh->bmh_Left=0;
bh->bmh_Top=0;
bh->bmh_Width=w;
bh->bmh_Height=h;
bh->bmh_PageWidth=w;
bh->bmh_PageHeight=h;
bh->bmh_Depth=depth;
bh->bmh_Compression=cmpByteRun1;
bh->bmh_XAspect=0;
bh->bmh_YAspect=0;
}

static void GetMode(Object *obj,BitMapHeader *bh,Data *data)
{
ULONG mode;
mode=BestModeID(
BIDTAG_NominalWidth,bh->bmh_PageWidth,BIDTAG_NominalHeight,bh->bmh_PageHeight,
BIDTAG_DesiredWidth,bh->bmh_Width,BIDTAG_DesiredHeight,bh->bmh_Height,
BIDTAG_Depth,bh->bmh_Depth,
BIDTAG_DIPFMustNotHave,DIPF_IS_DUALPF|DIPF_IS_PF2PRI|DIPF_IS_HAM|DIPF_IS_EXTRAHALFBRITE,TAG_END);
SetDTAttrs(obj,0,0,PDTA_ModeID,mode,TAG_END);
if(bh->bmh_Depth<=8) SetDTAttrs(obj,0,0,PDTA_NumColors,1<<bh->bmh_Depth,TAG_END);
}

static ULONG GetBody_Gray(Object *obj,JPEGDecHandle *jph,BitMapHeader *bh,BitMap *bm,Data *data)
{
ULONG error=0;
UBYTE *buffer;
RastPort rp;
if(buffer=(UBYTE *)AllocBufferFromJPEG(jph,JPG_ScaleDenom,data->scale,TAG_END))
	{
	if(!DecompressJPEG(jph,JPG_DestRGBBuffer,buffer,JPG_ScaleDenom,data->scale,JPG_DCTMethod,data->dct,JPG_FancyUpsampling,data->fup,TAG_END))
		{
		if(data->mode) DoMethod(obj,PDTM_WRITEPIXELARRAY,buffer,PBPAFMT_GREY8,bh->bmh_Width,0,0,bh->bmh_Width,bh->bmh_Height);
		else Chunky2BitMap(buffer,0,0,bh->bmh_Width,bh->bmh_Height,bm,0,0,TAG_END);
		}
	else error=ERROR_NO_FREE_STORE;
	FreeJPEGBuffer(buffer);
	}
else
	{
	BitMap *tbm;
	if(tbm=AllocBitMap(bh->bmh_Width,1,8,BMF_MINPLANES,bm))
		{
		RastPort trp;
		InitRastPort(&rp);
		InitRastPort(&trp);
		rp.BitMap=bm;
		trp.BitMap=tbm;
		JPEGINFO info={0,&rp,&trp,0,8,1};
		if(DecompressJPEG(jph,JPG_DecompressHook,getline,JPG_DecompressUserData,&info,JPG_ScaleDenom,data->scale,JPG_DCTMethod,data->dct,JPG_FancyUpsampling,data->fup,JPG_FancyUpsampling,data->fup,TAG_END)) error=ERROR_NO_FREE_STORE;
		FreeBitMap(tbm);
		}
	else error=ERROR_NO_FREE_STORE;
	}
return error;
}

static ULONG GetBody_RGB(Object *obj,JPEGDecHandle *jph,BitMapHeader *bh,Data *data)
{
ULONG error=0;
UBYTE *buffer;
if(buffer=(UBYTE *)AllocBufferFromJPEG(jph,JPG_ScaleDenom,data->scale,TAG_END))
	{
	if(!DecompressJPEG(jph,JPG_DestRGBBuffer,buffer,JPG_ScaleDenom,data->scale,JPG_DCTMethod,data->dct,JPG_FancyUpsampling,data->fup,TAG_END))
		{
		DoMethod(obj,PDTM_WRITEPIXELARRAY,buffer,RECTFMT_RGB,bh->bmh_Width*3,0,0,bh->bmh_Width,bh->bmh_Height);
		}
	else error=ERROR_NO_FREE_STORE;
	FreeJPEGBuffer(buffer);
	}
else
	{
	JPEGINFO info={obj,0,0,0,24,0};
	if(DecompressJPEG(jph,JPG_DecompressHook,getline,JPG_DecompressUserData,&info,JPG_ScaleDenom,data->scale,JPG_DCTMethod,data->dct,JPG_FancyUpsampling,data->fup,TAG_END)) error=ERROR_NO_FREE_STORE;
	}
return error;
}

static ULONG GetBody_RGB8(Object *obj,JPEGDecHandle *jph,BitMapHeader *bh,BitMap *bm,Data *data)
{
ULONG error=0;
UBYTE *rgb;
if(rgb=(UBYTE *)AllocVec(4*bh->bmh_Width*bh->bmh_Height,MEMF_CLEAR))
	{
	JPEGINFO info={obj,0,0,rgb,bh->bmh_Depth,0};
	if(!DecompressJPEG(jph,
		JPG_DecompressHook,getline,
		JPG_DecompressUserData,&info,
		JPG_ScaleDenom,data->scale,
		JPG_DCTMethod,data->dct,
		JPG_FancyUpsampling,data->fup,
		TAG_END))
		{
		APTR rmh;
		if(rmh=CreateRMHandler(RND_MemType,RMHTYPE_POOL,TAG_END))
			{
			ULONG *palette;
			if(palette=CreatePalette(RND_RMHandler,rmh,TAG_END))
				{
				APTR hst;
				if(hst=CreateHistogram(RND_RMHandler,rmh,TAG_END))
					{
					if(AddRGBImage(hst,(ULONG *)rgb,bh->bmh_Width,bh->bmh_Height,TAG_END)==ADDH_SUCCESS)
						{
						ULONG numc;
						GetDTAttrs(obj,PDTA_NumColors,&numc,TAG_END);
						if(ExtractPalette(hst,palette,numc,TAG_END)==EXTP_SUCCESS)
							{
							UBYTE *chunky;
							if(chunky=(UBYTE *)AllocVec(bh->bmh_Width*bh->bmh_Height,0))
								{
								if(Render((ULONG *)rgb,bh->bmh_Width,bh->bmh_Height,chunky,palette,RND_DitherMode,data->dither,TAG_END)==REND_SUCCESS)
									{
									UBYTE *color;
									if(color=(UBYTE *)AllocVec((1<<bh->bmh_Depth)*4,0))
										{
										ExportPalette(palette,color,TAG_END);
										if(!(error=Colors2DT(obj,color))) Chunky2BitMap(chunky,0,0,bh->bmh_Width,bh->bmh_Height,bm,0,0,TAG_END);
										FreeVec(color);
										}
									else error=ERROR_NO_FREE_STORE;
									}
								else error=ERROR_NO_FREE_STORE;
								FreeVec(chunky);
								}
							else error=ERROR_NO_FREE_STORE;
							}
						else error=ERROR_NO_FREE_STORE;
						}
					else error=ERROR_NO_FREE_STORE;
					DeleteHistogram(hst);
					}
				else error=ERROR_NO_FREE_STORE;
				DeletePalette(palette);
				}
			else error=ERROR_NO_FREE_STORE;
			DeleteRMHandler(rmh);
			}
		else error=ERROR_NO_FREE_STORE;
		}
	else error=ERROR_NO_FREE_STORE;
	FreeVec(rgb);
	}
else error=ERROR_NO_FREE_STORE;
return error;
}

static ULONG GetPicture(IClass *cl,Object *obj)
{
ULONG error=0;
long err;
BPTR file;
BitMapHeader *bh;
GetDTAttrs(obj,DTA_Name,&bh,TAG_END);
if(bh) SetDTAttrs(obj,0,0,DTA_ObjName,bh,TAG_END);
GetDTAttrs(obj,DTA_Handle,&file,PDTA_BitMapHeader,&bh,TAG_END);
if(bh)
	{
	JPEGDecHandle *jph;
	if(!file)
		{
		GetDTAttrs(obj,DTA_SourceType,&err,TAG_END);
		if(err!=DTST_RAM) return ERROR_REQUIRED_ARG_MISSING;
		else return 0;
		}
	if(!AllocJPEGDecompress(&jph,JPG_SrcFile,file,TAG_END))
		{
		UBYTE colorspace;
		ULONG w,h,bpp;
		Data *data=(Data *)INST_DATA(cl,obj);
		ReadPrefs(data);
		if(!GetJPEGInfo(jph,JPG_Width,&w,JPG_Height,&h,JPG_ColourSpace,&colorspace,JPG_BytesPerPixel,&bpp,JPG_ScaleDenom,data->scale,TAG_END))
			{
			BitMap *bm;
			switch(colorspace)
				{
				case JPCS_GRAYSCALE:
					FillBMHD(obj,bh,w,h,8);
					if(data->mode)
						{
						SetDTAttrs(obj,0,0,DTA_ErrorNumber,&error,DTA_NominalHoriz,w,DTA_NominalVert,h,PDTA_SourceMode,PMODE_V43,PDTA_ModeID,0,TAG_END);
						if(!error) error=GetBody_Gray(obj,jph,bh,bm,data);
						}
					else
						{
						GetMode(obj,bh,data);
						if(bm=AllocBitMap(w,h,8,BMF_CLEAR|BMF_DISPLAYABLE|BMF_STANDARD,0))
							{
							Colors2DT(obj,0);
							SetDTAttrs(obj,0,0,DTA_NominalHoriz,w,DTA_NominalVert,h,PDTA_BitMap,bm,TAG_END);
							error=GetBody_Gray(obj,jph,bh,bm,data);
							}
						else error=ERROR_NO_FREE_STORE;
						}
					break;
				case JPCS_RGB:
					if(data->mode)
						{
						FillBMHD(obj,bh,w,h,24);
						SetDTAttrs(obj,0,0,DTA_ErrorNumber,&error,DTA_NominalHoriz,w,DTA_NominalVert,h,PDTA_SourceMode,PMODE_V43,PDTA_ModeID,0,TAG_END);
						if(!error) error=GetBody_RGB(obj,jph,bh,data);
						}
					else
						{
						FillBMHD(obj,bh,w,h,data->depth);
						GetMode(obj,bh,data);
						if(bm=AllocBitMap(w,h,data->depth,BMF_CLEAR|BMF_DISPLAYABLE|BMF_STANDARD,0))
							{
							SetDTAttrs(obj,0,0,DTA_NominalHoriz,w,DTA_NominalVert,h,PDTA_BitMap,bm,TAG_END);
							error=GetBody_RGB8(obj,jph,bh,bm,data);
							}
						else error=ERROR_NO_FREE_STORE;
						}
					break;
				default:
					error=ERROR_NOT_IMPLEMENTED;
				}
			}
		else error=DTERROR_INVALID_DATA;
		}
	else error=DTERROR_INVALID_DATA;
	}
else error=ERROR_NO_FREE_STORE;
return error;
}

static ULONG mNew(IClass *cl,Object *obj,opSet *msg)
{
TagItem *ti;
if(ti=FindTagItem(DTA_SourceType,(((opSet *)msg)->ops_AttrList)))
	{
	if(ti->ti_Data!=DTST_FILE&&ti->ti_Data!=DTST_CLIPBOARD&&ti->ti_Data!=DTST_RAM)
		{
		SetIoErr(ERROR_OBJECT_WRONG_TYPE);
		return 0;
		}
	}
if(obj==(Object *)cl)
	{
	if(obj=(Object *)DoSuperMethodA(cl,obj,Msg(msg)))
		{
		ULONG error;
		if(error=GetPicture(cl,obj))
			{
			SetIoErr(error);
			CoerceMethod(cl,obj,OM_DISPOSE);
			obj=0;
			}
		}
	}
else
	{
	SetIoErr(ERROR_NOT_IMPLEMENTED);
	obj=0;
	}
return ULONG(obj);
}

static ULONG mSet(IClass *cl,Object *obj,opSet *msg)
{
ULONG retval;
if(retval=DoSuperMethodA(cl,obj,Msg(msg)))
	{
	RastPort *rp;
	if(rp=ObtainGIRPort(msg->ops_GInfo))
		{
		DoMethod(obj,GM_RENDER,msg->ops_GInfo,rp,GREDRAW_UPDATE);
		ReleaseGIRPort(rp);
		retval=0;
		}
	}
return retval;
}

static ULONG mWrite(IClass *cl,Object *obj,dtWrite *msg)
{
return (msg->dtw_Mode==DTWM_IFF)?DoSuperMethodA(cl,obj,Msg(msg)):SaveJPEG(cl,obj,msg);
}

extern "C" ULONG Dispatcher(register __a0 IClass *cl,register __a2 Object *obj,register __a1 Msg msg)
{
ULONG retval;
switch(msg->MethodID)
	{
	case OM_NEW:
		retval=mNew(cl,obj,(opSet *)msg);
		break;
	case OM_UPDATE:
		if(DoMethod(obj,ICM_CHECKLOOP)) break;
	case OM_SET:
		retval=mSet(cl,obj,(opSet *)msg);
		break;
	case DTM_WRITE:
		retval=mWrite(cl,obj,(dtWrite *)msg);
		break;
	default:
		retval=DoSuperMethodA(cl,obj,msg);
		}
return retval;
}

