/* This is the source code for CastAWote VoteModule v1.19.

   Last updated : Monday 11th November 1996 @ 17:10h.
   Last compiled: Saturday 20th December 1997 @ 19:40h.

   Recompiled   : Saturday 14th March 2009 @ 14:40h for CastA*W*ote

*/



/* Include the various things... */

#include <string.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include "swis.h"
#include "kernel.h"
/* #include "bbc.h" */



/* Define the definitions. Sorry, I couldn't be bothered to
   speak a language known to normal homo-sapiens. */
#define  VOTEMODULE           "VoteModule v1.19"
#define  UNKNOWN_CMD_SERVICE  4
#define  UNUSED(x)            (x = x)
#define  FALSE                0
#define  TRUE                 1

extern _kernel_oserror *votemodule_sys(int, _kernel_swi_regs *, void *);
extern _kernel_oserror *votemodule_commands(char *, int, int, void *);
extern int votemodule_initialise(char *, int, void *);



/* Define the variables in use... */

struct votebuffer {
   char creator[61];
   char system[61];
   int  voted_for;
   char question[61];
   char software_opts[61];
   char addinfo_one[61];
   char addinfo_two[61];
   char addinfo_three[61];
   char option_one_text[61];
   int  option_one_rslt;
   char option_two_text[61];
   int  option_two_rslt;
   char option_three_text[61];
   int  option_three_rslt;
   char option_four_text[61];
   int  option_four_rslt;
   char option_five_text[61];
   int  option_five_rslt;
   char option_six_text[61];
   int  option_six_rslt;
   char option_seven_text[61];
   int  option_seven_rslt;
   char option_eight_text[61];
   int  option_eight_rslt;
   char reserved_data_block[24];
   char vote_is_local[1];
   char vote_is_hidden[1];
   char vote_is_inaccessible[1];
   char vote_is_locked[1];
   char vote_flags_other[12];
   int  vote_data_crc;
   char vote_data_key[1];
   char reserved_developer[1];
   char reserved_user[1];
   char vote_datafile_type[1];
};

struct votehdr {
   char creator[61];
   char question[61];
   int  qnum;
};

typedef struct votebuffer votebuffer;
static struct votebuffer *votedata = NULL;
static struct votebuffer votecurrent;
static struct votehdr voteheader;

static int  autopilot = 0;
static int  maxvotes = 220;
static int  questions = 255;
static int  currvote = 0;
static int  data_not_saved = FALSE;
static int  junk_v = 0;
static char junk_s[255];
static char filename[256];
static int  returncode = 0;
static char *default_filename="<CastAWoteA$Dir>.Data.Data";
static char *memdead="Memory allocation failure";
static char *filedead="CastAWote data file not found";
static char regname[21];
static char regnum[11];
static int  fatalerror = FALSE;



/* Define procedures */
static void votemodule_cli_statistics(void);
static void votemodule_cli_extent(void);
static int  votemodule_loadvotes(_kernel_swi_regs *r);
static int  votemodule_savevotes(char *);
static int  votemodule_refresh(void);
static int  votemodule_getvote(_kernel_swi_regs *r);
static int  votemodule_putvote(_kernel_swi_regs *r);
static int  votemodule_addvoteopt(_kernel_swi_regs *r);
static void votemodule_voteop(_kernel_swi_regs *r);
static int  votemodule_amendvote(_kernel_swi_regs *r);
static int  votemodule_getvoteheader(_kernel_swi_regs *r);
static void vote_options_from_string(int currvote);
static void vote_options_to_string(int thisvote);
static int  votemodule_deletevote(int);
static int  votemodule_findvote(char *junk_s);
static void votemodule_registered(_kernel_swi_regs *r);
static void votemodule_getheapentry(_kernel_swi_regs *r);
static void votemodule_saveheapentry(_kernel_swi_regs *r);
static int votemodule_heapentryaction(int heapentry, int action);
static void lowercase(char []);
static char *blankit(char input[], int length);
static void savequestion(FILE* fp,int currvote);
static void flush_buffer(FILE* fp);
static void fill_buffer(FILE* fp);
static void read_string(FILE *file, char *string);
static int  read_word(FILE *file);
static void write_string(FILE *file, char *string);
static void write_word(FILE *file, int num);




/* Okay, this one handles the SYS commands. */
_kernel_oserror *votemodule_sys(int swi_no, _kernel_swi_regs *r, void *private_word)
{
   UNUSED(private_word);  /* Registers unaltered */

   switch(swi_no)
   {
      case 0:  /* This calls the function to deal with SYS "VoteModule_LoadVotes". */
         {
            junk_v = (int)r->r[0];
            sprintf(filename,"%s",(char*)r->r[1]);
            votemodule_loadvotes(r);
            break;
         }

      case 1:  /* This calls the function to deal with SYS "VoteModule_SaveVotes". */
         {
            char *savefile = (char*)r->r[1];
            if (!savefile)
            {
               r->r[0] = 3; /* Request failed, no filename */
            }
            else
            {
               r->r[0] = votemodule_savevotes(savefile);
            }
            break;
         }

      case 2:  /* This calls the function to deal with SYS "VoteModule_Refresh". */
      	 {
            r->r[0] = votemodule_refresh();
            break;
         }

      case 3:  /* This calls the function to deal with SYS "VoteModule_GetVote". */
         {
            votemodule_getvote(r);
            break;
         }

      case 4:  /* This calls the function to deal with SYS "VoteModule_PutVote". */
         {
            r->r[0] = votemodule_putvote(r);
            break;
         }

      case 5:  /* This calls the function to deal with SYS "VoteModule_AddVoteOpt". */
         {
            r->r[0] = votemodule_addvoteopt(r);
            break;
         }

      case 6:  /* This calls the function to deal with SYS "VoteModule_GetVotesCount". */
         /* Simple, do it here and save space setting up 'fancy' routines. */
	 {
            r->r[0] = questions;              /* Number of questions in database */
            r->r[1] = maxvotes;               /* Hardcoded to 220 maximum */
            r->r[8] = autopilot;              /* The 'backdoor' entry, changes each run */
            break;
         }

      case 7:  /* This calls the function to deal with SYS "VoteModule_Statistics". */
         /* Do this one 'on the fly' too... */
	 {
            r->r[0] = questions;                   /* Number of questions in database */
            r->r[1] = sizeof(votebuffer)*maxvotes; /* Memory reserved for database */
            r->r[2] = (int)&default_filename;      /* Pointer to 'default' filename */
            r->r[3] = data_not_saved;              /* Unsaved data present? */
            r->r[4] = 1;                           /* Module version, #.xx */
            r->r[5] = 18;                          /* Module version, x.##  */
            r->r[6] = (int)votedata;               /* Pointer to start of memory block */
            break;
         }

      case 8:  /* This calls the function to deal with SYS "VoteModule_VoteOp". */
         votemodule_voteop(r);
         break;

      case 9:  /* This calls the function to deal with SYS "VoteModule_AmendVote". */
         {
            r->r[0] = votemodule_amendvote(r);
            break;
         }

      case 10: /* This calls the function to deal with SYS "VoteModule_GetVoteHeader". */
         {
            votemodule_getvoteheader(r);
            break;
         }

      default: /* VoteUtils claims a chunk allowing up to 64 SYS calls to be
                  defined. If the call made is within VoteUtils' chunk, but
                  unused, return an error - otherwise assume it's okay.     */
         {
            printf("VoteModule SYS \"???\" call (number 0x16F%2X) is unknown...",swi_no);
            break;
         }
   }
   return 0;
}



