// EmuScan routines for Pico, also simple text and shape drawing routines.

// (c) Copyright 2006, notaz
// All Rights Reserved

#include "vid.h"
#include "ClientServer.h"
#include "SimpleServer.h"
#include "pico\picoInt.h"
#include "blit.h"
#include "debug.h"

// global stuff
extern TPicoConfig currentConfig;
extern TPicoKeyConfigEntry *keyConfig;
extern TPicoAreaConfigEntry areaConfig[];
extern const char * actionNames[];

// main framebuffer
static void *screenbuff = 0; // pointer to real device video memory
//static
unsigned short *framebuff = 0;  // temporary buffer in sega native BGR format
const  int framebuffsize  = (8+320)*(8+208+8)*2; // actual framebuffer size (in bytes+to support new rendering mode)
static int framebuff_offs = 0; // where to start copying (in pixels)
static int framebuff_len  = 0; // how much of the framebuffer actually needs to be copied (in pixels)
static int fc_lines, fc_inc; // lines, inc for "0 fit2" mode

// drawer function pointers
void (*drawTextFps)(const char *text) = 0;
void (*drawTextNotice)(const char *text) = 0;

// blitter
void (*vidBlit)(int full) = 0;
void (*vidBlitKeyCfg)(int full) = 0;

// stuff for rendermode2
static unsigned short cram_high[0x40];
static unsigned short dt_dmask=0x0333;
unsigned short color_redM2 = 0x022F;

// colors
const unsigned short color_red     = 0x022F;
const unsigned short color_red_dim = 0x0004;
const unsigned short color_green   = 0x01F1;
const unsigned short color_blue    = 0x0F11;
const unsigned short color_grey    = 0x0222;

// other
int txtheight_fit = 138;

// bitmasks
static const unsigned long mask_numbers[] = {
	0x12244800, // 47 2F /
	0x69999600, // 48 30 0
	0x26222200, // 49 31 1
	0x69168F00, // 50 32 2
	0x69219600, // 51 33 3
	0x266AF200, // 52 34 4
	0xF8E11E00, // 53 35 5
	0x68E99600, // 54 36 6
	0x71222200, // 55 37 7
	0x69699600, // 56 38 8
	0x69719600, // 57 39 9
	0x04004000, // 58 3A :
	0x04004400, // 59 3B ;
	0x01242100, // 60 3C <
	0x00707000, // 61 3D =
	0x04212400, // 62 3E >
	0x69240400, // 63 3F ?
	0x00000000, // 64 40 @ [used instead of space for now]
	0x22579900, // 65 41 A
	0xE9E99E00, // 66 42 B
	0x69889600, // 67 43 C
	0xE9999E00, // 68 44 D
	0xF8E88F00, // 69 45 E
	0xF8E88800, // 70 46 F
	0x698B9700, // 71 47 G
	0x99F99900, // 72 48 H
	0x44444400, // 73 49 I
	0x11119600, // 74 4A J
	0x9ACCA900, // 75 4B K
	0x88888F00, // 76 4C L
	0x9F999900, // 77 4D M
	0x9DDBB900, // 78 4E N
	0x69999600, // 79 4F O
	0xE99E8800, // 80 50 P
	0x6999A500, // 81 51 Q
	0xE99E9900, // 82 52 R
	0x69429600, // 83 53 S
	0x72222200, // 84 54 T
	0x99999600, // 85 55 U
	0x55552200, // 86 56 V
	0x9999F900, // 87 57 W
	0x55225500, // 88 58 X
	0x55222200, // 89 59 Y
	0xF1248F00, // 90 5A Z
};


////////////////////////////////
// Cram functions

inline int EmuCramNull(int cram)
{
  User::Panic(_L("Cram called!!"), 0);
  return cram;
}


////////////////////////////////
// PicoScan functions in "center" mode

int EmuScanCenter0(unsigned int num, unsigned short *sdata)
{
  //unsigned short *vidmem=framebuff;

  //unsigned short *sp, *sto;
  //sp=sdata+56; sto=sdata+264; vidmem += num*208;

  //do { *vidmem++ = *sp++; } while(sp < sto);
  memcpy(framebuff + num*208, sdata+56, 208*2); // memcpy gives ~1 fps (~2 with optimized memcpy)

  return 0;
}


