/*
 * DLibrary, an example program using the Acorn Toolbox and TPlusLib.
 * (c) Tony Howat / 20-20 Software, and RISC User 1997
 *
 * Version : 1.01
 *
 */

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

#include "event.h"
#include "toolbox.h"
#include "gadgets.h"
#include "wimplib.h"
#include "window.h"
#include "proginfo.h"
#include "fileinfo.h"
#include "saveas.h"
#include "menu.h"
#include "TPlusLib:drawwin.h"
#include "TPlusLib:flex.h"
#include "tpluslib:tplus.h"
#include "tpluslib:panes.h"
#include "tpluslib:alarm.h"
#include "addwin.h"

/* user messages */
#define MAIN_HIDDEN          1
#define MAIN_OPEN            2
#define MENU_QUIT            3
#define MENU_DELETE          4
#define DISPLAY_MENU_OPENING 5

/* components in main window */
#define CDESCRIPTION  4
#define CFILENAME     2
#define CTIME         6
#define CLAST         2
#define CNEXT         1
#define CNUMBER       0

/* components in display menu */
#define CDABOUT       3
#define CDSAVE        0
#define CDDELETE      2

/* raises any error x */
#define are(x) if(x) { raise_error(x); exit(0); }

#define FID   77        /* number of files to put in a directory */
#define FIDSQ (FID*FID) /* number of files in a directory squared */

#define VERSION "1.01"

/* structure for each record in the catalogue file */
typedef struct {
  char filename[255];
  char description[255];
  int time[2];
  int  number;
} catentry;

MessagesFD mfd; /* messages file */

static int message_list [] = {0};
static int event_list   [] = {0};
static IdBlock       id_blk;
static WimpPollBlock poll_block;

/* draw_length = length of currently loaded draw file
 * drawdata    = pointer to block of memory containing currently
 *               loaded draw file
 * cat_entries = number of drawings currently in the catalogue
 * current_pic = the number of the picture in the catalogue currently
 *               being displayed
 * pane, mainw, ctrlwin
 *             = object ids of the pane window (ie the one with the
 *               drawfile in it, the main window (the host window for
 *               the pane) and the control toolbar.
 */
static int draw_length=0,current_pic=-1,*drawdata=NULL, cat_entries=0;
static ObjectId pane=NULL, mainw=NULL, ctrlwin=NULL;
static catentry *cat=NULL;

/* -- find the size of a file using OS_File 17 ---
 * -- returns 0 if a file doesn't exist        --- */

int file_size(char *file)
{
  _kernel_swi_regs r;
  _kernel_oserror *e;

  r.r[0] = 17;
  r.r[1] = (int)file;
  if((e=_kernel_swi(OS_File, &r, &r))!=NULL) {
    raise_error(e);
    return 0;
  }

  if(r.r[0]==0) /* file doesn't exist */
    return 0;
  else
    return r.r[4]; /* return length */
}

/* --- loads the catalogue file into memory --- */

void cat_load(void)
{
  int cat_length;
  _kernel_swi_regs r;

  /* allocate memory for and load in the catalogue file */
  cat_length=file_size("<DLibrary$Dir>.Catalogue");
  if(flex_alloc((flex_ptr)&cat, cat_length) == FALSE) {
    werr(TRUE, msgs_lookup("faca:No room, failed to allocate %iK for catalogue area"),cat_length/1024);
    return;
  }

  /* load it in using OS_File 16 */
  r.r[0] = 16;
  r.r[1] = (int)"<DLibrary$Dir>.Catalogue";
  r.r[2] = (int)cat;
  r.r[3] = 0;
  if(_kernel_swi(OS_File, &r, &r)) {
    flex_free((flex_ptr)&cat);
    return;
  }

  /* update cat_entries with the number of entries in the newly loaded
   * catalogue */
  cat_entries=cat_length/sizeof(catentry);
  /* if there are any entries in the newly loaded catalogue we should
   * start at the first one, otherwise we can go to the default picture
   */
  if(cat_entries)
    current_pic=1;
}

/* --- save the catalogue file  --- */

