/* Mednafen - Multi-system Emulator
 *
 * 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
 */

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <zlib.h>

#include <trio/trio.h>

#include "types.h"
#include "mednafen.h"
#include "video.h"
#include "general.h"
#include "memory.h"
#include "state.h"
#include "movie.h"
#include "driver.h"
#include "ConvertUTF.h"

typedef struct
{
	uint16 glyph_num;
	uint8 data[7];
} font5x7;

typedef struct
{
        uint16 glyph_num;
        uint8 data[13];
} font6x13;

typedef struct
{
        uint16 glyph_num;
        uint8 data[13 * 2];
} font12x13;

static font5x7 FontData5x7[] =
{
	#include "font5x7.h"
};

static font6x13 FontData6x13[]=
{
        #include "font6x13.h"
};

static font12x13 FontData12x13[]=
{
        #include "font12x13.h"
};

static uint8 FontDataCache5x7[65536][7];
static uint8 FontDataCache6x13[65536][13];
static uint8 FontDataCache12x13[65536][13 * 2];

static uint32 FontDataHave5x7[65536 / 32];
static uint32 FontDataHave6x13[65536 / 32];
static uint32 FontDataHave12x13[65536 / 32];

void MDFN_InitFontData(void)
{
 unsigned int x;
 unsigned int inx;

 memset(FontDataHave5x7, 0, sizeof(FontDataHave5x7));
 memset(FontDataHave6x13, 0, sizeof(FontDataHave6x13));
 memset(FontDataHave12x13, 0, sizeof(FontDataHave12x13));

 for(inx=x=0;x<65536;x++)
 {
  if(inx < (sizeof(FontData5x7) / sizeof(font5x7)) && FontData5x7[inx].glyph_num == x)
  {
   memcpy(FontDataCache5x7[x], FontData5x7[inx].data, 7);
   FontDataHave5x7[x >> 5] |= 1 << (x & 0x1F);
   inx++;
  }
  else
   memset(FontDataCache5x7[x], 0, 7);
 }

 for(inx=x=0;x<65536;x++)
 {
  if(inx < (sizeof(FontData6x13) / sizeof(font6x13)) && FontData6x13[inx].glyph_num == x)
  {
   memcpy(FontDataCache6x13[x], FontData6x13[inx].data, 13);
   FontDataHave6x13[x >> 5] |= 1 << (x & 0x1F);
   inx++;
  }
  else
   memset(FontDataCache6x13[x], 0, 13);
 }

 for(inx=x=0;x<65536;x++)
 {
  if(inx < (sizeof(FontData12x13) / sizeof(font12x13)) && FontData12x13[inx].glyph_num == x)
  {
   memcpy(FontDataCache12x13[x], FontData12x13[inx].data, 13 * 2);
   FontDataHave12x13[x >> 5] |= 1 << (x & 0x1F);
   inx++;
  }
  else
   memset(FontDataCache12x13[x], 0, 13);
 }

}

static long min(long a1, long a2)
{
 if(a1 < a2)
  return(a1);
 return(a2);
}

uint32 GetTextPixLength(uint8 *msg)
{
 UTF32 utf32_buf[256];
 UTF32 *tstart = utf32_buf;
 uint32 slen;
 uint32 pixwidth;
 uint32 x;
 uint8 glyph_width[256];
 uint8 glyph_ov_width[256];

 ConvertUTF8toUTF32((const UTF8**)&msg, &msg[strlen((char *)msg)], &tstart, &tstart[256], lenientConversion);

 slen = (tstart - utf32_buf);

 pixwidth = 0;

 for(x=0;x<slen;x++)
 {
  uint32 thisglyph = utf32_buf[x];
  if(thisglyph > 0xFFFF) thisglyph = ' ';

  if(FontDataHave6x13[thisglyph >> 5] & (1 << (thisglyph & 0x1F)))
  {
   glyph_width[x] = 6;
   pixwidth += 6;
  }
  else if(FontDataHave12x13[thisglyph >> 5] & (1 << (thisglyph & 0x1F)))
  {
   glyph_width[x] = 12;
   pixwidth += 12;
  }
  else
  {
   glyph_width[x] = 6;
   pixwidth += 6;
  }
  if(thisglyph >= 0x0300 && thisglyph <= 0x036F)
  {
   if(x != (slen-1))
   {
    pixwidth -= min(glyph_width[x], glyph_width[x + 1]);
   }
   glyph_ov_width[x] = 0;
  }
  else
   glyph_ov_width[x] = glyph_width[x];
 }
 return(pixwidth);
}