/* This part deals with the OSCLI commands. */
_kernel_oserror *votemodule_commands(char *arg_string, int argc, int cmd_no, void *pw)
{
   UNUSED(pw);
   UNUSED(argc);
   UNUSED(arg_string);
   switch(cmd_no)
   {
      case 0 : votemodule_cli_statistics();
               break;
      case 1 : junk_v = 0;
               junk_v = votemodule_refresh();
               if (junk_v == 1)
                  printf("Refreshed...\n");
               if (junk_v == 2)
                  printf("Refresh failed...\n");
               break;
      case 2 : votemodule_cli_extent();
               break;
      return 0;
   }
   return 0;
}



/* Initialisation stuff. */

static void exitfunc(void)
{
   /* Prevent memory leaks... :-) */
   if (votedata)
   {
      free(votedata);
      votedata = NULL;
   }
}



static int fetchrnd(int v)
{
   /* Behaves much like BASICs RND. Returns a random number between 0 and 'v' specified. */
   return (int)(((float)rand()/(float)RAND_MAX)*v)+1;
}



int votemodule_initialise(char *cmd_tail, int podule_base, void *private_word)
{
   /* This is called whenever the module is loaded or run. */
   FILE *fp;
   int  filelen=0;

   UNUSED(cmd_tail);
   UNUSED(podule_base);
   UNUSED(private_word);
   votedata = calloc(maxvotes, sizeof(struct votebuffer));
   if (!votedata)
   {
      fp = 0;
      junk_v = sizeof(struct votebuffer)*maxvotes;
      junk_v = junk_v + 8192;
      printf("\a       !!! VOTEMODULE INITIALISATION FAILURE !!!\n\n");
      printf("       VoteModule requires %d bytes of memory.\n\n",junk_v);
      printf("       Please free up some memory and try again...\n");
      return (int)&memdead;
   }
   atexit(exitfunc);
   autopilot = fetchrnd(255);
   fp = fopen(default_filename,"rb");
   if (!fp)
   {
      printf("\aCan't open '<CastAWoteA$Dir>.Data.Data'.\n");
      printf("Has CastAWote been 'seen' by the filer?\n");
      return (int)&filedead;
   }

   while(!feof(fp) || filelen<60)
   {
      junk_v = fgetc(fp); /* Check if file is at least 60 bytes long */
      filelen++;
   }
   rewind(fp);

   if (filelen>58)
   {
      fill_buffer(fp);
   } else
   {
      questions=0; /* If too short (ie: 0 bytes) then assume blank file. */
   }

   fclose(fp);

   /* >>> REGISTRATION: */
   regname[0]  = 132;
   regname[1]  = 155;
   regname[2]  = 154;
   regname[3]  = 137;
   regname[4]  = 154;
   regname[5]  = 147;
   regname[6]  = 144;
   regname[7]  = 143;
   regname[8]  = 146;
   regname[9]  = 154;
   regname[10] = 145;
   regname[11] = 139;
   regname[12] = 130;
   regname[13] = 0;
   regname[14] = 0;
   regname[15] = 0;
   regname[16] = 0;
   regname[17] = 0;
   regname[18] = 0;
   regname[19] = 0;

   regnum[0] = 207;
   regnum[1] = 207;
   regnum[2] = 207;
   regnum[3] = 207;
   regnum[4] = 207;
   regnum[5] = 207;
   regnum[6] = 207;
   regnum[7] = 207;
   regnum[8] = 207;
   regnum[9] = 207;

   return 0;
}



int main(int argc, char *argv[])
{
   /* The main routine. Called when *RMRun only... */
   FILE *fp;
   UNUSED(argc);
   UNUSED(argv);
   printf("HeyRick CastAWote VoteModule version 1.19\n");                /*  */
   printf("         1997 [20th December] Rick Murray.\n\n");
   printf("Contact 'heyrick1973@yahoo.co.uk' for help.\n\n");
   printf("Loading...");
   fp = fopen(default_filename,"rb");
   if (!fp)
   {
      printf("\a\nCan't open '<CastAWoteA$Dir>.Data.Data'.\n");
      printf("Has CastAWote been 'seen' by the filer?\n");
      return 0;
   }
   fill_buffer(fp);
   fclose(fp);
   printf("Loaded %d questions...Done!\n",questions);
   /*os_swi1(Wimp_CommandWindow,-1);*/
   return 0;
}



/* *command routines */

static void votemodule_cli_extent(void)

{
   /* This provides the 'extent' display. This now only shows the amount of
      memory used by the votes buffer. The other variables are too hit'n'miss
      to be properly accounted... For example, many of the "FILE *fp"s are valid
      only in that current routine.
   */

   int temp_space;

   printf("This is the amount of memory required to hold the votes data...\n");
   temp_space = sizeof(votebuffer)*maxvotes;
   printf("Size of 'votedata'             : %d bytes\n\n",temp_space);
   printf("This is the amount of memory required, for the same, in BASIC...\n");
   temp_space=256*15;          /* 15 strings, 256 characters per string */
   temp_space+=4*9;            /* 9 integers, 4 bytes per integer */
   temp_space=temp_space*220;  /* Multiplied by number of votes */
   printf("   Strings : Take 256 bytes per string, 15 per question.\n");
   printf("   Integers: Take 4 bytes per integer, 9 per question.\n");
   printf("   In all  : Multiply the above by 220 votes.\n");
   printf("Size of 'votedata' in BASIC    : %d bytes (!)\n\n",temp_space);
}



