#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "kernel.h"
#include "swis.h"

#include "tboxlibs/wimp.h"
#include "tboxlibs/wimplib.h"
#include "tboxlibs/flex.h"

#include "iconbar.h"
#include "modversion.h"
#include "pinboard.h"
#include "pinsprite.h"

static int iconbar_opacity_supported=-1;
static int iconbar_last_scrollx=-1;
static int iconbar_stopped_scrollx=-1;
int iconbar_modified=0;

#define ALTERED_NOT 0
#define ALTERED_NO_ORIGINAL 1
#define ALTERED 2

static char iconbar_altered[]={ALTERED_NOT,ALTERED_NOT,ALTERED_NOT,ALTERED_NOT,ALTERED_NOT};

void iconbar_opacity_init(void)
{
  iconbar_opacity_supported=module_version("WindowManager")>=58100;
}

static int *iconbar_find_tile_sprite(int **wimp_area,int bpp)
{
  // try to find a tile sprite
  char name[13];
  int *sprite_ptr;
                                       
  // first, specific to this screen mode
  sprintf(name,"tile_i%%%d",1<<bpp);
  if ((sprite_ptr=find_sprite(name,wimp_area))!=NULL) return sprite_ptr;
  if ((sprite_ptr=find_sprite("tile_i",wimp_area))!=NULL) return sprite_ptr;

  sprintf(name,"tile_1-%d",1<<bpp);
  if ((sprite_ptr=find_sprite(name,wimp_area))!=NULL) return sprite_ptr;
  return find_sprite("tile_1",wimp_area);
}

static int iconbar_save_sprite(int *spritearea,int *sprite,char *filename)
{
  // spool it out
  int handle;
  _kernel_oserror *e;

  int area[3]; // saved files miss first word
  area[0]=1; // # sprites
  area[1]=16; // 1st sprite offset
  area[2]=*sprite+area[1]; // offset to first free word

  if (_swix(OS_Find,_INR(0,1)|_OUT(0),128,filename,&handle)) return 0;
  e=_swix(OS_GBPB,_INR(0,3),2,handle,area,12);
  if (!e) e=_swix(OS_GBPB,_INR(0,3),2,handle,sprite,4+6); // sprite length+first bit of name
  if (!e) e=_swix(OS_BPut,_INR(0,1),'-',handle); // convert % to -
  if (!e) e=_swix(OS_GBPB,_INR(0,3),2,handle,((char *)sprite)+4+6+1,(*sprite)-4-6-1); // rest of the sprite

  _swix(OS_Find,_INR(0,1),0,handle);

  return e==NULL;
}

static void iconbar_clear_tile(int *sprite_area,int *sprite_pointer)
{
  int save_area_size;
  int *save_area=NULL;
  int r[4];

  if (_swix(OS_SpriteOp,_INR(0,2)|_OUT(3),512+62,sprite_area,sprite_pointer,&save_area_size)) return;

  if (!flex_alloc((flex_ptr)&save_area,save_area_size)) return;

  save_area[0]=0;

  if (_swix(OS_SpriteOp,_INR(0,3)|_OUTR(0,3),60+512,sprite_area,sprite_pointer,save_area,&r[0],&r[1],&r[2],&r[3])==NULL) {
    _swix(Wimp_SetColour,_IN(0),1);
    _swix(OS_Plot,_INR(0,2),4,0,0);
    _swix(OS_Plot,_INR(0,2),96+5,32767,32767);
    _swix(OS_SpriteOp,_INR(0,3),r[0],r[1],r[2],r[3]);
  }

  flex_free((flex_ptr)&save_area);
}

