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

#include "swis.h"

#include "tboxlibs/toolbox.h"
#include "tboxlibs/event.h"
#include "tboxlibs/flex.h"
#include "tboxlibs/wimp.h"
#include "tboxlibs/wimplib.h"
#include "tboxlibs/menu.h"
#include "tboxlibs/saveas.h"
#include "tboxlibs/colourdbox.h"
#include "tboxlibs/fontdbox.h"

#include "pinboard.h"
#include "indirected.h"
#include "iconize.h"
#include "tinydirs.h"
#include "events.h"
#include "pinfilter.h"
#include "sticky.h"
#include "localfiles.h"
#include "fs.h"
#include "options.h"
#include "msgtrans.h"
#include "buffer.h"
#include "base.h"
#include "outline.h"
#include "paths.h"
#include "drag.h"
#include "debug.h"
#include "confirm.h"
#include "mystring.h"
#include "toggle.h"
#include "watermark.h"
#include "doublesize.h"

int taskhandle=0,filer_taskhandle=0;
int pollword=0;
int flex_da=0; // we may need to free this on module exit if it's forced

static int wimp_messages[]={Wimp_MDataSave,
                            Wimp_MDataSaveAck,
                            Wimp_MDataLoad,
                            Wimp_MDataLoadAck,
                            Wimp_MHelpRequest,
                            Wimp_MModeChange,
                            Wimp_MTaskCloseDown,
                            Wimp_MIconize,
                            Wimp_MWindowClosed,
                            Wimp_MWindowInfo,
                            Wimp_MFontChanged,
                            Wimp_MIconizeAt,
                            Wimp_MToggleBackdrop,
                            0};

static int toolbox_events[]={
                             TEvent_Tinydir_SelectAll,
                             TEvent_Tinydir_ClearSelection, 
                             TEvent_Tinydir_RemoveIcon,
                             TEvent_Tinydir_OpenParent,     
                             TEvent_Tinywin_Clicked,
                             
                             Toolbox_ObjectAutoCreated,
                             
                             Window_AboutToBeShown,
                             ActionButton_Selected,
                             WritableField_ValueChanged,
                             Window_HasBeenHidden,
                             
                             Menu_AboutToBeShown,
                             Menu_HasBeenHidden,
                             Menu_Selection,

                             ColourDbox_AboutToBeShown,
                             ColourDbox_ColourSelected,
                             
                             FontDbox_AboutToBeShown,
                             FontDbox_ApplyFont,

                             SaveAs_AboutToBeShown,
                             SaveAs_DialogueCompleted,
                             SaveAs_SaveToFile,
                             SaveAs_FillBuffer,

                             TEvent_Tinydir_FilterClick, /* filter iconbar click */
                             TEvent_Sticky_CloseClick, /* filter sticky close */
                             TEvent_MenuClick,
                             TEvent_Sticky_Open,
                             0};

static IdBlock toolbox_id;
MessagesFD msgtrans;
int *tb_sprite_area;

static const char *rmensure[]={
  "%RMEnsure Toolbox     0.00 %RMLoad System:Modules.Toolbox.Toolbox",
  "%RMEnsure Toolbox     0.00 %Error Pinboard requires Toolbox 1.58 or later to run",
  "%RMEnsure Menu        0.00 %RMLoad System:Modules.Toolbox.Menu",
  "%RMEnsure Menu        0.00 %Error Pinboard requires the Toolbox Menu module to run",
  "%RMEnsure Window      0.00 %RMLoad System:Modules.Toolbox.Window",
  "%RMEnsure Window      0.00 %Error Pinboard requires the Toolbox Window module to run",
  "%RMEnsure TextGadgets 0.00 %RMLoad System:Modules.Toolbox.TextGadget",
  "%RMEnsure TextGadgets 0.00 %Error Pinboard requires the Toolbox TextGadget module 0.44 or later to run",
  "%RMEnsure IconBar     0.00 %RMLoad System:Modules.Toolbox.IconBar",
  "%RMEnsure IconBar     0.00 %Error Pinboard requires the Toolbox IconBar module to run",
  "%RMEnsure ColourDbox  0.00 %RMLoad System:Modules.Toolbox.ColourDBox",
  "%RMEnsure ColourDbox  0.00 %Error Pinboard requires the Toolbox ColourDbox module to run",
  "%RMEnsure FontDbox    0.00 %RMLoad System:Modules.Toolbox.FontDbox",
  "%RMEnsure FontDbox    0.00 %Error Pinboard requires the Toolbox FontDbox module to run",
  "%RMEnsure SaveAs      0.00 %RMLoad System:Modules.Toolbox.SaveAs",
  "%RMEnsure SaveAs      0.00 %Error Pinboard requires the Toolbox SaveAs module to run",
  NULL};