static void DrawTextSub(uint8 *msg, uint8 **glyph_ptrs, uint8 *glyph_width, uint8 *glyph_ov_width, uint32 width, uint32 &pixwidth, uint32 &slen, bool want5x7 = 0)
{
 UTF32 utf32_buf[256];
 UTF32 *tstart = utf32_buf;
 unsigned int x;

 ConvertUTF8toUTF32((const UTF8**)&msg, &msg[strlen((char *)msg)], &tstart, &tstart[256], lenientConversion);

 slen = (tstart - utf32_buf);

 pixwidth = 0;

 for(x=0;x<slen;x++)
 {
  uint32 thisglyph = utf32_buf[x];

  if(want5x7)
  {
   if(FontDataHave5x7[thisglyph >> 5] & (1 << (thisglyph & 0x1F)))
   {
    glyph_ptrs[x] = FontDataCache5x7[thisglyph];
    glyph_width[x] = 5;
    pixwidth += 5;
   }
   else
   {
    glyph_ptrs[x] = FontDataCache5x7[(unsigned char)'?'];
    glyph_width[x] = 5;
    pixwidth += 5;
   }
  }
  else if(FontDataHave6x13[thisglyph >> 5] & (1 << (thisglyph & 0x1F)))
  {
   glyph_ptrs[x] = FontDataCache6x13[thisglyph];
   glyph_width[x] = 6;
   pixwidth += 6;
  }
  else if(FontDataHave12x13[thisglyph >> 5] & (1 << (thisglyph & 0x1F)))
  {
   glyph_ptrs[x] = FontDataCache12x13[thisglyph];
   glyph_width[x] = 12;
   pixwidth += 12;
  }
  else
  {
   glyph_ptrs[x] = FontDataCache6x13[(unsigned char)'?'];
   glyph_width[x] = 6;
   pixwidth += 6;
  }

  if(thisglyph >= 0x0300 && thisglyph <= 0x036F)
  {
   if(x != (slen-1))
   {
    pixwidth -= min(glyph_width[x], glyph_width[x + 1]);
   }
   glyph_ov_width[x] = 0;
  }
  else
   glyph_ov_width[x] = glyph_width[x];
  if(pixwidth > width) // Oopsies, it's too big for the screen!  Just truncate it for now.
  {
   slen = x;
   pixwidth -= glyph_width[x];
   break;
  }
 }

}

uint32 DrawTextTrans(uint8 *dest, int pitch, uint32 width, uint8 *msg, uint32 fgcolor, int centered, bool want5x7)
{
 uint32 slen;
 uint32 pixwidth;
 uint32 x;
 uint8 *glyph_ptrs[256];
 uint8 glyph_width[256];
 uint8 glyph_ov_width[256];

 pitch /= sizeof(uint8);

 DrawTextSub(msg, glyph_ptrs, glyph_width, glyph_ov_width, width, pixwidth, slen, want5x7);

 if(centered)
 {
  int32 poot = width - pixwidth;

  dest += poot / 2;
 }

 if(want5x7)
  for(x=0;x<slen;x++)
  {
   int gx, gy;
   uint8 *src_glyph = glyph_ptrs[x];

   for(gy=0;gy<7;gy++)
   {
    for(gx=0;gx<glyph_width[x];gx++)
    {
     if((src_glyph[gy] << gx) & 0x80) dest[gy * pitch + gx] = fgcolor;
    }
   }
   dest += glyph_ov_width[x];
  }
 else
  for(x=0;x<slen;x++)
  {
   int gx, gy;
   uint8 *src_glyph = glyph_ptrs[x];

   if(glyph_width[x] > 8)
   {
    for(gy=0;gy<13;gy++)
    {
     for(gx=0;gx<glyph_width[x];gx++)
     {
       if((src_glyph[gy * 2 + (gx >> 3)] << (gx & 0x7)) & 0x80) dest[gy * pitch + gx] = fgcolor;
     }
    }
    //dest += glyph_ov_width[x];
   }
   else
   { 
    for(gy=0;gy<13;gy++)
    {
     for(gx=0;gx<glyph_width[x];gx++)
     {
      if((src_glyph[gy] << gx) & 0x80) dest[gy * pitch + gx] = fgcolor;
     }
    }
    //dest += glyph_width[x];
   }
   dest += glyph_ov_width[x];
  }

 return(pixwidth);
}