static void votemodule_cli_statistics(void)
{
   /* This outputs a basic statistics display. */
   printf("\nHeyRick VoteModule version 1.19 ; to support CastAWote.\n\n\n"); /*  */
   printf("VOTES DATABASE LOADED        : ");
   if (questions == 255)
   {
      printf("No.\n");
   }
   else
   {
      printf("Yes.\n");
   }
   printf("NUMBER OF VOTES IN DATABASE  : ");
   if (questions == 255)
   {
      printf("None - not loaded!\n");
   }
   else
   {
      printf("%d.\n",questions);
   }
   printf("FILE FORMAT                  : ");
   if (returncode == 123)
   {
      printf("Enhanced.\n\n");
      /* Future structure additions to be defined */
   }
   else
   {
      printf("Normal.\n\n");
      /* So 'hardwire' the normal type */
   }
   printf("UNSAVED DATA?                : ");
   if (data_not_saved == TRUE)
   {
      printf("Yes.\n");
   }
   else
   {
      printf("No.\n");
   }
   printf("VIRTUAL MEMORY               : Not supported in this version...\n");
   printf("                               Use \"VoteMod_VM\".\n");
   printf("MODULE FULLY OPERATIONAL?    : ");
   if (returncode == 123)
   {
      printf("Yes.\n\n");
      /* Not this one! */
   }
   else
   {
      printf("No.\n\n");
      /* This option. */
   }
   printf("Coded by                     : Richard Murray\n");
   printf("With the assistance of       : Richard Sargeant, Keith Hall,\n");
   printf("                               Rick Decker and Robin Abecasis.\n");
   printf("Notable testers and thanks   : John Stonier, Steve Pursey, Robin\n");
   printf("                               Abecasis, Keith Hall, Dane Koekoek,\n");
   printf("                               DaviD Dade/Dave Coleman and Chris\n");
   printf("                               Jackson.\n\n");
}



/* SWI routines */

static int votemodule_loadvotes(_kernel_swi_regs *r)
{
   /* >> SYS "VoteModule_LoadVotes" << (&16F00).

      On entry:
          R0 =
          R1 = Pointer to filename.

        On exit:
          R0 = 1 if loaded okay
               2 if load failed because unsaved data
               3 if load failed because file can't be opened
               4 if load failed because data corrupted (set by string loader)
               5 if load failed because data corrupted (set by word loader)

        This call will instruct VoteModule to load/reload the votes database.
        You can specify a filename. If none is given, "<CastAWote$Dir>.Data.Data"
        is used.

        There is a number you can pass in R0 to force a load even if unsaved data
        buffered. This is undocumented.
   */

   FILE *fp;
   int  filelen=0;

   r->r[0] = 0;
   if (data_not_saved == TRUE)
   {
      if (junk_v != autopilot)
      {
         r->r[0] = 2;
         return 0;
      }
   }
   if (strlen(filename) == 0)
   {
      strcpy(filename,default_filename);
   }

   fp = fopen(filename,"rb");

   if (!fp)
   {
      r->r[0] = 3;
      return 0;
   }

   while(!feof(fp) || filelen<60)
   {
      junk_v = fgetc(fp); /* Check if file is at least 60 bytes long */
      filelen++;
   }
   rewind(fp);

   if (filelen>58)
   {
      fill_buffer(fp);
   } else
   {
      questions=0; /* If too short (ie: 0 bytes) then assume blank file. */
   }

   fclose(fp);
   data_not_saved = FALSE;
   r->r[0] = 1;
   return 0;
}



static int votemodule_savevotes(char *filename)
{
   /* >> SYS "VoteModule_SaveVotes" << (&16F01).

      On entry:
        R1 = Pointer to filename.

      On exit:
        R0 = 1 if save okay
             2 if load failed (you have to find out why it failed!)
	     3 if no filename given
   */

   FILE *fp;

   if (!filename) filename = default_filename;
   else if (strlen(filename) == 0) filename = default_filename;

   fp = fopen(filename,"wb");
   if (!fp) return 2;
   write_string(fp,"*** > ]eludoMetoV[ el4if atad etoWAtsaC < ***");
   write_word(fp,questions);
   for (currvote = 0; currvote < questions; currvote++)
   {
      savequestion(fp,currvote);
   }
   write_string(fp,"elif fo dnE");
   fclose(fp);
   data_not_saved = FALSE;
   return 1;
}



static int votemodule_refresh(void)
{
   /* >> SYS "VoteModule_Refresh" << (&16F02).

      This is the call used to write the CastAWote database to
      "<CastAWote$Dir>.Data.Data". It returns '1' in R0 if saved okay, else
      '2'. It is up to the calling program to fix the error and try again, or
      use VoteModule_SaveVotes to chuck the data somewhere else.

      On entry:

      On exit:
        R0 = 1 if save okay, else 2
   */

   FILE *fp;

   if (questions == 255) return 2;

   fp = fopen(default_filename,"wb");
   if (!fp) return 2;

   flush_buffer(fp);

   fclose(fp);

   data_not_saved = FALSE;
   return 1;
}



static int votemodule_getvote(_kernel_swi_regs *r)
{
   /* >> SYS "VoteModule_GetVote" << (&16F03).

      This call instructs VoteModule to build up details of the specified vote,
      and return a pointer...

      On entry:
        R0 = Vote number

      On exit:
        R0 = 1 if okay
             2 if vote out of range (doesn't exist)
        R1 = If R0 is 1 then this is a pointer to datablock
             If R0 is 2 then this is the number of questions in the database
   */

   currvote = r->r[0];
   if (currvote > questions)
   {
      r->r[0] = 2;
      r->r[1] = questions;
      return 0;
   }
   if (currvote < 1)
   {
      r->r[0] = 2;
      r->r[1] = questions;
      return 0;
   }
   currvote--;
   strcpy(blankit(votecurrent.creator,61),votedata[currvote].creator);
   strcpy(blankit(votecurrent.system,61),votedata[currvote].system);
   votecurrent.voted_for         = 0;
   votecurrent.voted_for         = votedata[currvote].voted_for;
   strcpy(blankit(votecurrent.question,60),votedata[currvote].question);
   strcpy(blankit(votecurrent.software_opts,60),votedata[currvote].software_opts);
   strcpy(blankit(votecurrent.addinfo_one,60),votedata[currvote].addinfo_one);
   strcpy(blankit(votecurrent.addinfo_two,60),votedata[currvote].addinfo_two);
   strcpy(blankit(votecurrent.addinfo_three,60),votedata[currvote].addinfo_three);
   strcpy(blankit(votecurrent.option_one_text,60),votedata[currvote].option_one_text);
   votecurrent.option_one_rslt   = 0;
   votecurrent.option_one_rslt   = votedata[currvote].option_one_rslt;
   strcpy(blankit(votecurrent.option_two_text,60),votedata[currvote].option_two_text);
   votecurrent.option_two_rslt   = 0;
   votecurrent.option_two_rslt   = votedata[currvote].option_two_rslt;
   strcpy(blankit(votecurrent.option_three_text,60),votedata[currvote].option_three_text);
   votecurrent.option_three_rslt = 0;
   votecurrent.option_three_rslt = votedata[currvote].option_three_rslt;
   strcpy(blankit(votecurrent.option_four_text,60),votedata[currvote].option_four_text);
   votecurrent.option_four_rslt  = 0;
   votecurrent.option_four_rslt  = votedata[currvote].option_four_rslt;
   strcpy(blankit(votecurrent.option_five_text,60),votedata[currvote].option_five_text);
   votecurrent.option_five_rslt  = 0;
   votecurrent.option_five_rslt  = votedata[currvote].option_five_rslt;
   strcpy(blankit(votecurrent.option_six_text,60),votedata[currvote].option_six_text);
   votecurrent.option_six_rslt   = 0;
   votecurrent.option_six_rslt   = votedata[currvote].option_six_rslt;
   strcpy(blankit(votecurrent.option_seven_text,60),votedata[currvote].option_seven_text);
   votecurrent.option_seven_rslt = 0;
   votecurrent.option_seven_rslt = votedata[currvote].option_seven_rslt;
   strcpy(blankit(votecurrent.option_eight_text,60),votedata[currvote].option_eight_text);
   votecurrent.option_eight_rslt = 0;
   votecurrent.option_eight_rslt = votedata[currvote].option_eight_rslt;

   vote_options_from_string(currvote);
   votecurrent.vote_is_local[0] = votedata[currvote].vote_is_local[0];
   votecurrent.vote_is_hidden[0] = votedata[currvote].vote_is_hidden[0];
   votecurrent.vote_is_inaccessible[0] = votedata[currvote].vote_is_inaccessible[0];
   votecurrent.vote_is_locked[0] = votedata[currvote].vote_is_locked[0];
   votecurrent.vote_flags_other[11] = votedata[currvote].vote_flags_other[11];
   votecurrent.vote_data_crc = votedata[currvote].vote_data_crc;
   votecurrent.vote_data_key[0] = votedata[currvote].vote_data_key[0];
   votecurrent.reserved_developer[0] = votedata[currvote].reserved_developer[0];
   votecurrent.reserved_user[0] = votedata[currvote].reserved_user[0];
   votecurrent.vote_datafile_type[0] = votedata[currvote].vote_datafile_type[0];

   r->r[0] = 1;
   r->r[1] = (int)&votecurrent;
   return 0;
}