static void ensure_toolbox(void)
{
  int i;
  _kernel_oserror *e;
  for (i=0;rmensure[i];i++) {
    e=_swix(OS_CLI,_IN(0),rmensure[i]);
    if (e) {
      wimp_report_error(e,1,"Pinboard",0,0,0); // in case messagetrans failed to load resources
      exit(1); // fatal
    }
  }
}

static void app_init(void)
{
  _kernel_oserror *e;
  
  if ((e=toolbox_initialise(0,310,wimp_messages,toolbox_events,"Resources:$.Resources.Pinboard",&msgtrans,&toolbox_id,0,&taskhandle,(void **)&tb_sprite_area)) !=NULL) {
    wimp_report_error(e,1,"Pinboard",0,0,0); // in case messagetrans failed to load resources
    exit(1);
  }
  
  flex_da=flex_init("Pinboard",(int *)&msgtrans,128*1024*1024);
  
  event_initialise(&toolbox_id);
  
  // start event handling
  event_set_mask(Wimp_Poll_NullMask | Wimp_Poll_PollWord);
  
  pinfilter_start(taskhandle);

  confirm_init();
}

static int filer_get_taskhandle(void)
{
  // look for the filer
  int call=0;
  int info[4];
  
  while (call>=0) {
    _swix(TaskManager_EnumerateTasks,_INR(0,2)|_OUT(0),call,info,16,&call);
    if (strcmp((char *)info[1],"Filer")==0) return info[0];
  } 
  
  return 0;
}

static int quit_message(WimpMessage *message,void *handle)
{
  int *quit=(int *)handle;
  IGNORE(message);
  
  *quit=1;
  return 1;
}

static int overwrite_find_name(char *name)
{
  // update name...

  int index=1;
  int offset;
  int type;

  offset=strlen(name);
  if (offset>211) offset=211-5;

  do {
    sprintf(name+offset,"_%d",index);
    _swix(OS_File,_INR(0,1)|_OUT(0),5,name,&type);
    if (type==0) return 1;
    offset++;
  } while (offset<1000);

  msgtrans_report_error("OverFail");
  return 0;
}