void cat_save(void)
{
  _kernel_swi_regs r;
  _kernel_oserror *e;

  /* save using os_file 10 */
  r.r[0] = 10;
  r.r[1] = (int)"<DLibrary$Dir>.Catalogue";
  r.r[2] = 0xffd; /* data */
  r.r[4] = (int)cat;  /* start address */
  r.r[5] = (int)cat+flex_size((flex_ptr)&cat); /* end address */
  if((e=_kernel_swi(OS_File, &r, &r))!=NULL) {
    raise_error(e);
    return;
  }
}

/* --- check that a directory exists, if it doesn't create it. if ---
 * --- something gets in the way complain.                        --- */

int checkdirexists(char *path)
{
  _kernel_swi_regs r;
  _kernel_oserror *e;

  /* check to see if something of this name already exists */
  r.r[0] = 13;
  r.r[1] = (int)path;
  if((e=_kernel_swi(OS_File, &r, &r))!=NULL) {
    raise_error(e);
    return 1;
  }

  switch(r.r[0]) 
  {
    case 0: /* no directory of that name has been found, create it */
    {
      _kernel_swi_regs r;
      _kernel_oserror *e;
    
      r.r[0] = 8;
      r.r[1] = (int)path;
      r.r[4] = 0;
      if((e=_kernel_swi(OS_File, &r, &r))!=NULL) {
        raise_error(e);
        return 1;
      }
      return 0;
    }
    case 2:  /* existing directory found, so use it */
      return 0;
    default: /* image or file found in place, complain */
      werr(FALSE,msgs_lookup("nmfda:%s is a file or image which should not be there"),path);
      return 1;
  }
}


/* --- Good-bye! --- */

int wimp_quit (WimpMessage *message, void *h) {
  /* nothing to tidy up */
  exit(0);
  return(0); /* Keep the compiler happy */
}

/* --- Click on "Quit" in the menu --- */

int menu_quit (int event_code, ToolboxEvent *event_block,
                   IdBlock *id_blk, void *h)
{
  /* nothing to tidy up */
  exit(0);
  return(0); /* Keep the compiler happy */
}

/* --- our main window is hidden free up diagram memory --- */

int main_hidden (int event_code, ToolboxEvent *event_block,
                   IdBlock *id_blk, void *h)
{
  flex_free((flex_ptr)&drawdata); /* free up flex memory for diagram */
  drawwin_deregister(pane);       /* deregister pane from drawwin */
  drawdata=NULL; /* clear old drawdata pointer */
  draw_length=0; /* we have no drawing loaded, so length = 0 */
  mainw=NULL;
  return 0;
}

/* --- load and show a new picture --- */
void update_pic_display(void)
{
  _kernel_swi_regs r;
  char file[80]="<DLibrary$Dir>.Picture";
  char timestr[80];

  /* generate a pathname from the number */
  if(current_pic!=-1)
    sprintf(file,"<DLibrary$Dir>.Files.%x.%x.%x",
                 cat[current_pic-1].number/FIDSQ,
                 (cat[current_pic-1].number%FIDSQ)/FID,
                 cat[current_pic-1].number);
 
  /* get rid of previous drawing (if it was loaded at all) */
  if(drawdata!=NULL)
  {
    flex_free((flex_ptr)&drawdata);
    drawwin_deregister(pane);
  }

  /* allocate memory for and load in the drawing */
  draw_length=file_size(file);
  if(flex_alloc((flex_ptr)&drawdata, draw_length) == FALSE) {
    werr(0, msgs_lookup("nmfda:No room, failed to allocate %iK for draw area"),draw_length/1024);
    return;
  }

  r.r[0] = 16;
  r.r[1] = (int)file;
  r.r[2] = (int)drawdata;
  r.r[3] = 0;
  if(_kernel_swi(OS_File, &r, &r)) {
    flex_free((flex_ptr)&drawdata);
    return;
  }

  /* register the drawing to our pane window */
  drawwin_register(DRAWWIN_SCALE_PIC_BESTFIT,pane,(void **)&drawdata,draw_length,95);

  /* update the icons in the main window */
  if(current_pic!=-1)
  {
    /* convert risc-os 5 byte time into text */
    r.r[0]=(int)cat[current_pic-1].time;
    r.r[1]=(int)timestr;
    r.r[2]=80;
    _kernel_swi(OS_ConvertStandardDateAndTime, &r, &r);
    displayfield_set_value(0,mainw,CFILENAME,cat[current_pic-1].filename);
    displayfield_set_value(0,mainw,CTIME,timestr);
    displayfield_set_value(0,mainw,CDESCRIPTION,cat[current_pic-1].description);
  } else {
    displayfield_set_value(0,mainw,CFILENAME,msgs_lookup("none:None"));
    displayfield_set_value(0,mainw,CTIME,"");
    displayfield_set_value(0,mainw,CDESCRIPTION,"");
  }

}