static int votemodule_putvote(_kernel_swi_regs *r)
{
   /* >> SYS "VoteModule_PutVote" << (&16F04).

      This call instructs VoteModule to add the vote passed to it to the database.
      data beyond block%+975 is ignored. It is assumed that newvote data is zero.
      Therefore you need not set this to anything in particular.

      On entry:
        R0 = Pointer to vote block, in same format as VoteModule_GetVote.

      On exit:
        R0 =  1 if successfully added
              2 if overflow (ie: 220 votes already in database)
             10 if creator < 1 character
             11 if question < 1 character
             12 if option_one_text < 1 character
             13 if option_two_text < 1 character
             14 if software_opts has no egg ('' - denotes flags)
   */

   struct votebuffer *votetemp = NULL;
   votetemp = (struct votebuffer *)r->r[0];

   if ((questions+1) > maxvotes) return 2;

   if (strlen(votetemp->creator) < 1) return 10;
   if (strlen(votetemp->question) < 1) return 11;
   if (strlen(votetemp->option_one_text) < 1) return 12;
   if (strlen(votetemp->option_two_text) < 1) return 13;

   junk_v = strcspn(votetemp->software_opts,"");
   if ( junk_v == strlen(votetemp->software_opts) ) return 14;

   strcpy(votedata[questions].creator,votetemp->creator);
   strcpy(votedata[questions].system,votetemp->system);
   votedata[questions].voted_for = 0;
   strcpy(votedata[questions].question,votetemp->question);
   strcpy(votedata[questions].software_opts,votetemp->software_opts);
   strcpy(votedata[questions].addinfo_one,votetemp->addinfo_one);
   strcpy(votedata[questions].addinfo_two,votetemp->addinfo_two);
   strcpy(votedata[questions].addinfo_three,votetemp->addinfo_three);
   strcpy(votedata[questions].option_one_text,votetemp->option_one_text);
   votedata[questions].option_one_rslt = 0;
   strcpy(votedata[questions].option_two_text,votetemp->option_two_text);
   votedata[questions].option_two_rslt = 0;
   strcpy(votedata[questions].option_three_text,votetemp->option_three_text);
   votedata[questions].option_three_rslt = 0;
   strcpy(votedata[questions].option_four_text,votetemp->option_four_text);
   votedata[questions].option_four_rslt = 0;
   strcpy(votedata[questions].option_five_text,votetemp->option_five_text);
   votedata[questions].option_five_rslt = 0;
   strcpy(votedata[questions].option_six_text,votetemp->option_six_text);
   votedata[questions].option_six_rslt = 0;
   strcpy(votedata[questions].option_seven_text,votetemp->option_seven_text);
   votedata[questions].option_seven_rslt = 0;
   strcpy(votedata[questions].option_eight_text,votetemp->option_eight_text);
   votedata[questions].option_eight_rslt = 0;

   vote_options_to_string(questions);

   questions++;

   data_not_saved = TRUE;

   votemodule_refresh(); /* To be on the *safe* side */

   return 1;
}



static int votemodule_addvoteopt(_kernel_swi_regs *r)
{
   /* >> SYS "VoteModule_AddVoteOpt" << (&16F05).
      This call instructs VoteModule to add the option you've chosen to the
      results for the question.

      If accepted, the option will be updated and the total counter will be
      recalculated.

      On entry:
        R0 = Vote number.
        R1 = Option chosen.

      On exit:
        R0 = 1 if successfully added
             2 if invalid vote number
             3 if invalid option number
   */

   int opt_chosen = 0;
   int opt_total = 0;

   currvote = r->r[0];
   if (currvote > questions) return 2;
   if (currvote < 1) return 2;
   currvote--;

   opt_chosen = r->r[1];

   if (opt_chosen > 8) return 3;
   if (opt_chosen < 1) return 3;

   if (opt_chosen == 1)
   {
      if (strlen(votedata[currvote].option_one_text) == 0)
         return 3;
   }
   if (opt_chosen == 2)
   {
      if (strlen(votedata[currvote].option_two_text) == 0)
         return 3;
   }
   if (opt_chosen == 3)
   {
      if (strlen(votedata[currvote].option_three_text) == 0)
         return 3;
   }
   if (opt_chosen == 4)
   {
      if (strlen(votedata[currvote].option_four_text) == 0)
         return 3;
   }
   if (opt_chosen == 5)
   {
      if (strlen(votedata[currvote].option_five_text) == 0)
         return 3;
   }
   if (opt_chosen == 6)
   {
      if (strlen(votedata[currvote].option_six_text) == 0)
         return 3;
   }
   if (opt_chosen == 7)
   {
      if (strlen(votedata[currvote].option_seven_text) == 0)
         return 3;
   }
   if (opt_chosen == 8)
   {
      if (strlen(votedata[currvote].option_eight_text) == 0)
         return 3;
   }

   switch(opt_chosen)
   {
      case 1 : votedata[currvote].option_one_rslt++;
               break;
      case 2 : votedata[currvote].option_two_rslt++;
               break;
      case 3 : votedata[currvote].option_three_rslt++;
               break;
      case 4 : votedata[currvote].option_four_rslt++;
               break;
      case 5 : votedata[currvote].option_five_rslt++;
               break;
      case 6 : votedata[currvote].option_six_rslt++;
               break;
      case 7 : votedata[currvote].option_seven_rslt++;
               break;
      case 8 : votedata[currvote].option_eight_rslt++;
   }

   opt_total=votedata[currvote].option_one_rslt;
   opt_total+=votedata[currvote].option_two_rslt;
   opt_total+=votedata[currvote].option_three_rslt;
   opt_total+=votedata[currvote].option_four_rslt;
   opt_total+=votedata[currvote].option_five_rslt;
   opt_total+=votedata[currvote].option_six_rslt;
   opt_total+=votedata[currvote].option_seven_rslt;
   opt_total+=votedata[currvote].option_eight_rslt;
   votedata[currvote].voted_for = opt_total;

   data_not_saved = TRUE;
   return 1;
}



