#include <vo_stdio.h>
#include <volink.h>     /* Linker directives like DLLENTRY */
#include <apploader.h>  /* RunLibraryFunction etc */
#include <kernel.h>
#include <string.h>
#include <stdlib.h>
#include <audio.h>
#include <consolestate.h>
#include <saturate.h>
#include <lcd.h>

#define RENDER_GFX_ID 0x1c9a
#define MAX_RENDER_BUF_SIZE 160
#define MAX_PALETTE_BITS 16
#define PALETTE_SIZE (1<<MAX_PALETTE_BITS)
#define GFX_FILE_NAME "S:RenderEx.v5g"

FILE *gfxFp = NULL;
u_int16 renderBitmapBuf[PALETTE_SIZE+MAX_RENDER_BUF_SIZE];

#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))


/*
  Rendering function.
 */
s_int16 RenderBitmap(register int index, register int x, register int y,
		     void (*modifyPalette)(register u_int16 *palette, register s_int16 paletteSize)) {
  u_int32 pos;
  int w, h, bits, pixels, i, bitsLeft = 0;
  u_int16 *rp = renderBitmapBuf, mask, bufUsed, bufPalette;
  u_int16 nRows, rowsLeft;
  u_int32 lWord = 0;
  u_int16 maxRGB3[3];
  fseek(gfxFp, 8+index*6, SEEK_SET);
  pos = (u_int32)fgetc(gfxFp) << 16;
  pos |= (u_int16)fgetc(gfxFp) << 8;
  pos |= fgetc(gfxFp);
  w = fgetc(gfxFp);
  h = nRows = fgetc(gfxFp);
  bits = fgetc(gfxFp);
  pixels = w*h;
  mask = (1<<bits)-1;
  if (bits > MAX_PALETTE_BITS) {
    return -1;
  }

  bufPalette = 1<<bits;

  /* Calculate into how many slices rendering the icon should be divided to */
  bufUsed = (bits*w*nRows+15)/16;
  while (bufPalette+bufUsed > MAX_RENDER_BUF_SIZE) {
    nRows = 1<<(QsortLog2(nRows-1)-1);
    bufUsed = bits*w*nRows;
    if (bufUsed & 15) {
      printf("E: align %d %d\n", bufUsed>>4, bufUsed&15);
      return -1;
    }
    bufUsed >>= 4;
  }

  fseek(gfxFp, pos, SEEK_SET);
  fread(rp, sizeof(*rp), bufPalette, gfxFp);

  if (modifyPalette) modifyPalette(rp, 1<<bits);

  rp += 1<<bits;

  /* Do the actual rendering, potentially in slices. */
  while (h) {
    nRows = MIN(h, nRows);
    bufUsed = (bits*w*nRows+15) >> 4;
    fread(rp, sizeof(*rp), bufUsed, gfxFp);
    LcdFilledRectangle(x|(bits<<12), y, x+w-1, y+nRows-1, renderBitmapBuf,
		       COLOR_WHITE);
    h -= nRows;
    y += nRows;
  }

  return 0;
}




/*

  Example custom palette modification code.

  This code handles the palette in one of two ways. Example below:

  SetPaletteModifier(0xFFFF80, pmmLimit);
  - Limits maximum palette value to 0xFF, 0FF, 0x80 if RGB24 space.
    - Value 0xFF 0xFF 0xFF becomes 0xFF 0xFF 0x80
    - Value 0x80 0x80 0x40 becomes 0x80 0x80 0x40

  SetPaletteModifier(0xFFFF80, pmmMultiply);
  - Multiplier palette with given 8-bit value triplet.
    - Value 0xFF 0xFF 0xFF becomes 0xFF 0xFF 0x80
    - Value 0x80 0x80 0x40 becomes 0x3f 0x3f 0x20

  You can modify the palette tuning functions if you wish for different
  functionality.

 */
u_int16 paletteModifier[3] = {0x1f, 0x3f, 0x1f};
enum PaletteModifierMode {
  pmmLimit,	/* Limit to maximum (0xffffff = no-op) */
  pmmMultiply   /* Multiply (0x808080 = max = no-op    */
};
enum PaletteModifierMode paletteModifierMode = pmmLimit;