int EmuScanCenter90(unsigned int num, unsigned short *sdata)
{
  // ignore top and bottom lines
  if(num < 8)   return 7-num;     // skip first 8 lines
  if(num > 215) return 223+8-num; // this should not happen, just in case

  num -= 8;
  if(!num) {
	if(Pico.video.reg[12]&1) { // copy less in 32-col mode
	  framebuff_offs= 0;
      framebuff_len = 208*320;
	} else {
	  framebuff_offs= 208*32;
      framebuff_len = 208*256;
	}
  }

  unsigned short *vidmem=framebuff;
  vidmem += 207-num; // adjust x

  // do less copy in 32-column mode
  unsigned short *sp, *sto;
  int pixels;
  if(!(Pico.video.reg[12]&1))
       { sp=sdata+32; sto=sdata+288; pixels = 288; vidmem += 32*208; }
  else { sp=sdata;    sto=sdata+320; pixels = 320; }

  do { *vidmem = *sp++; vidmem+=208; } while(sp < sto);

  if(num == 207) return 16; // skip bottom of this frame and top of next

  return 0;
}


int EmuScanCenter180(unsigned int num, unsigned short *sdata)
{
  unsigned short *vidmem=framebuff;

  unsigned short *sp, *sto;
  sp=sdata+56; sto=sdata+264; vidmem += (224-num)*208;

  do { *(--vidmem) = *sp++; } while(sp < sto); // reversed

  return 0;
}


int EmuScanCenter270(unsigned int num, unsigned short *sdata)
{
  // ignore top and bottom lines
  if(num < 8)   return 7-num;     // skip first 8 lines
  if(num > 215) return 223-num+8;

  num -= 8;
  if(num > 207) return 0;
  if(!num) {
	if(Pico.video.reg[12]&1) {
	  framebuff_offs= 0;
      framebuff_len = 208*320;
	} else {
	  framebuff_offs= 208*32;
      framebuff_len = 208*256;
	}
  }

  unsigned short *vidmem=framebuff+320*208;
  vidmem -= 208-num; // adjust x

  // do less copy in 32-column mode
  unsigned short *sp, *sto;
  if(!(Pico.video.reg[12]&1))
       { sp=sdata+32; sto=sdata+288; vidmem -= 32*208; }
  else { sp=sdata;    sto=sdata+320; }

  do { *vidmem = *sp++; vidmem-=208; } while(sp < sto);

  if(num == 207) return 16; // skip bottom of this frame and top of next

  return 0;
}



////////////////////////////////
// PicoScan functions in "fit" mode

static int EmuScanFit0(unsigned int num, unsigned short *sdata)
{
  // 0.65, 145 lines in normal mode; 0.8125, 182 lines in 32-column mode

  // draw this line? (ARM4s don't support division, so do some tricks here)
  static int u = 0, num2 = 0;
  if(!num) {
    u = num2 = 0;
	if(currentConfig.iScreenMode == TPicoConfig::PMFit) {
      if(Pico.video.reg[12]&1) { // 32 col mode? This can change on any frame
        fc_inc = 6500;
	    txtheight_fit = 138;
        framebuff_len = 208*145;
	    memset(framebuff+208*145, 0, 208*37*2);
      } else {
        fc_inc = 8125;
	    txtheight_fit = 175;
        framebuff_len = 208*182;
      }
	}
  }
  u += fc_inc;
  if(u < 10000) return 0;
  u -= 10000;

  unsigned short *vidmem=framebuff;

  int slen;
  unsigned short *sp;
  if(!(Pico.video.reg[12]&1))
       { sp=sdata+32; slen=256; }
  else { sp=sdata;    slen=320; }

  vidmem += num2*208;
/*
  int i=0;
  while(sp < sto) {
	i += inc;
    if(i >= 10000) {
	  *vidmem++ = *sp;
	  i -= 10000;
	}
	sp++;
  }
*/
  PicuShrink(vidmem, 208, sp, slen);

  num2++;

  // calculate how many lines pico engine should skip,
  // making sure this func will be called on scanline 0
  int skip = 0;
  while(u+fc_inc < 10000 && num+skip != 223) { u+=fc_inc; skip++; }

  return skip;
}