static int message_datasave(WimpMessage *message,void *handle)
{
  _backdrop_state *state=(_backdrop_state *)handle;
  char *path=getenv("Pinboard$Path");
  char *dot;
  IGNORE(handle);
  
  WimpMessage datasaveack;
  datasaveack.data.data_save_ack=message->data.data_save;


  // is it going to a sticky?
  if (sticky_find_from_handle(message->data.data_save.destination_window)!=-1) {
    // make it a temporary file instead
    strcpy(datasaveack.data.data_save_ack.leaf_name,"<Wimp$Scrap>");
    datasaveack.data.data_save_ack.estimated_size=-1; // unsafe
  } else {
    // we convert to a local path for saving
    int type;

    if ((dot=strrchr(message->data.data_save.leaf_name,'.'))!=NULL) {
      if (strlen(dot+1)+strlen(path)>211) return 1;
      sprintf(datasaveack.data.data_save_ack.leaf_name,"%s%s",path,dot+1);
    } else {
      if (strlen(message->data.data_save.leaf_name)+strlen(path)>211) return 1; // overflow

      sprintf(datasaveack.data.data_save_ack.leaf_name,"%s%s",path,message->data.data_save.leaf_name);
    }

    // do we already have a file by this name?
    _swix(OS_File,_INR(0,1)|_OUT(0),5,datasaveack.data.data_save_ack.leaf_name,&type);
    if (type!=0) {
      // already present.  Do we care?
      switch (state->overwrite) {
        case OVERWRITE_ALWAYS:
          break;

        case OVERWRITE_RENAME:
          // find an unused name
          if (!overwrite_find_name(datasaveack.data.data_save_ack.leaf_name)) return 1;
          break;

        case OVERWRITE_CONFIRM:
          // ask user to intervene
          {
            char buttons[96];
            int response;
            strcpy(buttons,msgtrans_lookup("OverAbt"));
            strcat(buttons,",");
            strcat(buttons,msgtrans_lookup("OverRep"));
            strcat(buttons,",");
            strcat(buttons,msgtrans_lookup("OverRen"));
            response=wimp_report_error(msgtrans_errorlookup("Overmsg"),(4<<9) | (1<<8),msgtrans_lookup("_TaskName"),0,0,buttons);
            if (response==3) return 1; // abort
            if (response==5) {
              if (!overwrite_find_name(datasaveack.data.data_save_ack.leaf_name)) return 1;
            }
          }
          break;
      }
    }

  }

  datasaveack.hdr.size=((strlen(datasaveack.data.data_save_ack.leaf_name)+1+44)+3) &~3;
  datasaveack.hdr.your_ref=message->hdr.my_ref;
  datasaveack.hdr.action_code=Wimp_MDataSaveAck;
  wimp_send_message(17,&datasaveack,message->hdr.sender,0,0);
  return 1;
}

static void autosave_check_update(void)
{
  // if *Pinboard was run from an obey file, use this filename as a future save point
  // filename is in Obey$File
  // check if it starts 'Pinboard'
  char *testname=getenv("Obey$File");
  FILE *in=fopen(testname,"r");
  char buf[9]={0};

  if (in) {
    fread(buf,1,9,in);
    fclose(in);
    if (strncasecmp(buf,"Pinboard\n",9)==0) {
      // yes, update the destination
      saveas_set_file_name(0,saveas_win,testname);
    }
  }
}