static void votemodule_voteop(_kernel_swi_regs *r)
{
   /* >> SYS "VoteModule_VoteOp" << (&16F08).

         Perform various specified operations on the database.

      On entry
         R0 = Operation:
                 1 - ???
                 2 - Delete the vote specified in R1.
                 3 - Compare first 30 characters string passed (in R1) against all
                     votes and return vote number or -1. For NVP.
                 4 - Delete all heap entries for the vote specified in R1.
                 5 - Return the heap entry for the user specified in R1.
                 6 - Save the heap entry (in R2) for the user (in R1) specified.
                 7 - Delete heap entries for the specified vote.
                 8 - Decrement all heap entries after the vote specified.
                 9 - Increment all heap entries after the vote specified.

         R1 = Depends on R0.

       On exit
         Depends on entry value of R0.

   */

   int opt = 0;
   int heapentry = 0;

   opt = r->r[0];

   switch(opt)
   {
      case     2 : currvote = r->r[1];
      	           currvote--;
      	           r->r[0]=votemodule_deletevote(currvote);
      	           break;
      case     3 : sprintf(junk_s,"%s",(char*) r->r[1]);
      	           r->r[0]=votemodule_findvote(junk_s);
      	           break;
      case     5 : votemodule_getheapentry(r);
      	       	   break;
      case     6 : votemodule_saveheapentry(r);
      	       	   break;
      case     7 : heapentry = r->r[1];
                   heapentry+=20;
                   r->r[0]=votemodule_heapentryaction(heapentry,1); /* Delete entry */
                   break;
      case     8 : heapentry = r->r[1];
                   heapentry+=20;
                   r->r[0]=votemodule_heapentryaction(heapentry,3); /* Decrement after */
                   break;
      case     9 : heapentry = r->r[1];
                   heapentry+=20;
                   r->r[0]=votemodule_heapentryaction(heapentry,2); /* Increment after */
                   break;
      case    10 : r->r[0]=votemodule_heapentryaction(0,4);         /* 'Tidy' heap */
                   break;
      case 0x16F : votemodule_registered(r);
      	   	   break;
      case 0x16F00:votemodule_registered(r);
      	   	   break;
      default    : break;
   }
   return;
}



static int votemodule_amendvote(_kernel_swi_regs *r)
{
   /* >> SYS "VoteModule_AmendVote" << (&16F09).

         Amend an existing vote in the database.

      On entry
         R0 = Pointer to data block.
         R1 = Question.

      On exit
         R0 = Operation status:
                 1 if added okay.
                 2 if question number is invalid.
                10 if creator < 1 character.
                11 if question < 1 character.
                12 if option_one_text < 1 character.
                13 if option_two_text < 1 character.
                14 if software_opts has no egg ('' - denotes flags).

      Use
         VoteModule_AmendVote will take your pointer and try to update the
         specified question.

         This is very much like PutVote, except you can specify any question
         number. There is a possible problem with the vote being changed whilst
         it is being read, therefore CastAWote will still refuse to operate when
         VoteEdit is loaded.

         It is assumed by VoteModule that:
           * Your data block is in EXACTLY the same format as for GetVote (above).
           * You have checked to ensure the vote is valid (two options etc).
           * That the strings are zero-terminated.

         The easiest way is to GetVote the data, amend it then chuck back the same
         data block.
   */

   struct votebuffer *votetemp = NULL;
   votetemp = (struct votebuffer *)r->r[0];

   currvote = r->r[1];
   if (currvote > questions)
   {
      r->r[1] = questions;
      return 2;
   }
   if (currvote < 1)
   {
      r->r[1] = questions;
      return 2;
   }
   currvote --;

   if (strlen(votetemp->creator) < 1) return 10;
   if (strlen(votetemp->question) < 1) return 11;
   if (strlen(votetemp->option_one_text) < 1) return 12;
   if (strlen(votetemp->option_two_text) < 1) return 13;

   junk_v = strcspn(votetemp->software_opts,"");
   if ( junk_v == strlen(votetemp->software_opts) ) return 14;

   strcpy(blankit(votedata[currvote].system,60),votetemp->creator);
   strcpy(blankit(votedata[currvote].system,60),votetemp->system);
   votedata[currvote].voted_for = votetemp->voted_for;
   strcpy(blankit(votedata[currvote].question,60),votetemp->question);
   strcpy(blankit(votedata[currvote].software_opts,60),votetemp->software_opts);
   strcpy(blankit(votedata[currvote].addinfo_one,60),votetemp->addinfo_one);
   strcpy(blankit(votedata[currvote].addinfo_two,60),votetemp->addinfo_two);
   strcpy(blankit(votedata[currvote].addinfo_three,60),votetemp->addinfo_three);
   strcpy(blankit(votedata[currvote].option_one_text,60),votetemp->option_one_text);
   votedata[currvote].option_one_rslt = votetemp->option_one_rslt;
   strcpy(blankit(votedata[currvote].option_two_text,60),votetemp->option_two_text);
   votedata[currvote].option_two_rslt = votetemp->option_two_rslt;
   strcpy(blankit(votedata[currvote].option_three_text,60),votetemp->option_three_text);
   votedata[currvote].option_three_rslt = votetemp->option_three_rslt;
   strcpy(blankit(votedata[currvote].option_four_text,60),votetemp->option_four_text);
   votedata[currvote].option_four_rslt = votetemp->option_four_rslt;
   strcpy(blankit(votedata[currvote].option_five_text,60),votetemp->option_five_text);
   votedata[currvote].option_five_rslt = votetemp->option_five_rslt;
   strcpy(blankit(votedata[currvote].option_six_text,60),votetemp->option_six_text);
   votedata[currvote].option_six_rslt = votetemp->option_six_rslt;
   strcpy(blankit(votedata[currvote].option_seven_text,60),votetemp->option_seven_text);
   votedata[currvote].option_seven_rslt = votetemp->option_seven_rslt;
   strcpy(blankit(votedata[currvote].option_eight_text,60),votetemp->option_eight_text);
   votedata[currvote].option_eight_rslt = votetemp->option_eight_rslt;

   vote_options_to_string(currvote);

   data_not_saved = TRUE;
   return 1;
}