int EmuScanFit90(unsigned int num, unsigned short *sdata)
{
  // 0.9285

  // draw this line?
  static int u = 0, num2 = 0;
  if(!num) {
    u = num2 = 0;
	if(Pico.video.reg[12]&1) {
	  framebuff_offs= 0;
      framebuff_len = 208*320;
	} else {
	  framebuff_offs= 208*32;
      framebuff_len = 208*256;
	}
  }
  u += 9285;
  if(u < 10000) return 0;
  u -= 10000;

  unsigned short *vidmem=framebuff;
  vidmem += 207-num2; // adjust x

  // do less copy in 32-column mode
  unsigned short *sp, *sto;
  if(!(Pico.video.reg[12]&1))
       { sp=sdata+32; sto=sdata+288; vidmem += 32*208; }
  else { sp=sdata;    sto=sdata+320; }

  do { *vidmem = *sp++; vidmem+=208; } while(sp < sto);

  num2++;

  // skip next line?
  if(u+9285 < 10000 && num != 223) { u+=9285; return 1; }

  return 0;
}

int EmuScanFit180(unsigned int num, unsigned short *sdata)
{
  // 0.65, 145 lines in normal mode; 0.8125, 182 lines in 32-column mode

  // draw this line? (ARM4s don't support division)
  static int u = 0, num2 = 0;
  if(!num) {
    u = num2 = 0;
	if(currentConfig.iScreenMode == TPicoConfig::PMFit) {
      if(Pico.video.reg[12]&1) { // 32 col mode? This can change on any frame
	    fc_lines = 145;
        fc_inc = 6500;
	    txtheight_fit = 138;
        framebuff_len = 208*145;
	    memset(framebuff+208*145, 0, 208*37*2);
      } else {
        fc_lines = 182;
        fc_inc = 8125;
	    txtheight_fit = 175;
        framebuff_len = 208*182;
      }
    }
  }
  u += fc_inc;
  if(u < 10000) return 0;
  u -= 10000;

  unsigned short *vidmem=framebuff;

  int slen;
  unsigned short *sp;
  if(!(Pico.video.reg[12]&1))
       { sp=sdata+32; slen=256; }
  else { sp=sdata;    slen=320; }

  vidmem += (fc_lines-num2)*208;

  PicuShrinkReverse(vidmem, 208, sp, slen);

  num2++;

  // skip some lines?
  int skip = 0;
  while(u+fc_inc < 10000 && num+skip != 223) { u+=fc_inc; skip++; }

  return skip;
}

int EmuScanFit270(unsigned int num, unsigned short *sdata)
{
  // 0.9285

  // draw this line?
  static int u = 0, num2 = 0;
  if(!num) {
    u = num2 = 0;
	if(Pico.video.reg[12]&1) {
	  framebuff_offs= 0;
      framebuff_len = 208*320;
	} else {
	  framebuff_offs= 208*32;
      framebuff_len = 208*256;
	}
  }
  u += 9285;
  if(u < 10000) return 0;
  u -= 10000;

  unsigned short *vidmem=framebuff+320*208;
  vidmem -= 208-num2; // adjust x

  // do less copy in 32-column mode
  unsigned short *sp, *sto;
  if(!(Pico.video.reg[12]&1))
       { sp=sdata+32; sto=sdata+288; vidmem -= 32*208; }
  else { sp=sdata;    sto=sdata+320; }

  do { *vidmem = *sp++; vidmem-=208; } while(sp < sto);

  num2++;

  // skip next line?
  if(u+9285 < 10000 && num != 223) { u+=9285; return 1; }

  return 0;
}


////////////////////////////////
// text drawers
// warning: text must be at least 1px away from screen borders

void drawTextM2(int x, int y, const char *text, long color)
{
	unsigned short *vidmem=framebuff;
	int charmask, i, cx = x, cy;
	unsigned short *l, *le;

	// darken the background (left border)
	for(l=vidmem+(cx-1)+(y-1)*328, le=vidmem+(cx-1)+(y+7)*328; l < le; l+=328)
		*l = (*l >> 2) & dt_dmask;

	for(const char *p=text; *p; p++) {
		cy = y;
		charmask = *(mask_numbers + (*p - 0x2F));

		for(l = vidmem+cx+(y-1)*328, le = vidmem+cx+(y+7)*328; l < le; l+=328-4) {
			*l = (*l >> 2) & dt_dmask; l++; *l = (*l >> 2) & dt_dmask; l++;
			*l = (*l >> 2) & dt_dmask; l++; *l = (*l >> 2) & dt_dmask; l++;
			*l = (*l >> 2) & dt_dmask;
		}

		for(i=0; i < 24; i++) {
			// draw dot. Is this fast?
			if(charmask&0x80000000) *( vidmem + (cx+(i&3)) + (cy+(i>>2))*328 ) = color;
			charmask <<= 1;
		}
		cx += 5;
	}
}

