#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "tboxlibs/flex.h"

#include "Global/VduExt.h"
#include "bbc.h"
#include "kernel.h"
#include "swis.h"
#include "doublesize.h"
#include "pinsprite.h"

extern int *tb_sprite_area;

int *find_sprite(char *spritename,int **areabase)
{
  int *rom,*ram;
  int *address;

  if (_swix(Wimp_BaseOfSprites,_OUTR(0,1),&rom,&ram)) return NULL;

  if (_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,ram,spritename,&address)==NULL) {
    if (areabase) *areabase=ram;
    return address;
  }
  if (_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,rom,spritename,&address)==NULL) {
    if (areabase) *areabase=rom;
    return address;
  }

  return NULL;
}

static int xdpi(int mode)
{
  return 180/(1<<bbc_modevar(mode,4));
}

static int ydpi(int mode)
{
  return 180/(1<<bbc_modevar(mode,5));
}

/* transfer mask from source to dest, converting format */

static void ps_simplemask(int *source,int *dest,_mask mask_type,int w,int h)
{
 // binary mask, may be displaced from left
 // mask bpp == sprite bpp

  int x,y;
  int source_maskstride;
  int dest_maskstride=(w+3)&~3;

  int lbit=source[6];
  int rbit;
  int source_bpp=1<<bbc_modevar(source[10],9);
  int mask_piece=(1<<source_bpp)-1;
  int maskbpp;
  int dest_x;

  unsigned char *sourcemask=((unsigned char *)source)+source[9];
  unsigned char *destmask=((unsigned char *)dest)+dest[9];

  unsigned char data;

  if (source[10]>=0 && source[10]<=256) {
    source_maskstride=source[4]*4+4;
    rbit=lbit+8*w/(8/source_bpp);
    maskbpp=source_bpp;
  } else {
    maskbpp= (mask_type==mask_simple_new) ? 1 : 8;
    rbit=w*maskbpp;
    source_maskstride=((rbit+31)/32)<<2;
  }

  for (y=0;y<h;y++) {
    for (x=lbit, dest_x=0;x<rbit;x+=maskbpp, dest_x++) {
      // get source mask byte
      data=*(sourcemask+x/8);
      data=(data>>(x &7)) & mask_piece;

      if (mask_type==mask_alpha) {
        *(destmask+dest_x)=data;
      } else {
        *(destmask+dest_x)=data ? 255:0;
      }
    }

    destmask+=dest_maskstride;
    sourcemask+=source_maskstride;
  }
}

static void ps_simpleoverlaymask(int *source,int *dest,_mask mask_type,int w,int h,int offsetw,int maxh)
{
 // binary mask, may be displaced from left
 // mask bpp == sprite bpp
 // merge the pin mask in, so that pin always visible

  int source_maskstride;
  int dest_maskstride=(w+3)&~3;

  int lbit=source[6];
  int rbit;
  int source_bpp=1<<bbc_modevar(source[10],9);
  int mask_piece=(1<<source_bpp)-1;
  int maskbpp;
  int x,y,dest_x;

  unsigned char *sourcemask=((unsigned char *)source)+source[9];
  unsigned char *destmask=((unsigned char *)dest)+dest[9];

  unsigned char data;

  if (source[10]>=0 && source[10]<=256) {
    source_maskstride=source[4]*4+4;
    rbit=lbit+8*w/(8/source_bpp);
    maskbpp=source_bpp;
  } else {
    maskbpp= (mask_type==mask_simple_new) ? 1 : 8;
    rbit=w*maskbpp;
    source_maskstride=((rbit+31)/32)<<2;
  }

  for (y=0;y<h && y<maxh;y++) {
    for (x=lbit, dest_x=offsetw; x<rbit && dest_x<w; x+=maskbpp, dest_x++) {
      // get source mask byte
      data=*(sourcemask+x/8);
      data=(data>>(x &7)) & mask_piece;
      if (data==255) *(destmask+dest_x)=255;
    }

    destmask+=dest_maskstride;
    sourcemask+=source_maskstride;
  }
}