/* --- fill in the current picture etc on the toolbar and grey out ---
 * --- the arrow gadgets if necessary                              --- */
void update_controls(void)
{
   char buffer[40];

   if(cat_entries>0)
     sprintf(buffer,msgs_lookup("nofn:%i of %i"),current_pic,cat_entries);
   else
     sprintf(buffer,msgs_lookup("empty:Empty"));

   fade_gadget(ctrlwin,CLAST,(cat_entries==0 || current_pic==1));
   fade_gadget(ctrlwin,CNEXT,(cat_entries==0 || current_pic==cat_entries));
   displayfield_set_value(0,ctrlwin,CNUMBER,buffer);
}


/* --- our main window is hidden free up diagram memory --- */

int change_drawing (int event_code, ToolboxEvent *event_block,
                    IdBlock *id_blk, void *h)
{
  AdjusterClickedEvent *ace=(AdjusterClickedEvent *)event_block;

  if(ace->direction)
   ++current_pic;
  else
   --current_pic;

  update_pic_display();
  update_controls();

  return 0;
}

/* --- add a drawfile (loaded into memory, pointed to by *data and ---
 * --- of length length(!), with the source filename and           ---
 * --- description supplied.                                       --- */

void lib_add_file(void *data, int length, char *file, char *desc)
{
  char pathname[255];
  FILE *out;
  int num=0, size;
  _kernel_swi_regs r;

  /* find a number to use - ie the largest one currently in use +1 */
  if(cat_entries>0)
  {
    int n=0;

    while(n<cat_entries)
    {
      if(cat[n].number>=num)
        num=cat[n].number+1;
      ++n;
    }
  }

  sprintf(pathname,"<DLibrary$Dir>.Files.%x",num/FIDSQ);
  if(checkdirexists(pathname))
  {
    werr(FALSE,msgs_lookup("dne:Drawing not added."),pathname,file);
    return;
  }
  sprintf(pathname,"<DLibrary$Dir>.Files.%x.%x",num/FIDSQ,(num%FIDSQ)/FID);
  if(checkdirexists(pathname))
  {
    werr(FALSE,msgs_lookup("dne:Drawing not added."),pathname,file);
    return;
  }

  sprintf(pathname,"<DLibrary$Dir>.Files.%x.%x.%x",num/FIDSQ,
          (num%FIDSQ)/FID,num);
  if((out=fopen(pathname,"wb"))==NULL)
  {
    werr(FALSE,msgs_lookup("fowc:Failed to open \"%s\" whilst copying \"%s\" to the library, drawing not added."),pathname,file);
    return;
  }

  fwrite(data,length,1,out); /* write out the draw data */
  fclose(out);

  /* --- add the drawing to the catalogue --- */

  if(cat==NULL) /* null pointer indicates no memory has been allocated */
  {
    if(flex_alloc((flex_ptr)&cat, sizeof(catentry)) == FALSE)
      werr(TRUE,msgs_lookup("famfe:Failed to allocate memory for entry"));
    size=sizeof(catentry);
  } else {
    /* how big should our flex block be to include this? */
    size=flex_size((flex_ptr)&cat)+sizeof(catentry);
    /* resize it */
    if(flex_extend((flex_ptr)&cat,size)==0) {
      werr(FALSE,msgs_lookup("cneca:Could not extend catalogue area, drawing not added."));
      return;
    }
  }

  /* clear the memory first */
  memset(&cat[cat_entries],'\0',sizeof(catentry));
  strcpy(cat[cat_entries].filename,file);
  strcpy(cat[cat_entries].description,desc);
  cat[cat_entries].number=num;

  /* fill in the time */
  *cat[cat_entries].time=3; /* 3 is needed in the block pointed to by
                             * R1 because we are calling OS_Word 14,3 -
                             * see the PRM for details */
  r.r[0] = 14;
  r.r[1] = (int)cat[cat_entries].time;
  _kernel_swi(OS_Word, &r, &r);

  ++cat_entries; /* we've now got one more drawing available */
  cat_save(); /* make sure catalogue on disc is up to date */

  if(cat_entries==1)
  {
    current_pic=1;
    update_pic_display();
  }

  update_controls();
}