void drawText0(int x, int y, const char *text, long color)
{
	unsigned short *vidmem=framebuff;
	int charmask, i, cx = x, cy;
	unsigned short *l, *le, dmask=0x0333;

	// darken the background (left border)
	for(l=vidmem+(cx-1)+(y-1)*208, le=vidmem+(cx-1)+(y+7)*208; l < le; l+=208)
		*l = (*l >> 2) & dmask;

	for(const char *p=text; *p; p++) {
		cy = y;
		charmask = *(mask_numbers + (*p - 0x2F));

		for(l = vidmem+cx+(y-1)*208, le = vidmem+cx+(y+7)*208; l < le; l+=208-4) {
			*l = (*l >> 2) & dmask; l++; *l = (*l >> 2) & dmask; l++;
			*l = (*l >> 2) & dmask; l++; *l = (*l >> 2) & dmask; l++;
			*l = (*l >> 2) & dmask;
		}

		for(i=0; i < 24; i++) {
			// draw dot. Is this fast?
			if(charmask&0x80000000) *( vidmem + (cx+(i&3)) + (cy+(i>>2))*208 ) = color;
			charmask <<= 1;
		}
		cx += 5;
	}
}

void drawText90(int x, int y, const char *text, long color)
{
	unsigned short *vidmem=framebuff;
	unsigned short *l, *le, dmask=0x0333;
	int charmask, i, cx, cy = y;

	for(l=vidmem+(x+1)+(cy-1)*208, le=vidmem+(x-7)+(cy-1)*208; l > le; l--)
		*l = (*l >> 2) & dmask;

	for(const char *p=text; *p; p++) {
		cx = x;
		charmask = *(mask_numbers + (*p - 0x2F));

		for(l = vidmem+(x+1)+(cy)*208, le = vidmem+(x+1)+(cy+5)*208; l < le; l+=208+7) {
			*l = (*l >> 2) & dmask; l--; *l = (*l >> 2) & dmask; l--;
			*l = (*l >> 2) & dmask; l--; *l = (*l >> 2) & dmask; l--;
			*l = (*l >> 2) & dmask; l--; *l = (*l >> 2) & dmask; l--;
			*l = (*l >> 2) & dmask; l--; *l = (*l >> 2) & dmask;
		}

		for(i=0; i < 24; i++) {
			if(charmask&0x80000000) *( vidmem + (cy+(i&3))*208 + (cx-(i>>2)) ) = color;
			charmask <<= 1;
		}
		cy += 5;
	}
}

void drawText180(int x, int y, const char *text, long color)
{
	unsigned short *vidmem=framebuff;
	int charmask, i, cx = x, cy;
	unsigned short *l, *le, dmask=0x0333;

	for(l=vidmem+(cx+1)+(y+1)*208, le=vidmem+(cx+1)+(y-7)*208; l > le; l-=208)
		*l = (*l >> 2) & dmask;

	for(const char *p=text; *p; p++) {
		cy = y;
		charmask = *(mask_numbers + (*p - 0x2F));

		for(l = vidmem+cx+(y+1)*208, le = vidmem+cx+(y-8)*208; l > le; l-=208-4) {
			*l = (*l >> 2) & dmask; l--; *l = (*l >> 2) & dmask; l--;
			*l = (*l >> 2) & dmask; l--; *l = (*l >> 2) & dmask; l--;
			*l = (*l >> 2) & dmask;
		}

		for(i=0; i < 24; i++) {
			if(charmask&0x80000000) *( vidmem + (cx-(i&3)) + (cy-(i>>2))*208 ) = color;
			charmask <<= 1;
		}
		cx -= 5;
	}
}

