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

#include "kernel.h"
#include "swis.h"
#include "tboxlibs/flex.h"
#include "bbc.h"
#include "Global/VduExt.h"

#include "debug.h"

#include "iconize.h"
#include "doublesize.h"

// double-size sprite

static const int scale[]={2,2,1,1};

typedef enum {
  mask_none,     // no mask
  mask_simple_old, // simple mask, but bpp=mode bpp
  mask_simple_new, // simple mask, but bpp is 1
  mask_alpha,    // 8bpp alpha mask
  mask_channel   // 8bpp alpha channel
} _mask;

static int *hires_pool=NULL;

static _mask masktype(int *sprite,int mask)
{
  if (mask) {
    if (sprite[10] & 0x80000000) return mask_alpha;
    if (sprite[10]>=0 && sprite[10]<=256) return mask_simple_old; else return mask_simple_new;
  } 

  int mf=bbc_modevar(sprite[10],bbc_ModeFlags);
  if ((mf!=-1) && (mf & ModeFlag_DataFormatSub_Alpha)) return mask_channel;

  return mask_none;
}

static void sds_oldsimplemask(int *source,int *dest,int w,int h)
{
 // 1bpp mask, may be displaced from left

  int x,y;
  int source_maskstride=source[4]*4+4;
  int dest_maskstride=dest[4]*4+4;
  unsigned int sourcebyte;
  int lbyte=source[6]/8;
  int rbyte=source[4]*4+(source[7]+7)/8;

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

  for (y=0;y<h;y++) {
    for (x=lbyte;x<rbyte;x++) {
      // get source mask byte
      sourcebyte=*(sourcemask+x);

      // double up the bits
      sourcebyte=(sourcebyte | (sourcebyte<<4)) &0xf0f;
      sourcebyte=(sourcebyte | (sourcebyte<<2)) &0x3333;
      sourcebyte=(sourcebyte | (sourcebyte<<1)) &0x5555;
      sourcebyte*=3;

      // output mask data
      *(destmask+x*2)=sourcebyte&0xff;
      *(destmask+x*2+1)=sourcebyte>>8;
      *(destmask+dest_maskstride+x*2)=sourcebyte&0xff;
      *(destmask+dest_maskstride+x*2+1)=sourcebyte>>8;
    }

    destmask+=dest_maskstride*2; // doing 2 rows at once
    sourcemask+=source_maskstride;
  }
}


static void sds_newsimplemask(int *source,int *dest,int w,int h)
{
  // 1bpp mask
  int x,y;
  int source_maskstride=(((w+7)/8)+3) &~3;
  int dest_maskstride=(((w*2+7)/8)+3) &~3;  // doubling up size
  unsigned int sourcebyte;

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

  for (y=0;y<h;y++) {
    for (x=0;x<w;x+=8) {
      // get source mask byte
      sourcebyte=*(sourcemask+x/8);

      // double up the bits
      sourcebyte=(sourcebyte | (sourcebyte<<4)) &0xf0f;
      sourcebyte=(sourcebyte | (sourcebyte<<2)) &0x3333;
      sourcebyte=(sourcebyte | (sourcebyte<<1)) &0x5555;
      sourcebyte*=3;

      // output mask data
      *(destmask+x/8*2)=sourcebyte&0xff;
      *(destmask+x/8*2+1)=sourcebyte>>8;
      *(destmask+dest_maskstride+x/8*2)=sourcebyte&0xff;
      *(destmask+dest_maskstride+x/8*2+1)=sourcebyte>>8;
    }

    destmask+=dest_maskstride*2; // doing 2 rows at once
    sourcemask+=source_maskstride;
  }
  
}

static void sds_channelmask(int *source,int *dest,int w,int h)
{
 // copy and extend mask
 // mask is embedded in the RGBA sequence in the sprite data itself

  int x,y;
  int source_maskstride=source[4]*4+4;
  int dest_maskstride=dest[4]*4+4;
  unsigned int sourcebyte;

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

  for (y=0;y<h;y++) {
    for (x=0;x<source_maskstride;x+=4) {
      // get source mask byte
      sourcebyte=*(sourcemask+x);

      // output mask data
      *(destmask+x*2)=sourcebyte;
      *(destmask+x*2+4)=sourcebyte;
      *(destmask+dest_maskstride+x*2)=sourcebyte;
      *(destmask+dest_maskstride+x*2+4)=sourcebyte;
    }

    destmask+=dest_maskstride*2; // doing 2 rows at once
    sourcemask+=source_maskstride;
  } 
}