int iconbar_reset(void)
{
  // undo any changes to the iconbar
  // we can't simply delete a sprite from the Wimp area
  // as this results in unpleasantness.
  // we'll save out a copy of the original and reload via IconSprites
  // so that the Wimp's internal pointers are all happy

  int i,*spritearea,*spriteptr,rc=0;

  //_swix(Wimp_BaseOfSprites,_OUT(1),&spritearea);

  for (i=0;i<5;i++) {
    switch (iconbar_altered[i]) {
      case ALTERED_NOT:
        // do nothing
        break;

      case ALTERED_NO_ORIGINAL:
        // there wasn't an original, so extract a matching tile_1 sprite
      case ALTERED:
        // there was an original, so find_tile_sprite should pick this up for export
        if ((spriteptr=iconbar_find_tile_sprite(&spritearea,1+i))!=NULL) {
          // output this one...
//iconbar_save_sprite(spritearea,spriteptr,"ram:stored");
          if (iconbar_save_sprite(spritearea,spriteptr,"pipe:$.restore")) {
            _swix(OS_CLI,_IN(0),"%IconSprites pipe:$.restore");
          }
        } else {
          // just paint the current one grey and be done
          char name[13];
          sprintf(name,"tile_i-%d",1<<(1+i));
          spriteptr=find_sprite(name,&spritearea);
          if (spriteptr) iconbar_clear_tile(spritearea,spriteptr);
        }

        iconbar_altered[i]=ALTERED_NOT;
        rc=1;
        break;
    }
  }

  iconbar_modified=0;

  return rc;
}

static void iconbar_save(int bpp)
{
  char old_name[13],new_name[13];
  int spritearea;
  _swix(Wimp_BaseOfSprites,_OUT(1),&spritearea);

  sprintf(new_name,"tile_i%%%d",1<<bpp);
  sprintf(old_name,"tile_i-%d",1<<bpp);

  // is there already a backup?
  if (_swix(OS_SpriteOp,_INR(0,2),256+40,spritearea,new_name)==NULL) {
    iconbar_altered[bpp-1]=ALTERED;
    return;
  }

  // try to make one

  if (_swix(OS_SpriteOp,_INR(0,3),256+26,spritearea,old_name,new_name)) {
    iconbar_altered[bpp-1]=ALTERED_NO_ORIGINAL;
  } else {
    iconbar_altered[bpp-1]=ALTERED;
  }
}

#define XEIG 0
#define YEIG 1
#define SCREENWIDTH 2
#define SCREENHEIGHT 3
#define SCREENBPP 4

void iconbar_redraw(void)
{
  int vars[]={4,5,11,12,-1};
  _swix(OS_ReadVduVariables,_INR(0,1),vars,vars);

  wimp_force_redraw(-1,0,0,vars[2]<<vars[0],ICONBAR_HEIGHT);
}

static void iconbar_plot(int x,int y,_backdrop_state *state,int *scale)
{
  // plot fragment of backdrop image
  int *area=(int *)state->bd_image;
  int opacity=(255*state->iconbar_opacity)/100;

  switch (BD_IMAGE_TYPE(state->bd_flags)) {
    case BD_IMAGE_SPRITE:
      _swix(OS_SpriteOp,_INR(0,7),52+512,area,((char *)area)+*(area+2),x,y,opacity<<8,scale,state->colourtrans);
      break;

    case BD_IMAGE_JPEG:
      _swix(JPEG_PlotScaled,_INR(0,6),state->bd_image,x,y,scale,state->image_length,3 | (opacity<<4),0);
      break;
  }
}