void drawText270(int x, int y, const char *text, long color)
{
	unsigned short *vidmem=framebuff;
	int charmask, i, cx, cy = y;
	unsigned short *l, *le, dmask=0x0333;

	for(l=vidmem+(x-1)+(cy+1)*208, le=vidmem+(x+7)+(cy+1)*208; l < le; l++)
		*l = (*l >> 2) & dmask;

	for(const char *p=text; *p; p++) {
		cx = x;
		charmask = *(mask_numbers + (*p - 0x2F));

		for(l = vidmem+(x-1)+(cy)*208, le = vidmem+(x-1)+(cy-5)*208; l > le; l-=208+7) {
			*l = (*l >> 2) & dmask; l++; *l = (*l >> 2) & dmask; l++;
			*l = (*l >> 2) & dmask; l++; *l = (*l >> 2) & dmask; l++;
			*l = (*l >> 2) & dmask; l++; *l = (*l >> 2) & dmask; l++;
			*l = (*l >> 2) & dmask; l++; *l = (*l >> 2) & dmask;
		}

		for(i=0; i < 24; i++) {
			if(charmask&0x80000000) *( vidmem + (cy-(i&3))*208 + (cx+(i>>2)) ) = color;
			charmask <<= 1;
		}
		cy -= 5;
	}
}

void drawTextFpsM2(const char *text)
{
	if(!text) return;
	drawTextM2((Pico.video.reg[12]&1) ? 256 : 224, 200, text, color_redM2);
}

void drawTextFps0(const char *text)
{
	if(!text) return;
	drawText0(176, 216, text, color_red);
}

void drawTextFpsFit0(const char *text)
{
	if(!text) return;
	drawText0(176, txtheight_fit, text, color_red);
}

void drawTextFps90(const char *text)
{
	if(!text) return;
	drawText90(10, 256, text, color_red);
}

void drawTextFps180(const char *text)
{
	if(!text) return;
	drawText180(32, 8, text, color_red);
}

void drawTextFps270(const char *text)
{
	if(!text) return;
	drawText270(200, 64, text, color_red);
}

void drawTextNoticeM2(const char *text)
{
	if(!text) return;
	drawTextM2(20, 200, text, color_redM2);
}

void drawTextNotice0(const char *text)
{
	if(!text) return;
	drawText0(2, 216, text, color_red);
}

void drawTextNoticeFit0(const char *text)
{
	if(!text) return;
	drawText0(2, txtheight_fit, text, color_red);
}

void drawTextNotice90(const char *text)
{
	if(!text) return;
	drawText90(10, 34, text, color_red);
}

void drawTextNotice180(const char *text)
{
	if(!text) return;
	drawText180(206, 8, text, color_red);
}

void drawTextNotice270(const char *text)
{
	if(!text) return;
	drawText270(200, 286, text, color_red);
}


////////////////////////////////
// misc drawers

// draws rect with width - 1 and height - 1
void drawRect(const TRect &rc, unsigned short color)
{
	if(rc.iTl.iX - rc.iBr.iX && rc.iTl.iY - rc.iBr.iY) {
		int stepX = rc.iTl.iX < rc.iBr.iX ? 1 : -1;
		int stepY = rc.iTl.iY < rc.iBr.iY ? 1 : -1;
		
		for(int x = rc.iTl.iX;; x += stepX) {
			*(framebuff + rc.iTl.iY*208 + x) = *(framebuff + (rc.iBr.iY - stepY)*208 + x) = color;
			if(x == rc.iBr.iX - stepX) break;
		}
		
		for(int y = rc.iTl.iY;; y += stepY) {
			*(framebuff + y*208 + rc.iTl.iX) = *(framebuff + y*208 + rc.iBr.iX - stepX) = color;
			if(y == rc.iBr.iY - stepY) break;
		}
	}
}

// draws fullsize filled rect
void drawRectFilled(const TRect rc, unsigned short color)
{
	if(rc.iTl.iX - rc.iBr.iX && rc.iTl.iY - rc.iBr.iY) {
		int stepX = rc.iTl.iX < rc.iBr.iX ? 1 : -1;
		int stepY = rc.iTl.iY < rc.iBr.iY ? 1 : -1;
		
		for(int y = rc.iTl.iY;; y += stepY) {
			for(int x = rc.iTl.iX;; x += stepX) {
				*(framebuff + y*208 + x) = *(framebuff + y*208 + x) = color;
				if(x == rc.iBr.iX) break;
			}
			if(y == rc.iBr.iY) break;
		}
	}
}

