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

#include "kernel.h"
#include "swis.h"
#include "bbc.h"

#include "tboxlibs/flex.h"

#include "pinboard.h"
#include "outline.h"
#include "indirected.h"

#include "debug.h"

#define outline_mask(x,y) (((x)<0 || (y)<0 || (x)>state->text_sprite_w-1 || (y)>state->text_sprite_h-1) ? 0 : *(maskaddr+(x)+(y)*maskstride))
#define outline_set_mask(x,y,m) *(maskaddr+(x)+(y)*maskstride)=m

void outline_init(_backdrop_state *state)
{
  int area_size,save_size,mask_size;
  int mode;
  _kernel_oserror *e;
  int *spriteaddr;
  
  debugf("Outline init\n");

  // claim space for sprite area if possible
  // if not, leave as NULL and renderer to fall back to another method
  
  if (state->text_sprite!=NULL) {
    flex_free((flex_ptr)&state->text_sprite);
    state->text_sprite=NULL;
    debugf("Free state->text_sprite\n");
  }

  if (state->text_savearea!=NULL) {
    flex_free((flex_ptr)&state->text_savearea);
    state->text_savearea=NULL;
    debugf("Free state->text_savearea\n");
  }

  state->text_sprite_w=MAX_ICON_WIDTH/state->pixel_x;
  state->text_sprite_h=ICON_TEXT_HEIGHT/state->pixel_y;

  debugf("Initial X size text_sprite_w: %d (%d/%d)\n",state->text_sprite_w,MAX_ICON_WIDTH,state->pixel_x);
  debugf("Initial Y size text_sprite_h: %d (%d/%d)\n",state->text_sprite_h,ICON_TEXT_HEIGHT,state->pixel_y);

  // work out how much space is needed for the sprite and area
  // area header + sprite header + 8bpp mask size + sprite size

  // sprite 8492 bytes
  // mask bytes?
  // 96x22
  
  debugf("Modevar 9: %d\n",bbc_modevar(-1,9));

  area_size=16 +
    44 +
    (((state->text_sprite_w+3) &~3)*state->text_sprite_h) +
    (((state->text_sprite_w*(1<<bbc_modevar(-1,9)))/8+3)&~3)*state->text_sprite_h;

  debugf("Sprite area size %d bytes\n",area_size);

  mask_size=(((state->text_sprite_w+3) &~3)*state->text_sprite_h);
  //  area_size+=1408; // WHY??? We get issues with the mask creation if there isn't free space for some reason.
  // Plan: do our own sprite mask bodge instead...
  
  debugf("Mask size %d bytes\n",mask_size);

  if (!flex_alloc((flex_ptr)&state->text_sprite,area_size)) {
    debugf("Failed to create sprite area\n");
    state->text_sprite=NULL;
    return;
  }
  
  // create a sprite for the current screen mode
  state->text_sprite[0]=area_size;
  state->text_sprite[1]=0;
  state->text_sprite[2]=16;
  state->text_sprite[3]=16;

  debugf("Initialised sprite area\n");

  // generate a mode word based on the current screen mode
  // for some reason, if we add an 8bpp mask by SpriteOp calls, even though there is enough area allocated, something overflows unless we have lots more
  // sprite area allocated than we really need.
  // so for now we'll manually add the mask.
  
  //  _swix(OS_Byte,_IN(0)|_OUT(2),135,&mode);
  // RISCOS 5 mode word:
  mode=1 | (bbc_modevar(-1,4)<<4) | (bbc_modevar(-1,5)<<6) | (bbc_modevar(-1,0) & 0xff00) | ((1+bbc_modevar(-1,9)) << 20) | 0x78000000;
  debugf("Mode word %08x\n",mode);
  if ((e=_swix(OS_SpriteOp,_INR(0,6),15+256,state->text_sprite,"1",0,state->text_sprite_w,state->text_sprite_h,mode))!=0) {
    debugf("Create fails (%s), try new mode word \n",e->errmess);
    // RO Adjust compatible
    mode = 1 | ((180/(1<<bbc_modevar(-1,4)))<<1) | ((180/(1<<bbc_modevar(-1,5)))<<14) | ((1+bbc_modevar(-1,9))<<27);
    debugf("Mode word %08x\n",mode);
  
    e=_swix(OS_SpriteOp,_INR(0,6),15+256,state->text_sprite,"1",0,state->text_sprite_w,state->text_sprite_h,mode);
    if (e) debugf("Create fails (%s)\n",e->errmess);
  }

  if (e) {
    // unable to create this type of sprite, so abort
    flex_free((flex_ptr)&state->text_sprite);
    state->text_sprite=NULL;
    debugf("Failed to create sprite so abort\n");
    return;
  }
  // add an 8bpp mask
  //e=_swix(OS_SpriteOp,_INR(0,3),38+256,state->text_sprite,"1",2u<<30);
  
  debugf("Locate sprite\n");

  // only 1 sprite in area
  e=_swix(OS_SpriteOp,_INR(0,2)|_OUT(2),24+256,state->text_sprite,"1",&spriteaddr);
  if (e) {
    debugf("Failed to locate sprite: %s\n",e->errmess);
    // unable to access this sprite, so abort
    flex_free((flex_ptr)&state->text_sprite);
    state->text_sprite=NULL;
    return;
  }

  *(spriteaddr+9)=*spriteaddr; // mask goes at the end of the data pool
  *spriteaddr=*spriteaddr+mask_size; // adjust the sprite size
  state->text_sprite[3]+=mask_size;
  *(spriteaddr+10)|=0x80000000; // set a wide mask
 
  debugf("Get save area size\n");
  // get save area size
  e=_swix(OS_SpriteOp,_INR(0,2)|_OUT(3),62+256,state->text_sprite,"1",&save_size);

  if (e) {
    debugf("Failed to get size: %s\n",e->errmess);
  }

  if (!flex_alloc((flex_ptr)&state->text_savearea,save_size)) {
    debugf("Failed to allocate store for save area\n");
    flex_free((flex_ptr)&state->text_sprite);
    state->text_sprite=NULL;
    state->text_savearea=NULL;
    return;
  }

  debugf("Success!  Clear cache\n");

  outline_clear_cache(state);
}