static void sds_alphamask(int *source,int *dest,int w,int h)
{
  // copy mask and extend
  // 8bpp alpha mask stored in mask area

  int x,y;
  int source_maskstride=source[4]*4+4;
  int dest_maskstride=dest[4]*4+4;
  unsigned int sourcebyte;

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

  for (y=0;y<h;y++) {
    for (x=0;x<source_maskstride;x++) {
      // get source mask byte
      sourcebyte=*(sourcemask+x);

      // output mask data
      *(destmask+x*2)=sourcebyte;
      *(destmask+x*2+1)=sourcebyte;
      *(destmask+dest_maskstride+x*2)=sourcebyte;
      *(destmask+dest_maskstride+x*2+1)=sourcebyte;
    }

    destmask+=dest_maskstride*2; // doing 2 rows at once
    sourcemask+=source_maskstride;
  }
  
}

int sprite_doublesize(_backdrop_state *state,char *sprite,int **area,int *free_area)
{
  int areasize,loarea,hiarea,wimparea,*sourcesprite;
  int w,h,mask,*savearea,savesize;
  _kernel_swi_regs r;

  *free_area=0;

  // do we already know of a doublesize version?
  // if current mode has eig 1/1 then we can check the hires pool :)
  if (state->pixel_x==2 && state->pixel_y==2) {
    // any luck?
    if (_swix(OS_SpriteOp,_INR(0,2),24+256,hires_pool,sprite)==NULL) {
      // yay!
      *area=hires_pool;
      return 1;
    }
  }

  // get sprite information
  _swix(Wimp_ReadSysInfo,_IN(0)|_OUTR(0,1),16,&loarea,&hiarea);

  if (_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,hiarea,sprite,&sourcesprite)) {
    if (_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,loarea,sprite,&sourcesprite)) {
      return 0;
    }
    wimparea=loarea;
  } else {
    wimparea=hiarea;
  }

  // get size of original sprite
  if (_swix(OS_SpriteOp,_INR(0,2)|_OUTR(3,5),512+40,wimparea,sourcesprite,&w,&h,&mask)) return 0;

  // estimate size of new sprite = 4*size of old sprite
  areasize=16+44+sourcesprite[0]*4;

  // make area
  if (!flex_alloc((flex_ptr)area,areasize)) return 0;
  *free_area=1;

  (*area)[0]=areasize;
  (*area)[1]=1;
  (*area)[2]=16;
  (*area)[3]=16+sourcesprite[0];

  // create sprite in the area
  // copy original (but not the image data) then extend it
  memcpy((*area)+4,sourcesprite,sourcesprite[8]<sourcesprite[9]?sourcesprite[8]:sourcesprite[9]);

  _swix(OS_SpriteOp,_INR(0,4),58+512,*area,(*area)+4,w,w);
  _swix(OS_SpriteOp,_INR(0,4),57+512,*area,(*area)+4,0,h);

  _swix(OS_SpriteOp,_INR(0,2)|_OUT(3),62+512,*area,(*area)+4,&savesize);
  if (!flex_alloc((flex_ptr)&savearea,savesize)) {
    return 0;
  }

  // plot the old sprite into the new one, scaled appropriately
  savearea[0]=0;
  r.r[0]=60+512;
  r.r[1]=(int)*area;
  r.r[2]=(int)((*area)+4);
  r.r[3]=(int)savearea;
  _kernel_swi(OS_SpriteOp,&r,&r);

  _swix(OS_SpriteOp,_INR(0,7),52+512,wimparea,sourcesprite,0,0,0,scale,0);

  // reset to screen
  _kernel_swi(OS_SpriteOp,&r,&r);


  // if there was a mask, we need to update it
  switch (masktype(sourcesprite,mask)) {
  case mask_none:
    break;

  case mask_simple_old:
    sds_oldsimplemask(sourcesprite,(*area)+4,w,h);
    break;

  case mask_simple_new:
    sds_newsimplemask(sourcesprite,(*area)+4,w,h);
    break;

  case mask_channel:
    sds_channelmask(sourcesprite,(*area)+4,w,h);
    break;

  case mask_alpha:
    sds_alphamask(sourcesprite,(*area)+4,w,h);
    break;
  }

  flex_free((flex_ptr)&savearea);

//  _swix(OS_SpriteOp,_INR(0,2),12+256,*area,"RAM:$.double");

  return 1;
}

void sprite_halfsize(char *spritename,char *output_name,int *halfsize_flag)
{
  // for a given sprite, see if there's a small version.
  // if not, then halfsize_flag is set and the original name is returned
  // output_name must be a buffer of 13 chars

  memset(output_name,0,13);

  // generate the potential name
  if (*spritename=='!') {
    memcpy(output_name,"sm",2);
    strncpy(output_name+2,spritename,10);
  } else if (strncmp(spritename,"file",4)==0) {
    memcpy(output_name,"small",5);
    strncpy(output_name+5,spritename+4,4);
  } else if (strcmp(spritename,"directory")==0) {
    strcpy(output_name,"small_dir");
  } else {
    // shouldn't happen...
    *halfsize_flag=1;
    strcpy(output_name,spritename);
    return;
  }

  // look for this sprite
  if (wimp_sprite_exists(output_name)) {
    *halfsize_flag=0;
  } else {
    *halfsize_flag=1;
  }

  return;
}