// direction: -1 left, 1 right
void drawArrow0(TPoint p, int direction, unsigned short color)
{
	int width = 11;
	int x = p.iX;
	int y = p.iY;

	for(; width > 0; x+=direction, y++, width -=2)
		for(int i=0; i < width; i++)
			*(framebuff + x + y*208 + i*208) = color;
}

void drawArrow90(TPoint p, int direction, unsigned short color)
{
	int width = 11;
	int x = p.iX - width;
	int y = p.iY;

	for(; width > 0; x++, y+=direction, width -=2)
		for(int i=0; i < width; i++)
			*(framebuff + x + y*208 + i) = color;
}


// copies temporary framebuff to real device framebuffer
void vidBlitRGB444(int full)
{
	unsigned short *ps;
	unsigned short *pd;
	int pixels;
	if(full) {
		ps = framebuff;
		pd = (unsigned short *) screenbuff;
		pixels = 208*320;
	} else {
		ps = framebuff + framebuff_offs;
		pd = (unsigned short *) screenbuff + framebuff_offs;
		pixels = framebuff_len;
	}

	vidConvCpyRGB444(pd, ps, pixels);
	//for(unsigned short *ps_end = ps + pixels; ps < ps_end; ps++)
		// Convert 0000bbb0 ggg0rrr0
		// to      0000rrr0 ggg0bbb0
	//	*pd++ = ((*ps&0x000F)<<8) | (*ps&0x00F0) | ((*ps&0x0F00)>>8);
}

void vidBlitRGB565(int full)
{
	unsigned short *ps;
	unsigned short *pd;
	int pixels;
	if(full) {
		ps = framebuff;
		pd = (unsigned short *) screenbuff;
		pixels = 208*320;
	} else {
		ps = framebuff  + framebuff_offs;
		pd = (unsigned short *) screenbuff + framebuff_offs;
		pixels = framebuff_len;
	}

	vidConvCpyRGB565(pd, ps, pixels);
	//for(; ps < ps_end; ps++)
		// Convert 0000bbb0 ggg0rrr0
		// to      rrr00ggg 000bbb00
	//	*pd++ = ((*ps&0x000F)<<12) | ((*ps&0x00F0)<<3) | ((*ps&0x0F00)>>7);
}

void vidBlitRGB32(int full)
{
	unsigned short *ps;
	unsigned long  *pd;
	int pixels;
	if(full) {
		ps = framebuff;
		pd = (unsigned long *) screenbuff;
		pixels = 208*320;
	} else {
		ps = framebuff  + framebuff_offs;
		pd = (unsigned long *) screenbuff + framebuff_offs;
		pixels = framebuff_len;
	}

	vidConvCpyRGB32(pd, ps, pixels);
	//for(; ps < ps_end; ps++)
		// Convert          0000bbb0 ggg0rrr0
		// to  ..0 rrr00000 ggg00000 bbb00000
	//	*pd++ = ((*ps&0x000F)<<20) | ((*ps&0x00F0)<<8) | ((*ps&0x0F00)>>4);
}

// -------- rendermode 2 ---------

void vidBlit16M2(int full)
{
	unsigned short *ps = framebuff+328*8;
	unsigned short *pd = (unsigned short *) screenbuff;

	if(currentConfig.iScreenRotation == TPicoConfig::PRot90) {
		if(Pico.video.reg[12]&1)
			vidConvCpyM2_16_90(pd, ps, 320/8);
		else {
			if(full) memset(pd, 0, 208*32*2);
			pd += 208*32;
			vidConvCpyM2_16_90(pd, ps, 256/8);
			if(full) memset(pd + 208*256, 0, 208*32*2);
		}
	} else if(currentConfig.iScreenRotation == TPicoConfig::PRot270) {
		if(Pico.video.reg[12]&1)
			vidConvCpyM2_16_270(pd, ps, 320/8);
		else {
			if(full) memset(pd, 0, 208*32*2);
			pd += 208*32;
			ps -= 64;     // the blitter starts copying from the right border, so we need to adjust
			vidConvCpyM2_16_270(pd, ps, 256/8);
			if(full) memset(pd + 208*256, 0, 208*32*2);
		}
	}
/*
    for(int x=0; x < 320; x++)
		for(int y=207; y>=0; y--) {
			*pd++ = *(ps+8+x+y*328);
		}
*/
}