static int votemodule_getvoteheader(_kernel_swi_regs *r)
{
   /* >> SYS "VoteModule_GetVoteHeader" << (&16F0A).

      This call instructs VoteModule to build up a header for the specified
      vote and return a pointer...

      This call is used by VoteEdit to build up the index display, and by
      CastAWote in 'ListVotes' option.

      On entry:
        R0 = Vote number

      On exit:
        R0 = 1 if okay
             2 if vote out of range (doesn't exist)
        R1 = If R0 is 1 then this is a pointer to datablock
             If R0 is 2 then this is the number of questions in the database
   */

   currvote = r->r[0];
   if (currvote > questions)
   {
      r->r[0] = 2;
      r->r[1] = questions;
      return 0;
   }
   if (currvote < 1)
   {
      r->r[0] = 2;
      r->r[1] = questions;
      return 0;
   }
   currvote --;
   strcpy(blankit(voteheader.creator,60),votedata[currvote].creator);
   strcpy(blankit(voteheader.question,60),votedata[currvote].question);
   voteheader.qnum = (currvote+1);

   r->r[0] = 1;
   r->r[1] = (int)&voteheader;
   return 0;
}



/* Support bits */


static void vote_options_from_string(int currvote)
{
   /* Decode the vote options string into the relevant data blocks. If the last byte
      (15) is equal to zero then assume format not supported in this question and
      fill with default values.

      Appears as sixteen bytes following a '' symbol.

       0 -  4 = RiscOS 5 byte time value when question created. Added by VoteModule.
       	    5 = 'Vote is local' value.
       	    6 = 'Vote is hidden' value.
       	    7 = 'Vote is inaccessible' value.
       	    8 = 'Vote is locked' value.
       9 - 12 = Reserved.
       	   13 = 'Reserved developer' value.
       	   14 = 'Reserved user' value.
       	   15 = 'Vote datafile type' value (should be 3).
   */
   int offset = 0;

   offset = strcspn(votedata[currvote].software_opts, "");
   if (offset == strlen(votedata[currvote].software_opts))
   {
      /* Yikes! This vote has no software options additional field!? */
      return;
   }

   if (votedata[currvote].software_opts[offset+16] == 0)
   {
      /* Assume not compatible */
      votedata[currvote].vote_is_local[0] = 88;
      votedata[currvote].vote_is_hidden[0] = 88;
      votedata[currvote].vote_is_inaccessible[0] = 88;
      votedata[currvote].vote_is_locked[0] = 88;
      votedata[currvote].vote_flags_other[11] = 0;
      votedata[currvote].vote_data_crc = 0;
      votedata[currvote].vote_data_key[0] = 0;
      votedata[currvote].reserved_developer[0] = 0;
      votedata[currvote].reserved_user[0] = 0;
      votedata[currvote].vote_datafile_type[0] = 3;
   }
   else
   {
      votedata[currvote].vote_is_local[0] = votedata[currvote].software_opts[offset+6];
      votedata[currvote].vote_is_hidden[0] = votedata[currvote].software_opts[offset+7];
      votedata[currvote].vote_is_inaccessible[0]=votedata[currvote].software_opts[offset+8];
      votedata[currvote].vote_is_locked[0] = votedata[currvote].software_opts[offset+9];
      votedata[currvote].vote_flags_other[11] = 0;
      votedata[currvote].vote_data_crc = 0;
      votedata[currvote].vote_data_key[0] = 0;
      votedata[currvote].reserved_developer[0] = votedata[currvote].software_opts[offset+14];
      votedata[currvote].reserved_user[0] = votedata[currvote].software_opts[offset+15];
      votedata[currvote].vote_datafile_type[0] = votedata[currvote].software_opts[offset+16];
   }

   return;
}



static void vote_options_to_string(int thisvote)
{
   /* The opposite of above, takes the votedata values and writes them into the options
      string.
   */
   int offset = 0;

   offset = strcspn(votedata[thisvote].software_opts, "");
   if (offset == strlen(votedata[thisvote].software_opts))
   {
      /* Yikes! This vote has no software options additional field!? */
      return;
   }

   votedata[thisvote].software_opts[offset+6] = votedata[thisvote].vote_is_local[0];
   votedata[thisvote].software_opts[offset+7] = votedata[thisvote].vote_is_hidden[0];
   votedata[thisvote].software_opts[offset+8] = votedata[thisvote].vote_is_inaccessible[0];
   votedata[thisvote].software_opts[offset+9] = votedata[thisvote].vote_is_locked[0];
   votedata[thisvote].software_opts[offset+14] = votedata[thisvote].reserved_developer[0];
   votedata[thisvote].software_opts[offset+15] = votedata[thisvote].reserved_user[0];
   votedata[thisvote].software_opts[offset+16] = votedata[thisvote].vote_datafile_type[0];

   return;
}



static int votemodule_deletevote(int currvote)
{
   /* This will delete the vote pointed to by currvote.
      Works by saving file with specified vote skipped, then reloading. :-)

      Now includes a check to not delete if currvote > questions.
   */

   FILE *fp;
   char filename[255];
   int  ourvote = 0;

   if (currvote>questions)
     return -1;

   strcpy(filename,default_filename);
   strcat(filename,"X");

   fp = fopen(filename,"wb");
   if (!fp) return 2;
   write_string(fp,"*** > {PMET} 91.1 eludoMetoV < ***");  /*  */
   write_word(fp,questions-1);
   if (questions > 0)
   {
      for (ourvote = 0; ourvote < currvote; ourvote++)
      {
         savequestion(fp,ourvote);
      }
      for (ourvote = currvote+1; ourvote < questions; ourvote++)
      {
         savequestion(fp,ourvote);
      }
   }
   write_string(fp,"elif fo dnE");
   fclose(fp);
   fp = fopen(filename,"rb");
   if (!fp) return 3;
   fill_buffer(fp);
   fclose(fp);
   remove(filename);
   data_not_saved = FALSE;
   return 1;
}



static int votemodule_findvote(char *junk_s)
{
   /* This one is, like, 99% written for NVP. It will accept a string, and
      compare it against every vote question (in lowercase). Returns -1 if
      not found, else the question number...
   */

   int  foundit=FALSE;
   int  loop = 0;
   char compare_one[255];
   char compare_two[255];

   lowercase(junk_s);
   strcpy(compare_one,junk_s);

   for (loop=0; loop!=questions; loop++)
   {
      strcpy(compare_two,votedata[loop].question);
      lowercase(compare_two);
      junk_v=strncmp(compare_two,compare_one,30);
      if (junk_v==0)
      {
         foundit=loop+1;
      }
   }
   if (foundit==FALSE) return -1;
   return foundit;
}