static int iconbar_do_render(_backdrop_state *state,int *sprite_area,int *sprite_pointer,int *vars,int wrap,int xscroll)
{
  int save_area_size;
  int *save_area=NULL;
  int *coltrans=NULL;
  int coltrans_size;
  int r[4];
  int switched=0;
  int *wimptile_ptr;
  int *wimp_area;
  int scale[4];

  // get save area size
  if (_swix(OS_SpriteOp,_INR(0,2)|_OUT(3),512+62,sprite_area,sprite_pointer,&save_area_size)) return 0;

  if (!flex_alloc((flex_ptr)&save_area,save_area_size)) return 0;

  // switch output
  save_area[0]=0;
  if (_swix(OS_SpriteOp,_INR(0,3)|_OUTR(0,3),60+512,sprite_area,sprite_pointer,save_area,&r[0],&r[1],&r[2],&r[3])) goto abort;
  switched=1;

  // find the existing tile background sprite if possible...
  // but only if we'll see it

  if (state->iconbar_opacity!=0) {
    wimptile_ptr=iconbar_find_tile_sprite(&wimp_area,vars[SCREENBPP]);

    if (wimptile_ptr) {
      // tile the existing background
      if (_swix(ColourTrans_SelectTable,_INR(0,5)|_OUT(4),wimp_area,wimptile_ptr,-1,-1,0,1,&coltrans_size)) goto abort;
      if (coltrans_size) {
        if (!flex_alloc((flex_ptr)&coltrans,coltrans_size)) goto abort;
        if (_swix(ColourTrans_SelectTable,_INR(0,5),wimp_area,wimptile_ptr,-1,-1,coltrans,1)) goto abort;
      }

      // get tile size
      int tile_width,tile_height,tile_mode,x,tile_xeig;
      if (_swix(OS_SpriteOp,_INR(0,2)|_OUTR(3,4)|_OUT(6),40+512,wimp_area,wimptile_ptr,&tile_width,&tile_height,&tile_mode)) goto abort;
      if (_swix(OS_ReadModeVariable,_INR(0,1)|_OUT(2),tile_mode,4,&tile_xeig)) goto abort;

      for (x=0;x<=(1+vars[SCREENWIDTH])<<vars[XEIG];x+=tile_width<<tile_xeig) _swix(OS_SpriteOp,_INR(0,7),52+512,wimp_area,wimptile_ptr,x,0,0,0,coltrans);
      if (coltrans) {
        flex_free((flex_ptr)&coltrans);
        coltrans=NULL;
      }
    } else {
      // plain colour background
      _swix(Wimp_SetColour,_IN(0),1);
      _swix(OS_Plot,_INR(0,2),4,0,0);
      _swix(OS_Plot,_INR(0,2),96+5,(1+vars[SCREENWIDTH])<<vars[XEIG],ICONBAR_HEIGHT);
    }
  }

  // overlay our backdrop image
  scale[0]=(1+vars[SCREENWIDTH])<<vars[XEIG];
  scale[1]=(1+vars[SCREENHEIGHT])<<vars[YEIG];
  scale[2]=state->image_width*state->pixel_x;
  scale[3]=state->image_height*state->pixel_y;

  char clip[9];
  // set clip rectangle
  clip[0]=24;
  clip[1]=0;
  clip[2]=0; // Xmin
  clip[3]=(ICONBAR_HEIGHT-wrap) &0xff;
  clip[4]=(ICONBAR_HEIGHT-wrap) >>8; // Ymin
  clip[5]=(scale[0]-1) &0xff;
  clip[6]=(scale[0]-1)>>8; // Xmax
  clip[7]=ICONBAR_HEIGHT &0xff;
  clip[8]=ICONBAR_HEIGHT >>8; // Ymax
  _swix(OS_WriteN,_INR(0,1),clip,9);

  // plot bottom bit of backdrop to top of sprite
  iconbar_plot(xscroll % scale[0],ICONBAR_HEIGHT-wrap,state,scale);
  if (xscroll) iconbar_plot(scale[0] + (xscroll % scale[0]),ICONBAR_HEIGHT-wrap,state,scale);

  // update clip rectangle
  clip[3]=0;
  clip[4]=0; // new Ymin
  clip[7]=(ICONBAR_HEIGHT-wrap-1) &0xff;
  clip[8]=(ICONBAR_HEIGHT-wrap-1) >>8;  // new Ymax
  _swix(OS_WriteN,_INR(0,1),clip,9);

  // plot next bit of backdrop to bottom bit of sprite
  iconbar_plot(xscroll % scale[0],-wrap,state,scale);
  if (xscroll) iconbar_plot(scale[0] + (xscroll % scale[0]),-wrap,state,scale);

  // reset to normal screen output
  _swix(OS_SpriteOp,_INR(0,3),r[0],r[1],r[2],r[3]);

  flex_free((flex_ptr)&save_area);

  return 1;

abort:
  if (switched) _swix(OS_SpriteOp,_INR(0,3),r[0],r[1],r[2],r[3]);
  if (coltrans) flex_free((flex_ptr)&coltrans);
  if (save_area) flex_free((flex_ptr)&save_area);

  return 0;
}