void vidBlitRGB32M2(int full)
{
	unsigned short *ps = framebuff+328*8;
	unsigned long  *pd = (unsigned long *) screenbuff;

	if(currentConfig.iScreenRotation == TPicoConfig::PRot90) {
		if(Pico.video.reg[12]&1)
			vidConvCpyM2_RGB32_90(pd, ps, 320/8);
		else {
			if(full) memset(pd, 0, 208*32*4);
			pd += 208*32;
			vidConvCpyM2_RGB32_90(pd, ps, 256/8);
			if(full) memset(pd + 208*256, 0, 208*32*4);
		}
	} else if(currentConfig.iScreenRotation == TPicoConfig::PRot270) {
		if(Pico.video.reg[12]&1)
			vidConvCpyM2_RGB32_270(pd, ps, 320/8);
		else {
			if(full) memset(pd, 0, 208*32*4);
			pd += 208*32;
			ps -= 64;     // the blitter starts copying from the right border, so we need to adjust
			vidConvCpyM2_RGB32_270(pd, ps, 256/8);
			if(full) memset(pd + 208*256, 0, 208*32*4);
		}
	}
}

void PrepareCramRGB444M2()
{
	vidConvCpyRGB444(cram_high, Pico.cram, 0x40);
}

void PrepareCramRGB565M2()
{
	vidConvCpyRGB565(cram_high, Pico.cram, 0x40);
}


////////////////////////////////
// main functions

int vidInit(int displayMode, void *vidmem, int p800, int reinit)
{
	if(!reinit) {
		// prepare framebuffer
		screenbuff = (unsigned short *) vidmem;
		framebuff =  (unsigned short *) malloc(framebuffsize);

		if(!screenbuff) return KErrNotSupported;
		if(!framebuff)  return KErrNoMemory;

		// Cram function: go and hack Pico so it never gets called
		PicoCram = EmuCramNull;
	}

	// select suitable blitter
	switch(displayMode) {
		case EColor4K:  vidBlit = vidBlitKeyCfg = vidBlitRGB444; break;
		case EColor64K: vidBlit = vidBlitKeyCfg = vidBlitRGB565; break;
		case EColor16M: vidBlit = vidBlitKeyCfg = vidBlitRGB32;  break;
		default: return KErrNotSupported;
	}

	memset(framebuff, 0, framebuffsize);

	// rendermode 2?
	if(PicoOpt&0x10) {
		switch(displayMode) {
			case EColor4K:
				vidBlit = vidBlit16M2;
				PicoPrepareCram = PrepareCramRGB444M2;
				PicoCramHigh = cram_high;
				color_redM2 = 0x0F22;
				dt_dmask = 0x0333;
				break;
			case EColor64K:
				vidBlit = vidBlit16M2;
				PicoPrepareCram = PrepareCramRGB565M2;
				PicoCramHigh = cram_high;
				color_redM2 = 0xF882;
				dt_dmask = 0x39e7;
				break;
			case EColor16M:
				vidBlit = vidBlitRGB32M2;
				break;
		}
		drawTextFps = drawTextFpsM2;
		drawTextNotice = drawTextNoticeM2;
		vidBlit(1);
		return 0;
	}

	framebuff_offs = 0;
	framebuff_len  = 208*320;
	vidBlit(1);

	// setup all orientation related stuff
	if(currentConfig.iScreenRotation == TPicoConfig::PRot0) {
		if(currentConfig.iScreenMode == TPicoConfig::PMCenter) {
			PicoScan = EmuScanCenter0;
			framebuff_len = 208*224;
			drawTextFps = drawTextFps0;
			drawTextNotice = drawTextNotice0;
		} else {
			if(currentConfig.iScreenMode == TPicoConfig::PMFit2) {
				if(p800) {
					fc_inc = 6518; // 0.651786 (144+2)
					txtheight_fit = 139;
					framebuff_len = 208*146;
				} else {
					fc_inc = 9286; // 0.92857
					txtheight_fit = 201;
					framebuff_len = 208*208;
				}
			}
			PicoScan = EmuScanFit0;
			drawTextFps = drawTextFpsFit0;
			drawTextNotice = drawTextNoticeFit0;
		}
	} else if(currentConfig.iScreenRotation == TPicoConfig::PRot90) {
		if(currentConfig.iScreenMode == TPicoConfig::PMFit)
			 PicoScan = EmuScanFit90;
		else PicoScan = EmuScanCenter90;
		drawTextFps = drawTextFps90;
		drawTextNotice = drawTextNotice90;
	} else if(currentConfig.iScreenRotation == TPicoConfig::PRot180) {
		if(currentConfig.iScreenMode == TPicoConfig::PMCenter) {
			PicoScan = EmuScanCenter180;
			framebuff_len = 208*224;
		} else {
			if(currentConfig.iScreenMode == TPicoConfig::PMFit2) {
				if(p800) {
					fc_inc = 6518; // 0.651786
					fc_lines = 146;
					framebuff_len = 208*146;
				} else {
					fc_inc = 9286; // 0.92857
					fc_lines = 208;
					framebuff_len = 208*208;
				}
			}
			PicoScan = EmuScanFit180;
		}
		drawTextFps = drawTextFps180;
		drawTextNotice = drawTextNotice180;
	} else if(currentConfig.iScreenRotation == TPicoConfig::PRot270) {
		if(currentConfig.iScreenMode == TPicoConfig::PMFit)
			 PicoScan = EmuScanFit270;
		else PicoScan = EmuScanCenter270;
		drawTextFps = drawTextFps270;
		drawTextNotice = drawTextNotice270;
	}

	return 0;
}