uint32 DrawTextTrans(uint32 *dest, int pitch, uint32 width, uint8 *msg, uint32 fgcolor, int centered, bool want5x7)
{
 uint32 slen;
 uint32 pixwidth;
 uint32 x;
 uint8 *glyph_ptrs[256];
 uint8 glyph_width[256];
 uint8 glyph_ov_width[256];

 pitch /= sizeof(uint32);

 DrawTextSub(msg, glyph_ptrs, glyph_width, glyph_ov_width, width, pixwidth, slen, want5x7);

 if(centered)
 {
  int32 poot = width - pixwidth;

  dest += poot / 2;
 }

 if(want5x7)
  for(x=0;x<slen;x++)
  {
   int gx, gy;
   uint8 *src_glyph = glyph_ptrs[x];

   for(gy=0;gy<7;gy++)
   {
    for(gx=0;gx<glyph_width[x];gx++)
    {
     if((src_glyph[gy] << gx) & 0x80) dest[gy * pitch + gx] = fgcolor;
    }
   }
   dest += glyph_ov_width[x];
  }
 else
  for(x=0;x<slen;x++)
  {
   int gx, gy;
   uint8 *src_glyph = glyph_ptrs[x];

   if(glyph_width[x] > 8)
   {
    for(gy=0;gy<13;gy++)
    {
     for(gx=0;gx<glyph_width[x];gx++)
     {
       if((src_glyph[gy * 2 + (gx >> 3)] << (gx & 0x7)) & 0x80) dest[gy * pitch + gx] = fgcolor;
     }
    }
    //dest += glyph_ov_width[x];
   }
   else
   {
    for(gy=0;gy<13;gy++)
    {
     for(gx=0;gx<glyph_width[x];gx++)
     {
      if((src_glyph[gy] << gx) & 0x80) dest[gy * pitch + gx] = fgcolor;
     }
    }
    //dest += glyph_width[x];
   }
   dest += glyph_ov_width[x];
  }
 return(pixwidth);
}

uint32 DrawTextTransShadow(uint32 *dest, int pitch, uint32 width, uint8 *textmsg, uint32 fgcolor, uint32 shadcolor, int centered)
{
 DrawTextTrans(dest + 1 + (pitch >> 2), pitch, width, textmsg, shadcolor, centered);
 return(DrawTextTrans(dest, pitch, width, textmsg, fgcolor, centered));
}


static uint32 howlong = 0;
static UTF8 *CurrentMessage = NULL;

#include "drawing.h"

void MDFNI_SaveSnapshot(void)
{
 int x=SaveSnapshot();
 if(!x)
  MDFN_DispMessage((UTF8 *)_("Error saving screen snapshot."));
 else
  MDFN_DispMessage((UTF8 *)_("Screen snapshot %d saved."),x-1);
}

void MDFNNES_DrawOverlay(uint32 *XBuf);
bool MDFNI_DrawOverlay(uint32 *XBuf, uint32 *OSDBuf, const MDFN_Rect *OSDRect, uint32 pitch32)
{
	bool dirty = 0;

	if(MDFNGameInfo)
	{
	 if(MDFNGameInfo->system == GISYS_NES)
	  MDFNNES_DrawOverlay(XBuf);
         if(MDFN_DrawSaveStates(OSDBuf, OSDRect, pitch32)) dirty = 1;
         if(MDFN_DrawMovies(OSDBuf, OSDRect, pitch32)) dirty = 1;
	}
        if(DrawMessage(OSDBuf, OSDRect, pitch32))
	 dirty = 1;
	return(dirty);
}

void MDFN_DispMessage(UTF8 *format, ...)
{
 va_list ap;

 if(CurrentMessage)
 {
  free(CurrentMessage);
  CurrentMessage = NULL;
 }

 va_start(ap,format);
 trio_vasprintf((char **)&CurrentMessage, (char *)format,ap);
 va_end(ap);

 if(CurrentMessage)
  howlong = MDFND_GetTime() + 2500;
}

void MDFN_ResetMessages(void)
{
 howlong=0;
}