static int hires_wanted(char *spritename)
{
  if (*spritename=='!') return 1;
  if (strncmp(spritename,"file_",5)==0) return 1;
  if (strncmp(spritename,"ic_",3)==0) return 1;
  if (strncmp(spritename,"directory",9)==0) return 1;
  return 0;
}

static int hires_check_modeword(int *ptr)
{
  // ensure this really is a 180x180 dpi sprite
  // if so, adjust it to appear to be 90x90 dpi so that the wimp etc doesn't try to be 
  // good and shrink it down for us >:-(

  if (*ptr<256) return 0; // old-style
  if ((*ptr &1)==0) return 0;

  if ((*ptr & 0x78000001)==0x78000001) {
    // RO5 modeword
    if ((*ptr & 0xf0)==0) {
      *ptr|=0x50;
      return 1;
    }
    return 0;
  } else {
    // RO3.5 modeword
    if ((*ptr & 0x07ffffff)==0x2d0169) { // 180<<1 | 180<14 | 1
      *ptr=(*ptr &0xf8000000) | 0x1680b5; // 90<<1 | 90<<14 | 1
      return 1;
    }
    return 0;
  }
}

static void hires_process(int **newdata,int *length)
{
  int readoffset,writeoffset,spritelength;
  char spritename[13];

  memset(spritename,0,13);

  readoffset=*(*newdata+2);
  writeoffset=readoffset;

  while (readoffset<*length) {
    // are we interested in this sprite?
    // we like:
    // 'file_***'
    // '!****'
    // 'directory'
    // 'ic_****'
    strncpy(spritename,(char *)(*newdata+readoffset/4+1),12);

    spritelength=*(*newdata+readoffset/4);

    if (hires_wanted(spritename) && hires_check_modeword(*newdata+readoffset/4+10)) {
      // keep this one
      // do we already have this sprite?  If so, we delete the old one and use the new one
      if (hires_pool) {
        _swix(OS_SpriteOp,_INR(0,2),256+25,hires_pool,spritename);
      }

      if (readoffset!=writeoffset) {
        // move it down
        memmove(*newdata+writeoffset/4,*newdata+readoffset/4,spritelength);
      }
      writeoffset+=spritelength;



    } else {
      // lose this one
      *(*newdata+1)=*(*newdata+1)-1;
    }
    readoffset+=spritelength;
  }

  // update length for return

  *length=writeoffset;
  *(*newdata+3)=writeoffset;
  *(*newdata)=writeoffset;
}

void hires_iconsprites(char *filename)
{
  // see if we can find a better resolution for doublesize icons
  int type, length, filetype;
  int *filedata;
  int firstoffset;

  char hiresname[3+strlen(filename)];
  sprintf(hiresname,"%s11",filename);

  // it's not a problem if any of this fails - fall back to scaled sprites instead.
  if (_swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(4)|_OUT(6),23,hiresname,&type,&length,&filetype)!=NULL) return;

  if (type!=1 || filetype!=0xff9) return;

  if (!flex_alloc((flex_ptr)&filedata,length+4)) return;

  if (_swix(OS_File,_INR(0,3),255,hiresname,filedata+1,0)) {
    flex_free((flex_ptr)&filedata);
    return;
  }

  // chug through the file and collect any suitable sprites
  hires_process(&filedata,&length);

  // append this chunk to our current pool if appropriate
  if (*(filedata+1)==0) {
    // none left
    flex_free((flex_ptr)&filedata);
    return;
  }

  // extend/shrink the main pool as necessary
  if (hires_pool==NULL) {
    // just move over
    if (!flex_reanchor((flex_ptr)&hires_pool,(flex_ptr)&filedata)) {
      flex_free((flex_ptr)&filedata);
    }
//    _swix(OS_SpriteOp,_INR(0,2),256+12,hires_pool,"ram:$.hires");

    // resize block if we need to
    if (*hires_pool!=flex_size((flex_ptr)&hires_pool)) {
      flex_extend((flex_ptr)&hires_pool,*hires_pool);
    }
    return;
  }

  // we may have deleted some sprites, so may be able to shrink the main pool
  firstoffset=*(filedata+2);

  if (!flex_extend((flex_ptr)&hires_pool,*(hires_pool+3)+length-firstoffset)) return; // not enough room to extend

  // append the new stuff.  Ignore first word (length)
  memcpy(hires_pool+*(hires_pool+3)/4,filedata+firstoffset/4,length-firstoffset);

  // update sprite count
  *(hires_pool+1)=*(hires_pool+1)+*(filedata+1);

  // update pool size
  *hires_pool=*hires_pool+length-firstoffset;

  // update freeword
  *(hires_pool+3)=*(hires_pool+3)+length-firstoffset;

  // lose the processed fragment
  flex_free((flex_ptr)&filedata);
}