static void ps_channeloverlaymask(int *source,int *dest,int w,int h,int offsetw,int maxh)
{
  // mask is embedded in the RGBA sequence in the sprite data itself
  // need to do TRGB as 4:4:4:4, 8:8:8:8, 1:5:5:5

  unsigned char *destmask=((unsigned char *)dest)+dest[9];
  unsigned char *sourcemask=((unsigned char *)source)+source[8];

  int source_maskstride=source[4]*4+4;
  int dest_maskstride=(w+3)&~3;
  int x,y,dest_x;
  int maskbyte_offset;
  int maskbyte_mask;
  int maskbyte_bits;
  int pixel_width;
  int sprite_type;

  unsigned char data;

  // determine flavour from the source mode word
  if ((source[10] & 0x780f000f) == 0x78000001) {
    sprite_type=(source[10]>>20) & 127;
  } else {
    sprite_type=(source[10]>>27) & 15;
  }

  switch (sprite_type) {
    case 5: // 1:5:5:5
      maskbyte_bits=1;
      pixel_width=2;
      maskbyte_offset=1;
      maskbyte_mask=128;
      break;

    case 6: // 8:8:8:8
      maskbyte_bits=8;
      pixel_width=4;
      maskbyte_offset=3;
      maskbyte_mask=0xff;
      break;

    case 16: // 4:4:4:4
      maskbyte_bits=4;
      pixel_width=2;
      maskbyte_offset=1;
      maskbyte_mask=0xf0;
      break;

    default: return; // not supported
  }

  for (y=0;y<h && y<maxh;y++) {
    for (x=0, dest_x=offsetw;x<w && dest_x<w;x++,dest_x++) {
      data=*(sourcemask+x*pixel_width+maskbyte_offset) & maskbyte_mask;
      // extend to 8 bits
      switch (maskbyte_bits) {
        case 1: data=data?255:0;
                break;
        case 4: data|=(data>>4);
                break;
        case 8:
                break;
      }
      if (data==255) *(destmask+x)=data;
    }
    destmask+=dest_maskstride;
    sourcemask+=source_maskstride;
  }
}

static void ps_channelmask(int *source,int *dest,int w,int h)
{
  // mask is embedded in the RGBA sequence in the sprite data itself
  // need to do TRGB as 4:4:4:4, 8:8:8:8, 1:5:5:5

  unsigned char *destmask=((unsigned char *)dest)+dest[9];
  unsigned char *sourcemask=((unsigned char *)source)+source[8];

  int source_maskstride=source[4]*4+4;
  int dest_maskstride=(w+3)&~3;
  int x,y;
  int maskbyte_offset;
  int maskbyte_mask;
  int maskbyte_bits;
  int pixel_width;
  int sprite_type;

  unsigned char data;

  // determine flavour from the source mode word
  if ((source[10] & 0x780f000f) == 0x78000001) {
    sprite_type=(source[10]>>20) & 127;
  } else {
    sprite_type=(source[10]>>27) & 15;
  }

  switch (sprite_type) {
    case 5: // 1:5:5:5
      maskbyte_bits=1;
      pixel_width=2;
      maskbyte_offset=1;
      maskbyte_mask=128;
      break;

    case 6: // 8:8:8:8
      maskbyte_bits=8;
      pixel_width=4;
      maskbyte_offset=3;
      maskbyte_mask=0xff;
      break;

    case 16: // 4:4:4:4
      maskbyte_bits=4;
      pixel_width=2;
      maskbyte_offset=1;
      maskbyte_mask=0xf0;
      break;

    default: return; // not supported
  }

  for (y=0;y<h;y++) {
    for (x=0;x<w;x++) {
      data=*(sourcemask+x*pixel_width+maskbyte_offset) & maskbyte_mask;
      // extend to 8 bits
      switch (maskbyte_bits) {
        case 1: data=data?255:0;
                break;
        case 4: data|=(data>>4);
                break;
        case 8:
                break;
      }
      *(destmask+x)=data;
    }
    destmask+=dest_maskstride;
    sourcemask+=source_maskstride;
  }
}

static char *pin_link_for_res(int mode)
{
  int xeig=bbc_modevar(mode,4),yeig=bbc_modevar(mode,5);

  if (xeig==0 || yeig==0) {
    return "link11";
  }
  if (xeig==1 && yeig==2) {
    return "link";
  }
  return "link22";
}

void ps_removepin(char *spritename)
{
  if (*spritename==255) {
    *spritename='!';
    return;
  }

  if (strncmp(spritename,"file\xff",5)==0) {
    *(spritename+4)='_';
    return;
  }

  if (strcmp(spritename,"directory\xff")==0) {
    *(spritename+9)=0;
    return;
  }

  if (strcmp(spritename,"applicatio\xff")==0) {
    *(spritename+10)='n';
    return;
  }

  return;
}

/* add a pin to a sprite if possible.  If we fail, then don't complain, just use the old sprite anyway */