static int event_pollword(int event_code,WimpPollBlock *event,IdBlock *id,void *handle)
{
  buffer_entry *entry;
  _kernel_oserror *e;
  _backdrop_state *state=(_backdrop_state *)handle;
  IGNORE(id);
  IGNORE(event);
  IGNORE(event_code);
    
  if (pollword & 2) {
    pollword &= ~2;

    sticky_check_furniture(state);
  }

  if (pollword==0) return 1;

  pollword &= ~1;

  do {
    e=buffer_shift(&entry);
    if (e==NULL && entry!=NULL) {
      switch (entry->hdr.type) {
      case BUFFER_ADD_TINYDIR:
        {
          int type,filetype;
          _swix(OS_File,_INR(0,1)|_OUT(0)|_OUT(6),23,entry->data.add_tinydir.path,&type,&filetype);
          if (type!=0) {
            if (filetype==0x2000) fs_boot_application(entry->data.add_tinydir.path);
            tinydir_add(state,entry->data.add_tinydir.path[0]?entry->data.add_tinydir.path:NULL,-1);
          }
        }
        break;
        
      case BUFFER_ADD_XTINYDIR:
        tinydir_xadd(state,entry->data.add_tinydir.path,-1);
        break;

      case BUFFER_REMOVE_TINYDIR:
        tinydir_remove_command(state,entry->data.remove_tinydir.path[0]?entry->data.remove_tinydir.path:NULL);
        break;
        
      case BUFFER_ADD_PIN:
        backdrop_add_pin_command(state,entry->data.add_pin.path,entry->data.add_pin.x,entry->data.add_pin.y,entry->data.add_pin.locked);
        break;

      case BUFFER_ADD_XPIN:
        backdrop_add_xpin_command(state,entry->data.add_pin.path,entry->data.add_pin.x,entry->data.add_pin.y,entry->data.add_pin.locked);
        break;
        
      case BUFFER_SET_OPTIONS:
        state->snap_to_grid=entry->data.set_options.snap_to_grid;
        state->disable_adjust=entry->data.set_options.disable_adjust;
        state->setting_iconize=entry->data.set_options.iconise_corner;
        state->setting_iconize_stack=entry->data.set_options.iconise_stack;
        state->setting_tidy=entry->data.set_options.tidy_corner;
        state->setting_tidy_stack=entry->data.set_options.tidy_stack;
        state->autosave=entry->data.set_options.autosave;
        state->overwrite=entry->data.set_options.overwrite;
        pin_update_iconsize(state,entry->data.set_options.iconsize);
        break;
        
      case BUFFER_SET_BACKDROP:

        if (entry->data.set_backdrop.colour!=-1) {
          state->bd_colour=entry->data.set_backdrop.colour;
          state->bg_colour=entry->data.set_backdrop.outline_colour;
        }
        if (entry->data.set_backdrop.text_colour!=-1) state->fg_colour=entry->data.set_backdrop.text_colour;

        switch (entry->data.set_backdrop.render_type) {
        case TEXT_TYPE_DEFAULT:
          state->bd_flags=(state->bd_flags &~BD_TEXT_STYLE_MASK) | BD_TEXT_THEME;
          break;
          
        case TEXT_TYPE_BLEND:
          state->bd_flags=(state->bd_flags &~BD_TEXT_STYLE_MASK) | BD_TEXT_BLEND;
          outline_init(state);
          outline_clear_cache(state);
          break;
          
        case TEXT_TYPE_OUTLINE:
          state->bd_flags=(state->bd_flags &~BD_TEXT_STYLE_MASK) | BD_TEXT_OUTLINE;
          state->bg_colour=entry->data.set_backdrop.outline_colour;
          outline_init(state);
          outline_clear_cache(state);
          break;
        }

        if (entry->data.set_backdrop.remove) {
          backdrop_load_image(state,0,NULL,1,0);
          state->bd_flags &=~ BD_IMAGE_CACHE;
        } else {
          if (entry->data.set_backdrop.path[0]) {
            backdrop_load_image(state,entry->data.set_backdrop.style,entry->data.set_backdrop.path,1,entry->data.set_backdrop.cacheJPEG);
          } else {
            backdrop_redraw_all(state);
          }
        }

        break;

      case BUFFER_SET_STICKY:
        {
          char *font,*file;
          int i;

          font=entry->data.set_sticky.data;
          file=font+1+strlen(font);

          if (*font!=0) {
            if (state->stickyfont) {
              flex_free((flex_ptr)&state->stickyfont);
              state->stickyfont=NULL;
            }
            if (!flex_alloc((flex_ptr)&state->stickyfont,1+strlen(font))) {
              msgtrans_report_error("NoMem");
            } else {
              strcpy(state->stickyfont,font);
            }
          }

          if (*file!=0) {
            if (entry->data.set_sticky.relocate) {
              // remove sticky file and update
              // delete
              if (sticky_filename) _swix(OS_File,_INR(0,1),6,sticky_filename);

              // update name
              if (!flex_extend((flex_ptr)&sticky_filename,1+strlen(file))) {
                msgtrans_report_error("NoMem");
              } else {
                strcpy(sticky_filename,file);
              }
              sticky_save(state);
            } else {
              // load new file
              if (!flex_extend((flex_ptr)&sticky_filename,1+strlen(file))) {
                msgtrans_report_error("NoMem");
              } else {
                strcpy(sticky_filename,file);

                // clear existing
                for (i=0;i<pinlist->count;i++) {
                  if (pin_is_sticky(&pinlist->info[i])) sticky_delete(state,i,1);
                }

                sticky_load(state);
              }
            }
          }

        }
        break;
        
      case BUFFER_SET_WATERMARK:
        if (entry->data.watermark.enable) {
          state->watermark_location=entry->data.watermark.location;
          state->wm_x_offset=entry->data.watermark.x_offset;
          state->wm_y_offset=entry->data.watermark.y_offset;
          watermark_load(state,entry->data.watermark.filename);
          backdrop_redraw_all(state);
        } else {
          if (state->watermark_data) {
            flex_free((flex_ptr)&state->watermark_data);
            state->watermark_data=NULL;
            state->watermark_length=0;
            backdrop_redraw_all(state);
          }
        }
        break;

      case BUFFER_CLEAR_PINBOARD:
        {
          int i;
          for (i=0;i<pinlist->count;i++) {
            if (!pin_is_sticky(&pinlist->info[i])) {
              if (pin_is_tiny(&pinlist->info[i])) {
                if (pin_is_fsfile(&pinlist->info[i])) {
                  toolbox_delete_object(0,pinlist->info[i].obj);
                  indirected_remove(pinlist->info[i].data.filename_offset);
                  pinlist->info[i].icontype=pin_type_empty;
                }
              } else {
                if (pin_is_fsfile(&pinlist->info[i])) {
                  pin_delete(state,i);
                }
              }
            }
          }
          pin_remove_empties(state);
//          backdrop_load_image(state,0,NULL,1,0);
          state->snap_to_grid=entry->data.clear_pinboard.snap_to_grid;
//          if (entry->data.clear_pinboard.iconise_to_iconbar) state->setting_iconize=ICONIZE_TO_ICONBAR; else state->setting_iconize=ICONIZE_TO_BUTTON;

          // if we were called from an obey file, then use this filename for future
          // save or autosave
          autosave_check_update();
        }
        break;

      case BUFFER_ICONSPRITES:
        pin_update_filetypeicons(state);
        hires_iconsprites(entry->data.iconsprites.filename);
        break;
        
      case BUFFER_UPCALL_INFO:
        {
          char fs[32];
          int p;
          e=_swix(OS_FSControl,_INR(0,3),33,entry->data.upcall_info.fsnumber,fs,32);
          if (e) {
            wimp_report_error(e,1,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
          } else {
            // do we know this one at all?
            options_test_flrsetup(fs,entry->data.upcall_info.path); // in case it's a settings file
            
            p=pin_locate_upcall(state,fs,entry->data.upcall_info.path,1);
            if (p!=-1) {
              // update the validation string and redraw
              pin_info_updated(state,p);
            }
          }
        }
        break;
        
      case BUFFER_UPCALL_DELETE:
        {
          char fs[32];
          int p,type;

          e=_swix(OS_FSControl,_INR(0,3),33,entry->data.upcall_info.fsnumber,fs,32);
          if (e) {
            wimp_report_error(e,1,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
          } else {
            p=pin_locate_upcall(state,fs,entry->data.upcall_delete.path,0);
            if (p!=-1) {
              // we have a reference
              _swix(OS_File,_INR(0,1)|_OUT(0),5,indirected(pinlist->info[p].data.filename_offset,char *),&type);
              if (type==0) {
                // really was deleted
                // remove the pin
                
                pin_delete(state,p);
                pin_remove_empties(state);
              }
            }
          }
        }
        break;
        
      case BUFFER_UPCALL_RENAME:
        {
          int p;
          char fs[32];
          char *newname;

          e=_swix(OS_FSControl,_INR(0,3),33,entry->data.upcall_rename.fsnumber,fs,32);
          if (e) {
            wimp_report_error(e,1,msgtrans_lookup("_TaskName"),NULL,NULL,NULL);
          } else {
            outline_clear_cache(state); // ensure we redraw the text with the new name later
            p=pin_locate_upcall(state,fs,entry->data.upcall_rename.path,0);
            if (p!=-1) {
              // is it a saved file?
              // if so, and the destination isn't still the pinboard, delete the icon
              if (upcall_make_filename(fs,1+strlen(entry->data.upcall_rename.path)+entry->data.upcall_rename.path,&newname)) {
                int del=0;

                if (pin_is_localfile(&pinlist->info[p])) {
                  if (strncmp(newname,"Pinboard:",9)!=0) del=1;
                }
                if (del) {
                  flex_free((flex_ptr)&newname);
                  pin_delete(state,p);
                  pin_remove_empties(state);
                } else {
                  // update the filename entry
                  indirected_remove((int)pinlist->info[p].data.filename_offset);
                  pinlist->info[p].data.filename_offset=(int)indirected_add_fromflex((flex_ptr)&newname,1+strlen(newname));
                  flex_free((flex_ptr)&newname);
                  pin_info_updated(state,p);
                }
              }
            } else {
              // file not known.  Is it going to end up in saved though?
              if (upcall_make_filename(fs,1+strlen(entry->data.upcall_rename.path)+entry->data.upcall_rename.path,&newname)) {
                if (strncmp(newname,"Pinboard:",9)==0) {
                  // yes - make an icon
                  pin_locate_upcall(state,fs,1+strlen(entry->data.upcall_rename.path)+entry->data.upcall_rename.path,1);
                }
                flex_free((flex_ptr)&newname);
              }
            }

          }
        }
      }
      buffer_remove(entry);
    }
  } while (e==NULL && entry!=NULL);
  
  return 1;
}

static int autosave_confirm(void)
{
  _kernel_oserror e;
  e.errnum=256;
  strcpy(e.errmess,msgtrans_lookup("AutoSave"));
  return wimp_report_error(&e,3 | (1<<8) | (4<<9),msgtrans_lookup("_TaskName"),0,0,0)==1;
}

int main(void)
{
  WimpPollBlock pollblock;
  int quit=0,event_code;
  _backdrop_state *state;

  debugf("\n");

  filer_get_version();

  ensure_toolbox();

  app_init();
  
  event_register_message_handler(Wimp_MQuit,quit_message,&quit);
  
  state=backdrop_init();

  indirected_init();
  pins_init();
  sticky_init(state);
  iconize_init(state);
  tinydir_init(state);
  
  sticky_load(state); // restore sticky state
  
  // get Filer's task handle (we can do this via Service Calls if we start before the Filer/Desktop)
  if (filer_taskhandle==0) filer_taskhandle=filer_get_taskhandle();  
  
  event_register_message_handler(Wimp_MDataSave,message_datasave,state);
  event_register_wimp_handler(-1,Wimp_EPollWordNonZero,event_pollword,state);
  event_register_message_handler(Wimp_MFontChanged,message_fontchanged,state);
  event_register_message_handler(Wimp_MHelpRequest,message_help,state);
  event_register_message_handler(Wimp_MToggleBackdrop,message_togglebd,state);
  localfiles_import(state);

  while (!quit) {
    event_poll(&event_code,&pollblock,&pollword);

    // reset filer column
    if (event_code!=18) {
      if (filer_base_column!=-1) {
        unsigned int old_mask;
        filer_base_column=-1;
        filer_base_row=-1;
        event_get_mask(&old_mask);
        event_set_mask(old_mask|1);
      }
    }
  }

  switch (state->autosave) {
    case AUTOSAVE_NONE:
      break;

    case AUTOSAVE_CONFIRM:
      if (autosave_confirm()) {
        char filename[256];
        if (saveas_get_file_name(0,saveas_win,filename,256,NULL)==NULL)
          pinboard_save(state,filename);
      }
      break;

    case AUTOSAVE_AUTO:
      {
        char filename[256];
        if (saveas_get_file_name(0,saveas_win,filename,256,NULL)==NULL)
          pinboard_save(state,filename);
      }
      break;
  }
  
  pin_restore_all_windows(state);
  
  event_finalise();
  
  pinfilter_stop(taskhandle);
  
  taskhandle=0;

  flex_da=0;

  return 0;
}