void vidFree()
{
	free(framebuff);
	framebuff = 0;
}

void vidDrawFrame(char *noticeStr, char *fpsStr, int num)
{
	PicoFrame();
	if(currentConfig.iFlags & 2)
		drawTextFps(fpsStr);
	drawTextNotice(noticeStr);

	vidBlit(!num); // copy full frame once a second
}

void vidKeyConfigFrame(const TUint whichAction, TInt flipClosed)
{
	int i;
	char buttonNames[128];
	buttonNames[0] = 0;
	memset(framebuff, 0, framebuffsize);

	unsigned long currentActCode = 1 << whichAction;

	if(flipClosed) {
		drawRectFilled(TRect(56, 2, 152, 16), color_grey); // 96x14
		drawArrow0(TPoint(64, 3), -1, color_green);
		drawArrow0(TPoint(144, 3), 1, color_green);
		drawText0(64, 20, "USE@JOG@TO@SELECT", color_red);

		drawText0(68, 6, actionNames[whichAction], color_red);
	} else {
		// draw all "buttons" in reverse order
		const TPicoAreaConfigEntry *e = areaConfig + 1; i = 0;
		while(e->rect != TRect(0,0,0,0)) { e++; i++; }
		for(e--, i--; e->rect != TRect(0,0,0,0); e--, i--)
			drawRect(e->rect, (currentConfig.iAreaBinds[i] & currentActCode) ? color_red : color_red_dim);
	
		// draw config controls
		drawRectFilled(TRect(190, 112, 204, 208), color_grey);
		drawArrow90(TPoint(203, 120), -1, color_green);
		drawArrow90(TPoint(203, 200),  1, color_green);

		drawText90(200, 124, actionNames[whichAction], color_red);
	}

	// draw active button names if there are any
	i = 0;
	for(TPicoKeyConfigEntry *e = keyConfig; e->name; e++, i++)
		if(currentConfig.iKeyBinds[i] & currentActCode) {
			if(buttonNames[0]) strcat(buttonNames, ";@");
			strcat(buttonNames, e->name);
		}
	if(buttonNames[0]) {
		if(flipClosed) {
			buttonNames[41] = 0; // only 61 chars fit
			drawText0(2, 138, buttonNames, color_blue);
		} else {
			buttonNames[61] = 0;
			drawText90(12, 10, buttonNames, color_blue);
		}
	}

	vidBlitKeyCfg(1);
}

void vidDrawFCconfigDone()
{
	drawText0(64, 20, "USE@JOG@TO@SELECT", 0); // blank prev text
	drawText0(54, 30, "OPEN@FLIP@TO@CONTINUE", color_red);
	vidBlitKeyCfg(1);
}

void vidDrawNotice(const char *txt)
{
	if(framebuff) {
		drawTextNotice(txt);
		vidBlit(1);
	}
}