/* --- called when main window should be opened --- */

int main_open (int event_code, ToolboxEvent *event_block,
               IdBlock *id_blk, void *h)
{
  /* if the window is already open bring it to the front */
  if(mainw) {
    window_front(mainw); /* tpluslib call */
    window_front(pane);  /* the pane window should still be on top */
    return 0;
  }

  /* create our main and pane windows, giving us ObjectIds for each */
  are(toolbox_create_object(0,"main",&mainw));
  are(toolbox_create_object(0,"pane",&pane));
  are(toolbox_create_object(0,"Toolbar",&ctrlwin));
  event_register_toolbox_handler(ctrlwin, Adjuster_Clicked, change_drawing, NULL);
  /* attach our toolbar */
  window_set_tool_bars(15,mainw,0,0,ctrlwin,0);
  /* open the main window */
  are(window_open_centre(0,mainw,0,0));
  toolbox_show_object(0,ctrlwin,0,0,mainw,0);
  /* finally register the pane with our library, it will be opened for
   * us */
  are(pane_register(mainw,0x1,pane));
  /* load the picture and display it, and update the controls */
  update_pic_display();
  update_controls();
  return 0;
}

/* --- the toolbox wants us to report an error --- */

int error_handler (int event_code, ToolboxEvent *event_block,
                   IdBlock *id_blk, void *h)
{
  raise_error ((_kernel_oserror *)event_block->data.bytes);
  return 1;
}

/* --- a file has been dragged somewhere --- */

int file_loader(WimpMessage *message, void *handle)
{
  WimpMessage msg;
  int winh,paneh;

  window_get_wimp_handle(0, mainw, &winh);
  window_get_wimp_handle(0, pane, &paneh);

  if (message->data.data_load.destination_window==winh ||
      message->data.data_load.destination_window==paneh)
  {
     /* check drag is draw file */
     if(message->data.data_load.file_type == 0xaff)
       add_open(message->data.data_load.leaf_name);
     else {
       werr(FALSE,msgs_lookup("icoudf:I can only use draw files"));
       return 0;
     }
  } else {
    return 0;
  }

  /* ack message */
  msg = *message;
  msg.hdr.your_ref = msg.hdr.my_ref;

  if (message->hdr.action_code == Wimp_MDataLoad)
      msg.hdr.action_code = Wimp_MDataLoadAck;
  wimp_send_message(Wimp_EUserMessage, &msg, msg.hdr.sender,0,0);

  return 1;
}

/* --- remove the catalogue entry and draw file for the currently ---
 * --- loaded file                                                --- */

int delete_drawing(int event_code, ToolboxEvent  *event, IdBlock *id_block,
                   void  *handle)
{
  char file[80]="";

  if(current_pic==-1)
  {
    werr(FALSE,msgs_lookup("lie:You can't delete this drawing,"
                           " the library is empty"));
    return 1;
  }

  sprintf(file,"<DLibrary$Dir>.Files.%x.%x.%x",
               cat[current_pic-1].number/FIDSQ,
               (cat[current_pic-1].number%FIDSQ)/FID,
               cat[current_pic-1].number);

  remove(file);
  /* shrink the flex block to get rid of deleted entry */
  flex_midextend((flex_ptr)&cat,(current_pic)*sizeof(catentry),
                 -sizeof(catentry));

  cat_save(); /* make sure catalogue on disc is up to date */

  --cat_entries;

  /* if the library is now empty we should go back to our default
   * drawing */
  if(current_pic==1 && cat_entries==0)
    current_pic=-1;
  /* if we were displaying the last picture in the library, and it
   * has just been deleted, go to the one before it */
  if(current_pic>cat_entries)
    current_pic=cat_entries;

  update_pic_display();
  update_controls();
  return 1;
}

/* just fill in the version number when our program info box is displayed */