static void outline_generate(_backdrop_state *state,pin *p,int y_offset)
{
  _kernel_swi_regs sa_r;
  _kernel_oserror *e;
  int *spriteaddr,maskstride;
  char *maskaddr;
  int mx,my;
  int fg,bg;

  debugf("Outline generate starts\n");

  if (p->icondata.flags & WimpIcon_Selected) {
    fg=state->bg_colour;
    bg=state->fg_colour;
  } else {
    fg=state->fg_colour;
    bg=state->bg_colour;
  }

  debugf("Render with FG %08x, BG %08x\n",fg,bg);

  spriteaddr=state->text_sprite+4;
  debugf("Sprite area at %p\n",(void *)state->text_sprite);
  debugf("Sprite at %p\n",(void *)spriteaddr);
  maskaddr=((char *)spriteaddr)+*(spriteaddr+9);
  debugf("Mask at %p\n",(void *)maskaddr);
  maskstride=(state->text_sprite_w+3) &~3;
  debugf("Mask stride %d\n",maskstride);

  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.1");  if (e) debugf("Save failed: %s\n",e->errmess);

  // create a sprite containing outlined text
  // remove mask
  debugf("Switch output to mask\n");
  state->text_savearea[0]=0;
  sa_r.r[0]=256+61;
  sa_r.r[1]=(int)state->text_sprite;
  sa_r.r[2]=(int)"1";
  sa_r.r[3]=(int)state->text_savearea;
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) debugf("Failed: %s\n",e->errmess);
  debugf("Set colour\n");
  e=_swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),0xffffff00,0,0);
  if (e) debugf("Failed: %s\n",e->errmess);
  debugf("Plot rubout box\n");
  e=_swix(OS_Plot,_INR(0,2),4,0,0);
  if (e) debugf("Failed: %s\n",e->errmess);
  debugf("Plot rubout box (2)\n");
  e=_swix(OS_Plot,_INR(0,2),96+5,MAX_ICON_WIDTH,ICON_TEXT_HEIGHT);
  if (e) debugf("Failed: %s\n",e->errmess);

  debugf("Reset output to screen\n");
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) debugf("Failed: %s\n",e->errmess);

  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.2");  if (e) debugf("Save failed: %s\n",e->errmess);

  // fill to BG colour
  debugf("fill to BG colour: switch output to sprite\n");
  state->text_savearea[0]=0;
  sa_r.r[0]=256+60;
  sa_r.r[1]=(int)state->text_sprite;
  sa_r.r[2]=(int)"1";
  sa_r.r[3]=(int)state->text_savearea;
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  debugf("Set colour to %08x\n",bg);
  e=_swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),bg,0,0);
  if (e) debugf("Failed: %s\n",e->errmess);
  e=_swix(OS_Plot,_INR(0,2),4,0,0);
  if (e) debugf("Failed: os_plot: %s\n",e->errmess);
  e=_swix(OS_Plot,_INR(0,2),96+5,MAX_ICON_WIDTH,ICON_TEXT_HEIGHT);
  if (e) debugf("Failed: os_plot(2): %s\n",e->errmess);
  debugf("Reset output to screen\n");

  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) debugf("Failed: %s\n",e->errmess);

  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.3");  if (e) debugf("Save failed: %s\n",e->errmess);

  // plot text to mask
  debugf("Plot text to mask: switch output to mask\n");
  state->text_savearea[0]=0;
  sa_r.r[0]=256+61;
  sa_r.r[1]=(int)state->text_sprite;
  sa_r.r[2]=(int)"1";
  sa_r.r[3]=(int)state->text_savearea;
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) debugf("Failed: %s\n",e->errmess);

  debugf("Set text colour to 0xfefefe00\n");
  e=_swix(Wimp_TextOp,_INR(0,2),0,0xfefefe00,0x00000000);
  if (e) debugf("Failed: %s\n",e->errmess);
  debugf("Plot text %s at %d,%d\n",indirected(p->icondata.buffer,char *),state->pixel_x,state->pixel_y+y_offset);
  e=_swix(Wimp_TextOp,_INR(0,5),2,indirected(p->icondata.buffer,char *),-1,-1,state->pixel_x,state->pixel_y+y_offset);
  if (e) debugf("Failed: %s\n",e->errmess);

  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.4");  if (e) debugf("Save failed: %s\n",e->errmess);

  // invert mask
  debugf("Invert mask\n");
  e=_swix(ColourTrans_SetGCOL,_IN(0)|_IN(3)|_IN(4),0xffffff00,0,3);
  if (e) debugf("Failed: %s\n",e->errmess);
  e=_swix(OS_Plot,_INR(0,2),4,0,0);
  if (e) debugf("Failed: %s\n",e->errmess);
  e=_swix(OS_Plot,_INR(0,2),96+5,MAX_ICON_WIDTH,ICON_TEXT_HEIGHT);
  if (e) debugf("Failed: %s\n",e->errmess);

  debugf("Switch output to screen\n");
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) debugf("Failed: %s\n",e->errmess);

  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.5");  if (e) debugf("Save failed: %s\n",e->errmess);

  // remove mask from text area

  debugf("Remove mask from text area\n");
  for (my=0;my<state->text_sprite_h;my++) {
    for (mx=0;mx<state->text_sprite_w;mx++) {
      if (outline_mask(mx,my)>0) outline_set_mask(mx,my,255);
    }
  }
  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.6");  if (e) debugf("Save failed: %s\n",e->errmess);

  // plot text over the top
  debugf("Plot text over the top\n");
  debugf("Switch output to sprite\n");
  state->text_savearea[0]=0;
  sa_r.r[0]=256+60;
  sa_r.r[1]=(int)state->text_sprite;
  sa_r.r[2]=(int)"1";
  sa_r.r[3]=(int)state->text_savearea;
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) debugf("Failed: %s\n",e->errmess);

  debugf("Set text colours to %08x, %08x\n",fg,bg);
  e=_swix(Wimp_TextOp,_INR(0,2),0,fg,bg);
  if (e) debugf("Failed: %s\n",e->errmess);

  debugf("Plot %s at %d,%d\n",indirected(p->icondata.buffer,char *),state->pixel_x,state->pixel_y+y_offset);
  e=_swix(Wimp_TextOp,_INR(0,5),2,indirected(p->icondata.buffer,char *),-1,-1,state->pixel_x,state->pixel_y+y_offset);
  if (e) debugf("Failed: %s\n",e->errmess);
  debugf("Reset output to screen\n");
  e=_kernel_swi(OS_SpriteOp,&sa_r,&sa_r);
  if (e) debugf("Failed: %s\n",e->errmess);

  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.7");  if (e) debugf("Save failed: %s\n",e->errmess);

  debugf("Create outline\n");
  // create the outline
  for (my=0;my<state->text_sprite_h;my++) {
    for (mx=0;mx<state->text_sprite_w;mx++) {
      if (outline_mask(mx,my)==0) {
        if (outline_mask(mx+1,my)==255 || outline_mask(mx-1,my)==255 || outline_mask(mx,my-1)==255 || outline_mask(mx,my+1)==255) {
          outline_set_mask(mx,my,254); // almost completely opaque
        }
      }
    }
  }
  e=_swix(OS_SpriteOp,_INR(0,2),256+12,state->text_sprite,"RAM:$.8");  if (e) debugf("Save failed: %s\n",e->errmess);

  debugf("All done\n");
  // done
}

void outline_render(_backdrop_state *state,pin *p,int id,int x,int y,int y_offset)
{
  int cid=id | (p->icondata.flags & WimpIcon_Selected); // else we don't redraw on selection state change
  _kernel_oserror *e;

  debugf("Outline render id %d\n",cid);

  if (state->cached_pin!=cid) {
    debugf("Cache miss; redo outline\n");
    outline_generate(state,p,y_offset);
    state->cached_pin=cid;
  }

  // plot the sprite; is valid for current screen mode and palette already
  debugf("Plot sprite to %d,%d\n",x,y);
  e=_swix(OS_SpriteOp,_INR(0,7),256+52,state->text_sprite,"1",x,y,(1<<3),0,0);
  if (e) debugf("Plot failed: %s\n",e->errmess); else debugf("Plot OK\n");
}