void iconbar_set_image(_backdrop_state *state)
{
  WimpGetWindowInfoBlock info;
  int wrap;
  int *sprite_area=NULL;
  int sprite_area_size;
  int *sprite_pointer;
  char tile_name[13];
  int screen_mode;

  if (!iconbar_opacity_supported) return;

  if (state->iconbar_opacity==100) {
    // ensure we aren't messing and bail out
    if (iconbar_reset()) iconbar_redraw();
    return;
  }

  int vars[]={4,5,11,12,9,-1};
  if (_swix(OS_ReadVduVariables,_INR(0,1),vars,vars)) return;

  info.window_handle=-2;
  if (_swix(Wimp_GetWindowInfo,_IN(1),1|((int)&info))) return;
  wrap=-(info.window_data.extent.ymin);
  iconbar_stopped_scrollx=info.window_data.xscroll;
  iconbar_last_scrollx=info.window_data.xscroll;

  sprintf(tile_name,"tile_i-%d",1<<vars[SCREENBPP]);

  // do we need to preserve the current image?
  if (iconbar_altered[vars[SCREENBPP]-1]==ALTERED_NOT) {
    // this is a new setup.  We need a new sprite file completely.
    if (_swix(OS_ScreenMode,_IN(0)|_OUT(1),1,&screen_mode)) return;

    iconbar_save(vars[SCREENBPP]);

    sprite_area_size=128+4*(1+vars[SCREENWIDTH])*(ICONBAR_HEIGHT>>vars[YEIG]);

    if (!flex_alloc((flex_ptr)&sprite_area,sprite_area_size)) return;

    // make our sprite    
    sprite_area[0]=sprite_area_size;
    sprite_area[1]=0;
    sprite_area[2]=16;
    sprite_area[3]=16;
    if (_swix(OS_SpriteOp,_INR(0,6),15+256,sprite_area,tile_name,0,1+vars[SCREENWIDTH],ICONBAR_HEIGHT>>vars[YEIG],screen_mode)) {
      flex_free((flex_ptr)&sprite_area);
      return;
    }

    if (_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,sprite_area,tile_name,&sprite_pointer)) {
      flex_free((flex_ptr)&sprite_area);
      return;
    }

    if (iconbar_do_render(state,sprite_area,sprite_pointer,vars,wrap,iconbar_stopped_scrollx)) {
      // save the sprite file
      if (_swix(OS_SpriteOp,_INR(0,2),12+256,sprite_area,"pipe:$.tile")==NULL) {
        // and transfer to the wimp area
        if (_swix(OS_CLI,_IN(0),"%IconSprites pipe:$.tile")==NULL) {
          // success!!!!
          iconbar_altered[vars[SCREENBPP]-1]=ALTERED;
          iconbar_redraw();
          iconbar_modified=1;
        }
      }
    }

    flex_free((flex_ptr)&sprite_area);
  } else {
    // we've done this before
    // locate the sprite so we can write directly into it
    sprite_pointer=find_sprite(tile_name,&sprite_area);
    if (sprite_pointer) {
      if (iconbar_do_render(state,sprite_area,sprite_pointer,vars,wrap,iconbar_stopped_scrollx)) {
        iconbar_redraw();
        iconbar_modified=1;
      }
    }
  }
}

void iconbar_poll(_backdrop_state *state)
{
  // if the icon bar has scrolled then we'll need to redo the image
  // to reduce system load we'll only update once the scrolling has been completed.  Otherwise it'll be very
  // messy with the repeated image refresh and complete iconbar redraws

  WimpGetWindowStateBlock info;
  info.window_handle=-2;
  wimp_get_window_state(&info);

  if (iconbar_last_scrollx!=info.xscroll) {
    // scroll has changed.  Make a note.
    iconbar_last_scrollx=info.xscroll;
    return;
  }

  // scroll has not changed, but is it the same as the last redraw?
  if (iconbar_stopped_scrollx!=iconbar_last_scrollx) {
    iconbar_set_image(state);
  }
}