static void votemodule_registered(_kernel_swi_regs *r)
{
   /* This routine takes the encoded text (set up in 'votemodule_initialise') and
      decodes it (it's a simple EOR -1 algorythm). The name and number are available
      on R0 and R1 respectively. If R1=1 on entry, the information will be printed
      to the screen as well.
   */

   char rnam[255];
   char rnum[255];
   int  loop=0;
   loop=0;
   while (regname[loop] != 0)
   {
      rnam[loop] = regname[loop] ^ -1;
      loop++;
   }
   rnam[loop]=0;
   loop=0;
   while (regnum[loop] != 0)
   {
      rnum[loop] = regnum[loop] ^ -1;
      loop++;
   }
   if (r->r[1]==1) printf("Registered to : %s (#%s).\n",rnam,rnum);
   r->r[0]=(int)&rnam;
   r->r[1]=(int)&rnum;
}



static void votemodule_getheapentry(_kernel_swi_regs *r)
{
   /* Fetch the heap entry for the user specified in R1. Return a blank heap if not
      found.
   */

   char username[36];
   char heapentry[256];
   int  found = FALSE;
   int  loop = 0;
   FILE *fp;

   strcpy(heapentry,"");
   sprintf(junk_s,"%s",(char*) r->r[1]);

   if (strlen(username)>35)
   {
      r->r[0]=3;
      return;
   }
   strcpy(username,junk_s);

   fp = fopen("<CastAWoteA$Dir>.Data.HasVoted","rb");
   if (!fp)
   {
      r->r[0] = 2;
      return;
   }

   while (found==FALSE && !feof(fp))
   {
      if (fgets(junk_s,254,fp) == NULL)
      {
         r->r[0]=4;
         strcpy(heapentry,"");
         r->r[1]=(int)&heapentry;
         fclose(fp);
         return;
      }

      if (strstr(junk_s,username)!=NULL)
      {
         found = TRUE;
         /* Strip off username */
         junk_v=strcspn(junk_s,",");
         if (junk_v!=NULL)
         {
            junk_v++;
            for (loop=0; junk_s[junk_v]!='\0'; junk_v++, loop++)
            {
               heapentry[loop]=junk_s[junk_v];
            }
            heapentry[loop]='\0';
         }
         r->r[0]=1;
         r->r[1]=(int)&heapentry;
      }
   }
   fclose(fp);
   if (found==FALSE || found==2)
   {
      /* Not found? Return blank string and code '4' */
      r->r[0]=4;
      strcpy(heapentry,"");
      r->r[1]=(int)&heapentry;
   }
   return;
}



static void votemodule_saveheapentry(_kernel_swi_regs *r)
{
   /* Save the heap entry (in R2) for the user specified (in R1). This will update the
      existing or create a new if not found. Uses a temporary file "XHasVoted".
   */

   char username[36];
   char heapentry[256];
   char lastjunk[256];
   int  found=FALSE;
   FILE *infp;
   FILE *outfp;

   rename("<CastAWoteA$Dir>.Data.HasVoted","<CastAWoteA$Dir>.Data.XHasVoted");


   sprintf(junk_s,"%s",(char*) r->r[1]);
   if (strlen(junk_s)>35)
   {
      r->r[0]=5;
      return;
   }
   strcpy(username,junk_s);

   sprintf(junk_s,"%s",(char*) r->r[2]);
   if (strlen(junk_s)>225)
   {
      r->r[0]=6;
      return;
   }
   strcpy(heapentry,junk_s);

   outfp = fopen("<CastAWoteA$Dir>.Data.HasVoted","wb");
   if (!outfp)
   {
      r->r[0] = 3;
      return;
   }

   infp = fopen("<CastAWoteA$Dir>.Data.XHasVoted","rb");
   if (!infp)
   {
      r->r[0] = 2;
      return;
   }

   while (!feof(infp))
   {
      if (fgets(junk_s,254,infp)!=NULL)
      {
        junk_v=strlen(junk_s);
        if (junk_s[junk_v-1]=='\n') junk_s[junk_v-1]='\0';
        if (strcmp(junk_s,lastjunk)!=NULL)
        {
           if (strstr(junk_s,username)==NULL)
           {
              fprintf(outfp,"%s\n",junk_s);
           }
           else
           {
              found=TRUE;
              fprintf(outfp,"%s,%s\n",username,heapentry);
           }
        }
      }
      else
      {
         strcpy(lastjunk,junk_s);
      }
   }
   fclose(infp);

   if (found!=TRUE)
   {
      fprintf(outfp,"%s,%s\n",username,heapentry);
   }

   fclose(outfp);
   remove ("<CastAWoteA$Dir>.Data.XHasVoted");
   r->r[0] = 1;
}



static int votemodule_heapentryaction(int heapentry, int action)
/* Perform actions on the heap...
   1 - Delete references to the specified vote.
   2 - Increment all references after the specified.
   3 - Decrement all references after the specified.
   4 - 'tidy' the heap (remove blank lines).
*/
{
   char heap[256];
   char lastheap[256];
   char userdata[35];
   char heapdata[256];
   char sortdata[222];
   int  loop=0;
   int  temp=0;
   FILE *infp;
   FILE *outfp;

   if (action!=4)
   {
      heapentry=heapentry-20;
      if (heapentry<1) return 4;
   }

   rename("<CastAWoteA$Dir>.Data.HasVoted","<CastAWoteA$Dir>.Data.XHasVoted");

   outfp = fopen("<CastAWoteA$Dir>.Data.HasVoted","wb");
   if (!outfp)
   {
      return 3;
   }

   infp = fopen("<CastAWoteA$Dir>.Data.XHasVoted","rb");
   if (!infp)
   {
      return 2;
   }

   while (!feof(infp))
   {
      fgets(heap,254,infp);
      if (strcmp(heap,lastheap)!=NULL)
      {
         if (strlen(heap)>1)
         {
            junk_v=strcspn(heap,",");
            if (junk_v!=NULL)
            {
               for (loop=0;loop!=junk_v; loop++)
               {
                  userdata[loop]=heap[loop];
               }
               userdata[loop]='\0';
               junk_v++;
               for (loop=0; heap[junk_v]!='\0'; junk_v++, loop++)
               {
                  heapdata[loop]=heap[junk_v];
               }
               heapdata[loop-1]='\0';
            }

            for (loop=0; loop<221; loop++) sortdata[loop]=255;

	    for (loop=0; heapdata[loop]!='\0'; loop++)
	    {
	       temp=heapdata[loop];
	       sortdata[temp-20]=temp;
	    }
	    sortdata[221]='\0';

	    switch (action)
	    {
	       case 1 : sortdata[heapentry]=255;
	       	        break;
	       case 2 : loop=heapentry;
	       	      	while (loop<221)
	       	      	{
	       	      	   temp=sortdata[loop];
	       	      	   if (temp!=255 && temp!='\0') temp++;
	       	      	   sortdata[loop]=temp;
	       	      	   loop++;
	       	      	}
	       	      	break;
	       case 3 : loop=heapentry;
	       	      	while (loop<221)
	       	      	{
	       	      	   temp=sortdata[loop];
	       	      	   if (temp!=255 && temp!='\0') temp--;
	       	      	   sortdata[loop]=temp;
	       	      	   loop++;
	       	      	}
	       	      	sortdata[heapentry-1]=255;
	       	      	sortdata[heapentry]=255;
	       	      	break;
	       default: break;
            }
            fprintf(outfp,"%s,",userdata);
            loop=1;
            while (loop<221)
            {
               if (sortdata[loop]!=255 && sortdata[loop]!='\0') fputc(sortdata[loop],outfp);
               loop++;
            }
            fprintf(outfp,"\n");
            strcpy(lastheap,heap);
         }
      }
   }

   fclose(infp);
   fclose(outfp);
   remove ("<CastAWoteA$Dir>.Data.XHasVoted");
   return 1;
}