int proginfo_show(int event_code, ToolboxEvent  *event, IdBlock *id_block,
                   void  *handle)
{
  proginfo_set_version(0,id_block->self_id,"V" VERSION " ("__DATE__")");
  return 1;
}

/* fill in the fileinfo dialogue */

int fileinfo_show(int event_code, ToolboxEvent  *event, IdBlock *id_block,
                   void  *handle)
{
  fileinfo_set_modified(0,id_block->self_id,0);
  fileinfo_set_file_name(0,id_block->self_id,cat[current_pic-1].filename);
  fileinfo_set_file_size(0,id_block->self_id,draw_length);
  fileinfo_set_date(0,id_block->self_id,cat[current_pic-1].time);
  return 1;
}

/* return the filename of a risc-os pathname */
static char *getleafname(char *pnpath)
{
  char *pnlast;

  while (*pnpath != '\0') {
    ++pnpath;
    if (*pnpath == '.')
      pnlast = pnpath;
  }

  return pnlast+1;
}

/* set up the saveas dialogue and point it at the correct area of RAM */

int saveas_show(int event_code, ToolboxEvent  *event, IdBlock *id_block,
                   void  *handle)
{
  saveas_set_file_name(0,id_block->self_id,
                       getleafname(cat[current_pic-1].filename));
  saveas_set_file_type(0,id_block->self_id,0xaff);
  saveas_set_file_size(0,id_block->self_id,draw_length);
  saveas_set_data_address(0,id_block->self_id,drawdata,draw_length,
                          drawdata,draw_length);
  return 1;
}

/* fade or unfade the display menu items depending on if there are
 * actually any pictures in the library */

int menu_show(int event_code, ToolboxEvent  *event, IdBlock *id_block,
                   void  *handle)
{
  menu_set_fade(0,id_block->self_id,CDABOUT,(current_pic==-1));
  menu_set_fade(0,id_block->self_id,CDSAVE,(current_pic==-1));
  menu_set_fade(0,id_block->self_id,CDDELETE,(current_pic==-1));
  return 1;
}

/* --- Initialise toolbox resources --- */
static void initialise (void)
{
  _kernel_oserror *e;

  if ((e = toolbox_initialise (0, 310, message_list, event_list,
       "<DLibrary$Dir>", &mfd, &id_blk, NULL, NULL, NULL)) != NULL) {
     raise_error (e);
     exit (1); /* Fatal */
  }

  event_initialise (&id_blk); /* this leaves us our id */

  /* initialise the flex memory manager */
  flex_init();

  /* initialise pane library (actually just initialises alarm) */
  pane_init();

  /* register our handlers for custom toolbox events */
  event_register_toolbox_handler(-1, MAIN_HIDDEN, main_hidden, NULL);
  event_register_toolbox_handler(-1, MAIN_OPEN, main_open, NULL);
  event_register_toolbox_handler(-1, MENU_QUIT, menu_quit, NULL);
  event_register_toolbox_handler(-1, MENU_DELETE, delete_drawing, NULL);

  /* pick up Wimp_MDataLoad events so we can detect file drags to
   * the main window */
  event_register_message_handler(Wimp_MDataLoad,file_loader,0);

  /* these two are self explanatory */
  event_register_toolbox_handler(-1, Toolbox_Error, error_handler, NULL); 
  event_register_message_handler(Wimp_MQuit, wimp_quit, NULL);

  /* register a handler so we can fill in the version in the proginfo box
   * before it is shown */
  event_register_toolbox_handler(-1, ProgInfo_AboutToBeShown,proginfo_show,0);

  /* register a handler so we can fill the fileinfo box before it is
   * shown */
  event_register_toolbox_handler(-1, FileInfo_AboutToBeShown,fileinfo_show,0);

  /* register a handler to update the pointers to the data for the saveas
   * whenever the saveas window is displayed */
  event_register_toolbox_handler(-1, SaveAs_AboutToBeShown,saveas_show,0);

  /* register a handler so we can grey out the options of the display
   * menu when they are not needed */
  event_register_toolbox_handler(-1, DISPLAY_MENU_OPENING,menu_show,0);
}


/* --- main routine --- */

int main (void)
{
   int event_code;

   initialise();
   cat_load();

   /* pane library uses alarm, so we need to use alarm's poll code */
   while (1)
     alarm_event_poll(&event_code, &poll_block, NULL);
}