static int WritePNGChunk(FILE *fp, uint32 size, char *type, uint8 *data)
{
 uint32 crc;

 uint8 tempo[4];

 tempo[0]=size>>24;
 tempo[1]=size>>16;
 tempo[2]=size>>8;
 tempo[3]=size;

 if(fwrite(tempo,4,1,fp)!=1)
  return 0;
 if(fwrite(type,4,1,fp)!=1)
  return 0;

 if(size)
  if(fwrite(data,1,size,fp)!=size)
   return 0;

 crc = crc32(0,(uint8 *)type,4);
 if(size)
  crc = crc32(crc,data,size);

 tempo[0]=crc>>24;
 tempo[1]=crc>>16;
 tempo[2]=crc>>8;
 tempo[3]=crc;

 if(fwrite(tempo,4,1,fp)!=1)
  return 0;
 return 1;
}

int SaveSnapshot(void)
{
 std::string fn;
 int x,u,y;
 FILE *pp=NULL;
 uint8 *compmem = NULL;
 uLongf compmemsize = (uLongf)( (MDFNGameInfo->DisplayRect.h * (MDFNGameInfo->DisplayRect.w + 1) * 3 * 1.001 + 1) + 12 );

 if(!(compmem=(uint8 *)MDFN_malloc(compmemsize)))
  return 0;


 if(!(pp = fopen(MDFN_MakeFName(MDFNMKF_SNAP_DAT, 0, NULL).c_str(), "rb")))
  u = 0;
 else
 {
  if(fscanf(pp, "%d", &u) == 0)
   u = 0;
  fclose(pp);
 }

 if(!(pp = fopen(MDFN_MakeFName(MDFNMKF_SNAP_DAT, 0, NULL).c_str(), "wb")))
  return(0);
 fseek(pp, 0, SEEK_SET);
 fprintf(pp, "%d\n", u + 1);
 fclose(pp);

 fn = MDFN_MakeFName(MDFNMKF_SNAP, u, "png");

 if(!(pp=fopen(fn.c_str(),"wb")))
 {
  return 0;
 }
 {
  static uint8 header[8]={137,80,78,71,13,10,26,10};
  if(fwrite(header,8,1,pp)!=1)
   goto PNGerr;
 }

 {
  uint8 chunko[13];

  chunko[0]=
  chunko[1] = MDFNGameInfo->DisplayRect.w >> 16;
  chunko[2] = MDFNGameInfo->DisplayRect.w >> 8;
  chunko[3] = MDFNGameInfo->DisplayRect.w & 0xFF;

  chunko[4]=chunko[5]=chunko[6]=0;
  chunko[7] = MDFNGameInfo->DisplayRect.h;			// Height

  chunko[8]=8;				// 8 bits per sample(24 bits per pixel)
  chunko[9]=2;				// Color type; RGB triplet
  chunko[10]=0;				// compression: deflate
  chunko[11]=0;				// Basic adapative filter set(though none are used).
  chunko[12]=0;				// No interlace.

  if(!WritePNGChunk(pp,13,"IHDR",chunko))
   goto PNGerr;
 }

 {
  uint8 *tmp_buffer;
  uint8 *tmp_inc;
  tmp_inc = tmp_buffer = (uint8 *)malloc((MDFNGameInfo->DisplayRect.w * 3 + 1) * MDFNGameInfo->DisplayRect.h);

  for(y=0;y<MDFNGameInfo->DisplayRect.h;y++)
  {
   *tmp_inc = 0;
   tmp_inc++;
   for(x=0;x<MDFNGameInfo->DisplayRect.w;x++)
   {
    int r,g,b;
    DECOMP_COLOR(*(uint32 *)((uint8 *)MDFNGameInfo->fb + (y + MDFNGameInfo->DisplayRect.y) * MDFNGameInfo->pitch + (x + MDFNGameInfo->DisplayRect.x) * 4), r, g, b);
    tmp_inc[0] = r;
    tmp_inc[1] = g;
    tmp_inc[2] = b;
    tmp_inc += 3;
   }
  }

  if(compress(compmem, &compmemsize, tmp_buffer, MDFNGameInfo->DisplayRect.h * (MDFNGameInfo->DisplayRect.w * 3 + 1))!=Z_OK)
  {
   if(tmp_buffer) free(tmp_buffer);
   goto PNGerr;
  }
  if(tmp_buffer) free(tmp_buffer);
  if(!WritePNGChunk(pp,compmemsize,"IDAT",compmem))
   goto PNGerr;
 }
 if(!WritePNGChunk(pp,0,"IEND",0))
  goto PNGerr;

 free(compmem);
 fclose(pp);

 return u+1;

 PNGerr:
 if(compmem)
  free(compmem);
 if(pp)
  fclose(pp);
 return(0);
}