void SetPaletteModifier(register s_int32 rgb24, register enum PaletteModifierMode) {
  int i;
  u_int16 *pmmp = paletteModifier+2;
  static const __mem_y s_int16 sh[3] = {3, 2, 3};
  const __mem_y s_int16 *shp = sh;
  for (i=0; i<3; i++) {
    *pmmp = (u_int16)rgb24 & 0xff;
    rgb24 >>= 8;
    if (paletteModifierMode == pmmLimit) {
      *pmmp >>= *shp++;
    }
    pmmp--;
  }
}

void ModifyPalette(register u_int16 *pal, register s_int16 n) {
  int i;
  for (i=0; i<n; i++) {
    u_int16 rgb[3];
    s_int16 t = *pal, j, res = 0;
    u_int16 *rgbP = rgb+2;
    static const __mem_y s_int16 sh[9] = {31, 5, 0, 63, 6, 5, 31, 0, 11};
    const __mem_y s_int16 *shp = sh;
    s_int16 *pmmp = paletteModifier+2;

    for (j=0; j<3; j++) {
      *rgbP = t&(*shp++);
      t >>= *shp++;
      if (paletteModifierMode == pmmLimit) {
	*rgbP = MIN(*rgbP, *pmmp);
      } else {
	*rgbP = *rgbP * *pmmp >> 8;
      }
      res |= *rgbP << *shp++;
      rgbP--;
      pmmp--;
    }

    *pal++ = res;
  }
}


ioresult main(char *parameters) {
  int nParam = RunProgram("ParamSpl", parameters);
  char *p = parameters;
  int res = S_ERROR;
  int i, bitmaps = 0;

  for (i=0; i<nParam; i++) {
    if (!strcmp(p, "-h")) {
      printf("Usage: RenderEx [-h]\n"
	     "-h\tShow this help\n"
	     );
      res = S_OK;
      goto finally;
    } else {
      printf("E: Unknown parameter \"%s\"\n", p);
      goto finally;
    }
    p += strlen(p)+1;
  }

  LcdFilledRectangle(0, 0, lcd0.width-1, lcd0.height-1, NULL, __RGB565RGB(0x50,0x50,0x50));

  SetPaletteModifier(0x8080FF, pmmLimit);

  gfxFp = fopen(GFX_FILE_NAME, "rb");
  if (!gfxFp) {
    printf("E: No " GFX_FILE_NAME "\n");
    goto finally;
  }
  {
    u_int16 id;
    fread(&id, sizeof(id), 1, gfxFp);
    if (id != RENDER_GFX_ID) {
      printf("E: " GFX_FILE_NAME " ID\n");
      goto finally;
    }
  }
  fread(&bitmaps, sizeof(bitmaps), 1, gfxFp);
  printf("%d gfx bitmaps\n", bitmaps);
  printf("\nPush buttons between 1 and 9 to test, Ctrl-C to exit\n");

  while (!(appFlags & APP_FLAG_QUIT)) {
    static int knobPos = -1;

    /* If there are bytes in the input buffer... */
    if (ioctl(stdin, IOCTL_TEST, NULL) > 0) {
      int ch = fgetc(stdin);
      switch (ch) {
      case '1':
      case '2':
      case '3':
      case '4':
      case '5':
	RenderBitmap(100-'0'+ch, 48, 0, NULL);
	break;
      case '6':
	SetPaletteModifier(0xFFFFFF, pmmLimit);
	break;
      case '7':
	SetPaletteModifier(0xFF8080, pmmLimit);
	break;
      case '8':
	SetPaletteModifier(0x80FF80, pmmLimit);
	break;
      case '9':
	SetPaletteModifier(0x8080FF, pmmLimit);
	break;
      }
    }

    if (++knobPos > 100) knobPos = 0;

    RenderBitmap(knobPos    , 0,  0, NULL);
    RenderBitmap(100-knobPos, 0, 32, ModifyPalette);

    Delay(20);
  }

  res = S_OK;
 finally:
  LcdFilledRectangle(0, 0, lcd0.width-1, lcd0.height-1, NULL, COLOR_BLACK);

  if (gfxFp) {
    fclose(gfxFp);
    gfxFp = NULL;
  }

  return res;
}