static void lowercase(char string[])
{
   /* Take the string passed and convert it to lower case. */
   int i=0;
   while (string[i] != 0)
   {
      string[i] = tolower(string[i]);
      i++;
   }
}


static char *blankit(char input[], int length)
{
   int loop = 0;

   for (loop=0; loop<=length; input[loop]='\0', loop++);
   return(input);
}


/* File handling routines */

static void loadquestion(FILE* fp, int currvote)
{
   /* Loads a question block into memory */
   read_string(fp,votedata[currvote].creator);
   read_string(fp,votedata[currvote].system);
   votedata[currvote].voted_for = read_word(fp);
   read_string(fp,votedata[currvote].question);
   read_string(fp,votedata[currvote].software_opts);
   read_string(fp,votedata[currvote].addinfo_one);
   read_string(fp,votedata[currvote].addinfo_two);
   read_string(fp,votedata[currvote].addinfo_three);
   read_string(fp,votedata[currvote].option_one_text);
   votedata[currvote].option_one_rslt = read_word(fp);
   read_string(fp,votedata[currvote].option_two_text);
   votedata[currvote].option_two_rslt = read_word(fp);
   read_string(fp,votedata[currvote].option_three_text);
   votedata[currvote].option_three_rslt = read_word(fp);
   read_string(fp,votedata[currvote].option_four_text);
   votedata[currvote].option_four_rslt = read_word(fp);
   read_string(fp,votedata[currvote].option_five_text);
   votedata[currvote].option_five_rslt = read_word(fp);
   read_string(fp,votedata[currvote].option_six_text);
   votedata[currvote].option_six_rslt = read_word(fp);
   read_string(fp,votedata[currvote].option_seven_text);
   votedata[currvote].option_seven_rslt = read_word(fp);
   read_string(fp,votedata[currvote].option_eight_text);
   votedata[currvote].option_eight_rslt = read_word(fp);
}



static void savequestion(FILE* fp, int currvote)
{
   /* Saves a question block from memory */
   write_string(fp,votedata[currvote].creator);
   write_string(fp,votedata[currvote].system);
   write_word(fp,votedata[currvote].voted_for);
   write_string(fp,votedata[currvote].question);
   write_string(fp,votedata[currvote].software_opts);
   write_string(fp,votedata[currvote].addinfo_one);
   write_string(fp,votedata[currvote].addinfo_two);
   write_string(fp,votedata[currvote].addinfo_three);
   write_string(fp,votedata[currvote].option_one_text);
   write_word(fp,votedata[currvote].option_one_rslt);
   write_string(fp,votedata[currvote].option_two_text);
   write_word(fp,votedata[currvote].option_two_rslt);
   write_string(fp,votedata[currvote].option_three_text);
   write_word(fp,votedata[currvote].option_three_rslt);
   write_string(fp,votedata[currvote].option_four_text);
   write_word(fp,votedata[currvote].option_four_rslt);
   write_string(fp,votedata[currvote].option_five_text);
   write_word(fp,votedata[currvote].option_five_rslt);
   write_string(fp,votedata[currvote].option_six_text);
   write_word(fp,votedata[currvote].option_six_rslt);
   write_string(fp,votedata[currvote].option_seven_text);
   write_word(fp,votedata[currvote].option_seven_rslt);
   write_string(fp,votedata[currvote].option_eight_text);
   write_word(fp,votedata[currvote].option_eight_rslt);
}



static void flush_buffer(FILE* fp)
{
   /* To save repeating this code... It will save the votes data to the file handle
      specified.
   */

   write_string(fp,"*** > ]eludoMetoV[ elif atad etoWAtsaC < ***");
   write_word(fp,questions);
   if (questions > 0)
   {
      for (currvote = 0; currvote < questions; currvote++)
      {
         savequestion(fp,currvote);
      }
   }
   write_string(fp,"elif fo dnE");
}



static void fill_buffer(FILE* fp)
{
   /* This is the reverse of 'flush_buffer'. It loads the votes data into memory from
      the given file handle.
   */

   read_string(fp, junk_s);
   questions = read_word(fp);
   if (questions > 0)
   {
      for (currvote = 0; currvote < questions; currvote++)
      {
         loadquestion(fp,currvote);
      }
   }
   read_string(fp, junk_s);
}



/* Physical file I/O routines (Word/string INPUT#/PRINT#) */

static void read_string(FILE *file, char *string)
{
   /* Reads a string in the style of BASIC's INPUT# command. */
   int len=0;
   int i=0;

   i = fgetc(file);
   if (i != 0x00)
   {
      returncode = 4;
      fclose(file);
      exit(EXIT_FAILURE);
   }
   len = fgetc(file);
   if (len>0)
     for (i=len-1;i>=0;i--)
     {
        string[i] = fgetc(file);
     }
     string[len] = '\0';
}



static int read_word(FILE *file)
{
   /* Reads a word in the style of BASIC's INPUT# command. */
   int num=0;

   num = fgetc(file);
   if (num != 0x40)
   {
      returncode = 5;
      fclose(file);
      exit(EXIT_FAILURE);
   }
   num = fgetc(file);
   num = (num << 8) | fgetc(file);
   num = (num << 8) | fgetc(file);
   num = (num << 8) | fgetc(file);
   return num;
}



static void write_string(FILE *file, char *string)
{
   /* Writes a string in the style of BASIC's PRINT# command. */
   int len = strlen(string);
   int i=0;

   fputc(0x00, file);
   fputc(len, file);
   for (i=len-1;i>=0;i--)
   {
      fputc(string[i], file);
   }
}



static void write_word(FILE *file, int num)
{
   /* Writes a word in the style of BASIC's PRINT# command. */
   fputc(0x40, file);
   fputc((0xFF & (num >> 24)), file);
   fputc((0xFF & (num >> 16)), file);
   fputc((0xFF & (num >> 8)), file);
   fputc((0xFF & num), file);
}