int ps_addpin(char *sprite,char *newname)
{
  char *pinname;
  int *spritearea,*areabase;
  int width,height,mask,mode;
  int *pinspritebase;
  int pinwidth,pinheight,pinmask,pinmode;
  _mask mask_type;
  int pin_x,pin_y;
  int length;
  _kernel_oserror *e;
  int savearea_size,*savearea;
  _kernel_swi_regs r;
  int *coltrans,coltrans_size;
  int *spritebase=NULL;
  int *palette=NULL;

  // see if we already have one of these
  if (strncmp(sprite,"file_",5)==0) {
    sprintf(newname,"file\xff%s",sprite+5);
  } else {
    if (strcmp(sprite,"directory")==0) {
      strcpy(newname,"directory\xff");
    } else {
      if (strcmp(sprite,"application")==0) {
        strcpy(newname,"applicatio\xff");
      } else {
        // pick app name
        sprintf(newname,"%c%s",0xff,sprite+1);
      }
    }
  }

  // test for existence
  if (find_sprite(newname,NULL)) {
//    return 1; // already got one
  }

  spritebase=find_sprite(sprite,&areabase);
  if (!spritebase) {
    // can't find the original
    return 0;
  }

  pinname=pin_link_for_res(mode);
  _swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,tb_sprite_area,pinname,&pinspritebase);
  _swix(OS_SpriteOp,_INR(0,2)|_OUTR(3,6),40+256,tb_sprite_area,pinname,&pinwidth,&pinheight,&pinmask,&pinmode);

  // create a 16M colour version - we want our red pin and some paletted sprites don't have red
  // how big?
  // work out x, y size
  _swix(OS_SpriteOp,_INR(0,2)|_OUTR(3,6),40+512,areabase,spritebase,&width,&height,&mask,&mode);

  if (width<pinwidth || height<pinheight) return 0; // pin is larger, so skip

  mask_type=masktype(spritebase,mask);

  // work out sprite size...
  length=(4*width)*height;
  length+=44; // add header
  length+=((width+3)&~3)*height; // mask

  if (!flex_alloc((flex_ptr)&spritearea,16+length)) return 0;
  spritearea[0]=length+16;
  spritearea[1]=0;
  spritearea[2]=16;
  spritearea[3]=16;

  e=_swix(OS_SpriteOp,_INR(0,6),15+256,spritearea,sprite,0,width,height,(mask_type!=mask_none ? (1<<31):0) | (6<<27) | 1 | (xdpi(mode)<<1) | (ydpi(mode)<<14));
  if (e) {
//     printf("bad create %s\n",e->errmess);
    flex_free((flex_ptr)&spritearea);
    return 0;
  }

//  spritearea[4+9]=spritearea[4+8];

  if (mask_type!=mask_none) {
    e=_swix(OS_SpriteOp,_INR(0,2),29+512,spritearea,spritearea+4);

    if (e) {
//      printf("bad mask %s\n",e->errmess);
      flex_free((flex_ptr)&spritearea);
      return 0;
    }
  }

  // transfer sprite into the new location
  _swix(OS_SpriteOp,_INR(0,2)|_OUT(3),62+512,spritearea,spritearea+4,&savearea_size);
  if (!flex_alloc((flex_ptr)&savearea,savearea_size)) {
    flex_free((flex_ptr)&spritearea);
    return 0;
  }
  savearea[0]=0;
  r.r[0]=60+512;
  r.r[1]=(int)spritearea;
  r.r[2]=(int)(spritearea+4);
  r.r[3]=(int)savearea;
  e=_kernel_swi(OS_SpriteOp,&r,&r);
  if (e) {
//    printf("can't switch %s\n",e->errmess);
    flex_free((flex_ptr)&spritearea);
    return 0;
  }

  // does the sprite have its own palette?
  if (spritebase[8]==44 || spritebase[9]==44) {
    // needs one
    // how many words?
    int palettebpp;

    if (!_swix(OS_ReadModeVariable,_INR(0,1)|_OUT(2),spritebase[10],9,&palettebpp)) {
      palettebpp=1<<palettebpp;
      if (palettebpp<8) {
        if (flex_alloc((flex_ptr)&palette,4*(1<<palettebpp))) {
          // do it...
          int wimp_palette[20];
          _swix(Wimp_ReadPalette,_INR(1,2),wimp_palette,0x45555254); // 'TRUE'

          switch (palettebpp) {
            case 1:
              *palette=wimp_palette[0] &0xffffff00;
              *(palette+1)=wimp_palette[1] &0xffffff00;
              break;

            case 2:
              *palette=wimp_palette[0] &0xffffff00;
              *(palette+1)=wimp_palette[2] &0xffffff00;
              *(palette+2)=wimp_palette[4] &0xffffff00;
              *(palette+3)=wimp_palette[7] &0xffffff00;
              break;

            case 4:
              {
                int palptr;
                for (palptr=0;palptr<16;palptr++) *(palette+palptr)=wimp_palette[palptr] &0xffffff00;
              }
              break;
          }
        }
      }
    }
  }

  if (palette) {
    e=_swix(ColourTrans_SelectTable,_INR(0,7)|_OUT(4),spritebase[10],palette,-1,-1,0,0,0,0,&coltrans_size);
  } else {
    e=_swix(ColourTrans_SelectTable,_INR(0,7)|_OUT(4),areabase,spritebase,-1,-1,0,1,0,0,&coltrans_size);
  }

  if (coltrans_size) {
    if (!flex_alloc((flex_ptr)&coltrans,coltrans_size)) {
      flex_free((flex_ptr)&savearea);
      flex_free((flex_ptr)&spritearea);
      return 0;
    }
    if (palette) {
      e=_swix(ColourTrans_SelectTable,_INR(0,7),spritebase[10],palette,-1,-1,coltrans,0,0,0);
    } else {
      e=_swix(ColourTrans_SelectTable,_INR(0,7),areabase,spritebase,-1,-1,coltrans,1,0,0);
    }
  } else {
    coltrans=NULL;
  }

  if (palette) {
    flex_free((flex_ptr)&palette);
    palette=NULL;
  }

  e=_swix(OS_SpriteOp,_INR(0,7),52+512,areabase,spritebase,0,0,0,0,coltrans);
  if (coltrans) flex_free((flex_ptr)&coltrans);


  // add pin to top-right corner
  // choose the best fit for the destination resolution

  pin_x=(width<<bbc_modevar(mode,4)) - (pinwidth<<bbc_modevar(pinmode,4));
  pin_y=(height<<bbc_modevar(mode,5)) - (pinheight<<bbc_modevar(pinmode,5));

  e=_swix(ColourTrans_SelectTable,_INR(0,7)|_OUT(4),tb_sprite_area,pinname,-1,-1,0,0,0,0,&coltrans_size);

  if (coltrans_size) {
    if (!flex_alloc((flex_ptr)&coltrans,coltrans_size)) {
      flex_free((flex_ptr)&savearea);
      flex_free((flex_ptr)&spritearea);
      return 0;
    }

    e=_swix(ColourTrans_SelectTable,_INR(0,7),tb_sprite_area,pinname,-1,-1,coltrans,0,0,0);
  } else {
    coltrans=NULL;
  }

  _swix(OS_SpriteOp,_INR(0,7),52+256,tb_sprite_area,pinname,pin_x,pin_y,8,0,coltrans);

  if (coltrans) flex_free((flex_ptr)&coltrans);

  _kernel_swi(OS_SpriteOp,&r,&r);

  switch (mask_type) {
    case mask_none:
      break;

    case mask_simple_old:
    case mask_simple_new:
    case mask_alpha:
      ps_simplemask(spritebase,spritearea+4,mask_type,width,height);
      break;

    case mask_channel:
      ps_channelmask(spritebase,spritearea+4,width,height);
      break;
  }

  // sort out the pin's mask, but only bother if there's a mask in the recipient sprite
  if (mask_type!=mask_none) {
    // get pin sprite info
    switch (masktype(pinspritebase,pinmask)) {
    case mask_none:
      break;

    case mask_channel:
      ps_channeloverlaymask(pinspritebase,spritearea+4,width,height,width-pinwidth,pinheight);
      break;

    default:
      ps_simpleoverlaymask(pinspritebase,spritearea+4,masktype(pinspritebase,pinmask),width,height,width-pinwidth,pinheight);
      break;
    }
  }

  // generate a new name
  _swix(OS_SpriteOp,_INR(0,3),26+512,spritearea,spritearea+4,newname);
  strcpy(sprite,newname);

  e=_swix(OS_SpriteOp,_INR(0,2),12+256,spritearea,"ram:$.test");

  e=_swix(OS_SpriteOp,_INR(0,2),12+256,spritearea,"pipe:pinboard");
  if (e) {
//    printf("bad save %s\n",e->errmess);
  } else {
    _swix(OS_CLI,_IN(0),"%IconSprites pipe:pinboard");
  }

  flex_free((flex_ptr)&savearea);
  flex_free((flex_ptr)&spritearea);

//  printf("done\n");
  return (e==NULL);
}


/*
int main(int argc,char *argv[])
{
  int i;
  char *pinboardspr="SDFS::4te.$.Programming.Pinboard.Resources.UK.Sprites";
  int length;

  _swix(OS_File,_INR(0,1)|_OUT(4),5,pinboardspr,&length);
  tb_sprite_area=(int *)malloc(length+4);
  tb_sprite_area[0]=length;
  _swix(OS_File,_INR(0,3),255,pinboardspr,tb_sprite_area+1,0);

  for (i=1;i<argc;i++) {
    addpin(argv[i]);
  }

  return 0;
}
*/
