/* tagfile.c
**
** An abstract data type for handling tagfiles
*/

/// Includes
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
/*
#ifdef AMIGA
#include <proto/dos.h>
#include <dos/rdargs.h>
#endif
*/
#include "protos.h"
#include "tagfile.h"
#include "config.h"
#include "macros.h"
//#include <rdlib_protos.h>
///
/// Prototypes
Prototype   struct TagFile *tagfile_openfile (struct TagFile *prev_tf, char *name);
Prototype   void    tagfile_bufferfile(struct TagFile *tf);
Local       ULONG   tagfile_processOPT (ULONG flags, char *line);
Local       BOOL    tagfile_loadindex (struct TagFile *tf);
Local       BOOL    tagfile_genindex_multi (struct TagFile *tf);
Local       BOOL    tagfile_genindex_oneliner (struct TagFile *tf);
Local       void    tagfile_seek(struct TagFile *tf, int offs);
Local       int     tagfile_tell(struct TagFile *tf);
Local       char   *tagfile_readline(struct TagFile *tf);
Prototype   struct  TagFile *tagfile_closefile (struct TagFile *tf);
Prototype   void    tagfile_closeall (struct TagFile *tf);
Prototype   struct  TagFile *tagfile_clone (struct TagFile *tf);
Prototype   char   *tagfile_getname (struct TagFile *tf);
Prototype   BOOL    tagfile_goto (struct TagFile *tf, unsigned int num);
Prototype   BOOL    tagfile_next (struct TagFile *tf);
Prototype   BOOL    tagfile_prev (struct TagFile *tf);
Prototype   BOOL    tagfile_random (struct TagFile *tf);
Prototype   BOOL    tagfile_bof (struct TagFile *tf);
Prototype   BOOL    tagfile_eof (struct TagFile *tf);
Prototype   char   *tagfile_get (struct TagFile *tf);
Prototype   int     tagfile_ordinal (struct TagFile *tf);
///

/// tagfile_openfile
/* ----------------------------- tagfile_openfile ------------------------------

 Comment: Open a named tag file

*/

struct TagFile *tagfile_openfile (struct TagFile *prev_tf, char *name)
{
    struct TagFile *tf;
    char *f1, *f2;
    BOOL ret;

    /* Allocate a new TagFile structure */
    tf = malloc (sizeof (struct TagFile));
                if (!tf) outofmemory ();
    memset(tf, 0, sizeof(struct TagFile));

    /* Open the tagfile */
    tf->FileHandle = fopen (name, "rb");
    if (!tf->FileHandle) {
        free (tf);
        return NULL;
    }

    /* Get the length */
    fseek (tf->FileHandle, 0L, SEEK_END);
    tf->LOF = ftell (tf->FileHandle);
                rewind (tf->FileHandle);

    /* Scan the filename for the file part */
    f2 = name;
    while ((f1 = strpbrk (f2, PATH_SEPERATOR)) != NULL)
        f2 = f1 + 1;

    /* Fill in some of the TagFile fields */
    tf->Previous = prev_tf;
    strcpy (tf->Filename, name);
    strcpy (tf->Basename, f2);
    tf->Flags = Config.DefTagfileType;
    tf->TagNum = -1;

    /* Process the [OPT] line */
    f1 = ReadLine (tf->FileHandle);
    if (strncmp (f1, "[OPT]", 5) == 0) {
        tf->Flags = tagfile_processOPT(tf->Flags, f1);
    } else {
        rewind (tf->FileHandle);
    }

    ret = tagfile_loadindex (tf);

    if (!ret) {
        fclose (tf->FileHandle);
        free (tf);
        tf = NULL;
    }

    return tf;
}
///
/// tagfile_bufferfile
void tagfile_bufferfile(struct TagFile *tf)
{
    // Uncomment next line to enable manual tagfile bufferin
    // if(Config.Buffer == 0) return;

    if(tf->Buffer) return;

    tf->Buffer = malloc(tf->LOF+1);
    if(tf->Buffer == NULL) {
      //AlertUser("Unable to buffer\n%s\nInsufficient memory\n"
      //          "Reverting to unbuffered mode", "_OK", tf->Filename);
      return;
    }

    /* This weird looking line sets the last byte in the buffer to zero */
    *((char *)(((int)tf->Buffer)+tf->LOF)) = 0;

    /* This weird looking line sets the pointer-within-the-buffer to a point
     * equivalent to the pointer within the disk file */
    tf->BufPtr = (char *)(((int)tf->Buffer)+ftell(tf->FileHandle));

    fseek(tf->FileHandle, 0, SEEK_SET);
    if(fread(tf->Buffer, 1, tf->LOF, tf->FileHandle) != tf->LOF) {
      //AlertUser("Unable to buffer\n%s\nFile I/O error\n"
      //          "Reverting to unbuffered mode", "_OK", tf->Filename);
      free(tf->Buffer);
      return;
    }
}
///
/// tagfile_processOPT
/* -------------------------- tagfile_processOPT ---------------------------

 Comment: PRIVATE: process the [OPT] line of a tagfile

*/

ULONG tagfile_processOPT (ULONG flags, char *line)
{
        char *tok, *str;

        line += 5; /* skip "[OPT]" part */
        str = line;

        while (tok = strtok(str, " ")) {
                str = NULL;
                if (strcmp(tok, "ONELINE") == 0) flags = (flags & 0xFFFF0000) | TAGFILE_ONELINER;
                if (strcmp(tok, "FORTUNE") == 0) flags = (flags & 0xFFFF0000) | TAGFILE_FORTUNE;
                if (strcmp(tok, "SPOT") == 0) flags = (flags & 0xFFFF0000) | TAGFILE_SPOT;
                if (strcmp(tok, "CYCLONE") == 0) flags = (flags & 0xFFFF0000) | TAGFILE_CYCLONE;
                if (strcmp(tok, "NOINDEX") == 0) flags = flags | TAGFILE_NOINDEX;
                if (strcmp(tok, "ALWAYSINDEX") == 0) flags = flags | TAGFILE_ALWAYSINDEX;
        }

        return flags;
}
///
/// tagfile_loadindex
/* -------------------------- tagfile_loadindex ---------------------------

 Comment: PRIVATE: attempt to load an index file

*/

BOOL tagfile_loadindex (struct TagFile *tf)
{
    struct { ULONG ver, flags, tagcnt, lof; } hdr;
    BOOL index = Config.MakeIndex, indexOK = FALSE, ret;
    char idxname[256];
    FILE *idxfh = NULL;

    if (tf->Flags & TAGFILE_NOINDEX) index = FALSE;
    if (tf->Flags & TAGFILE_ALWAYSINDEX) index = TRUE;

    if (index) {
        sprintf (idxname, "%s.index", tf->Filename);
        idxfh = fopen (idxname, "rb");
        if (idxfh) {
            if (fread (&hdr, sizeof (hdr), 1, idxfh) == 1 &&
                    hdr.ver == INDEX_VERSION &&
                    hdr.lof == tf->LOF) {
                tf->TagCount = hdr.tagcnt;
                tf->IndexTable = malloc (hdr.tagcnt*4);
                if (tf->IndexTable) {
                    if (fread (tf->IndexTable, 4, hdr.tagcnt, idxfh) == hdr.tagcnt)
                        indexOK = TRUE;
                    else
                        free (tf->IndexTable);
                }
            }
            fclose (idxfh);
        }
    }

    if (indexOK == FALSE) {
        tagfile_bufferfile(tf);
        if (tf->Flags & TAGFILE_MULTILINE)
            ret = tagfile_genindex_multi (tf);
        else
            ret = tagfile_genindex_oneliner (tf);
        if (ret == FALSE) return FALSE;

        if (index) {
            indexOK = FALSE;
            hdr.ver = INDEX_VERSION;
            hdr.flags = tf->Flags & TAGFILE_TYPE;
            hdr.tagcnt = tf->TagCount;
            hdr.lof = tf->LOF;

            idxfh = fopen (idxname, "wb");
            if (idxfh) {
                if (fwrite (&hdr, sizeof (hdr), 1, idxfh) == 1)
                    if (fwrite (tf->IndexTable, 4, hdr.tagcnt, idxfh) == hdr.tagcnt)
                        indexOK = TRUE;
                fclose (idxfh);
            }

            if (indexOK == FALSE)
                AlertUser ("Couldn't write index file\n%s\n%s", "_OK",
                            ErrorMessage());
        }

    }

    return TRUE;
}
///
/// tagfile_genindex_multi
/* -------------------------- tagfile_genindex_multi ---------------------------

 Comment: PRIVATE: generate an index of a multi-line tagfile

*/

BOOL tagfile_genindex_multi (struct TagFile *tf)
{
  ULONG *index;
  unsigned int pos, p, flag = 0;
  char *buf, *sep;

  /* Remember where we are */
  pos = tagfile_tell(tf);

  switch (tf->Flags & TAGFILE_TYPE) {
  case TAGFILE_FORTUNE: sep = "%"; break;
  case TAGFILE_SPOT: sep = "%%"; break;
  case TAGFILE_CYCLONE: sep = "%%%"; break;
  }

  /* Count the number of tags */
  tf->TagCount = 0;
  do {
    buf = tagfile_readline(tf);
    if (buf == NULL && flag)
      tf->TagCount++;
    if (buf) if (strncmp (buf, sep, 2) == 0) tf->TagCount++;
    flag = 1;
  } while (buf);

  /* Is the tagfile empty? */
  if (tf->TagCount == 0) return TRUE;

  /* Allocate memory for the index */
  index = malloc (tf->TagCount * sizeof (int) * 4);
  if (!index) outofmemory ();
  tf->IndexTable = index;

  /* Build the index */
  tagfile_seek (tf, pos);
  for (p = 0; p < tf->TagCount; p++) {
    index[p] = tagfile_tell(tf);
    for (;;) {
      buf = tagfile_readline(tf);
      if (buf == NULL) break;
      if (strncmp (buf, sep, 2) == 0) break;
    }
  }

  return TRUE;
}
///
/// tagfile_genindex_oneliner
/* ------------------------- tagfile_genindex_oneliner -------------------------

 Comment: PRIVATE: generate an index of a one-liner tagfile

*/

BOOL tagfile_genindex_oneliner (struct TagFile *tf)
{
  unsigned int pos, p;
  ULONG *index;
  char *buf;

  /* Remember where we are */
  pos = tagfile_tell(tf);

  /* Count the number of tags */
  tf->TagCount = 0;
  while (buf = tagfile_readline(tf))
    tf->TagCount++;

  /* Is the tagfile empty? */
  if (tf->TagCount == 0) return TRUE;

  /* Allocate memory for the index */
  index = malloc (tf->TagCount * sizeof (int) * 4);
  if (!index) outofmemory ();
  tf->IndexTable = index;

  /* Build the index */
  tagfile_seek (tf, pos);
  for (p = 0; p < tf->TagCount; p++) {
    index[p] = tagfile_tell(tf);
    tagfile_readline(tf);
  }

  buf = buf; /* To prevent compiler warnings */

  return TRUE;
}
///
/// tagfile_seek
void tagfile_seek(struct TagFile *tf, int offs)
{
    if(tf->Buffer) {
        tf->BufPtr = (char *)((int)(tf->Buffer)+offs);
    } else {
        fseek(tf->FileHandle, offs, SEEK_SET);
    }
}
///
/// tagfile_tell
int tagfile_tell(struct TagFile *tf)
{
    if(tf->Buffer) {
        return ((int)tf->BufPtr) - ((int)tf->Buffer);
    } else {
        return ftell(tf->FileHandle);
    }
}
///
/// tagfile_readline
char *tagfile_readline(struct TagFile *tf)
{
    if(tf->Buffer) {
        return ReadLineFromMemory(&tf->BufPtr);
    } else {
        return ReadLine(tf->FileHandle);
    }
}
///
/// tagfile_closefile
/* ----------------------------- tagfile_closefile -----------------------------

 Comment: close the given TagFile and return its parent

*/

struct TagFile *tagfile_closefile (struct TagFile *tf)
{
    struct TagFile *prev;

    prev = tf->Previous;
    fclose (tf->FileHandle);
    if(tf->Buffer) free(tf->Buffer);
    free (tf);

    return prev;
}
///
/// tagfile_closeall
/* ----------------------------- tagfile_closeall ------------------------------

 Comment: Close all the TagFiles in a given chain
 Supplied parameter can be NULL
*/

void tagfile_closeall (struct TagFile *tf)
{
    while (tf != NULL) tf = tagfile_closefile (tf);

    return;
}
///
/// tagfile_clone
/* ------------------------------- tagfile_clone -------------------------------

 Comment: clone a TagFile chain

*/

struct TagFile *tagfile_clone (struct TagFile *tf)
{
    struct TagFile *newtf;

    if (!tf) return NULL;

    /* Allocate memory and initialise */
    newtf = malloc(sizeof(struct TagFile));
    if(!newtf) return NULL;
    memcpy(newtf, tf, sizeof(struct TagFile));

    /* Create filehandle */
    newtf->FileHandle = fopen(newtf->Filename, "rb");
    if(!newtf->FileHandle) {
        free(newtf);
        return NULL;
    }
    fseek(newtf->FileHandle, ftell(tf->FileHandle), SEEK_SET);

    /* Clone index table */
    if(tf->IndexTable) {
        newtf->IndexTable = malloc(4*newtf->TagCount);
        if(!newtf->IndexTable) {
            fclose(newtf->FileHandle);
            free(newtf);
            return NULL;
        }
        memcpy(newtf->IndexTable, tf->IndexTable, 4*newtf->TagCount);
    }

    /* Clone buffer */
    newtf->Buffer = NULL;
    if(tf->Buffer) tagfile_bufferfile(newtf);

    /* Next item in list */
    newtf->Previous = tagfile_clone (tf->Previous);

    return newtf;
}
///
/// tagfile_getname
/* ------------------------------ tagfile_getname ------------------------------

 Comment: Return the name of a TagFile

*/

char *tagfile_getname(struct TagFile *tf)
{
                return tf->Basename;
}
///
/// tagfile_goto
/* ------------------------------- tagfile_goto --------------------------------

 Comment: jump to a specific tagline

*/

BOOL tagfile_goto (struct TagFile *tf, unsigned int num)
{
                if (num == (unsigned int)0xFFFFFFFF || num < tf->TagCount) {
                                tf->TagNum = num;
                                return TRUE;
                }

                return FALSE;
}
///
/// tagfile_next
/* ------------------------------- tagfile_next --------------------------------

 Comment: move to the next tagline

*/

BOOL tagfile_next (struct TagFile *tf)
{
    if (tagfile_eof (tf)) return FALSE;

    tf->TagNum++;
    return TRUE;
}
///
/// tagfile_prev
/* ------------------------------- tagfile_prev --------------------------------

 Comment: Move to the previous tagline

*/

BOOL tagfile_prev (struct TagFile *tf)
{
    if (tagfile_bof (tf)) return FALSE;

    tf->TagNum--;
    return TRUE;
}
///
/// tagfile_random
/* ------------------------------ tagfile_random -------------------------------

 Comment: pick a tagline at random

*/

BOOL tagfile_random (struct TagFile *tf)
{
    tf->TagNum = RandomNumber (tf->TagCount);
    return TRUE;
}
///
/// tagfile_bof
/* -------------------------------- tagline_bof --------------------------------

 Comment: is the current tagline the first in the tagfile?

*/

BOOL tagfile_bof (struct TagFile *tf)
{
    return tf->TagNum <= 0 ? TRUE : FALSE;
}
///
/// tagfile_eof
/* -------------------------------- tagfile_eof --------------------------------

 Comment: is the current tagline the last in the tagfile?

*/

BOOL tagfile_eof (struct TagFile *tf)
{
                if (tf->TagNum == 0xFFFFFFFF) return TRUE;
    if (tf->TagNum >= tf->TagCount) return TRUE;
    else return FALSE;
}
///
/// tagfile_get
/* -------------------------------- tagfile_get --------------------------------

 Comment: return the current tagline

*/

char *tagfile_get (struct TagFile *tf)
{
  unsigned int size = 0, lines = 0, p, q;
  char *buf, *buf2, *tag, *sep;

  /* Is the a tagline selected? */
  if (!tf) return (char *)(-1);
  if (tf->TagNum == 0xFFFFFFFF) return (char *)(-1);

  /* Check for "fortune cookie" mode */
  switch (tf->Flags & TAGFILE_TYPE) {
  case TAGFILE_ONELINER: sep = NULL; break;
  case TAGFILE_FORTUNE: sep = "%"; break;
  case TAGFILE_SPOT: sep = "%%"; break;
  case TAGFILE_CYCLONE: sep = "%%%"; break;
  }

  /* Seek to tagline */
  tagfile_seek (tf, tf->IndexTable[tf->TagNum]);

  /* Find out how much memory we need */
  while (1) {
    buf = tagfile_readline(tf);
    if (buf == NULL) break;
    if (sep!=NULL) if (strcmp (buf, sep) == 0) break;
    size += strlen (buf) + 1;
    for (p = 0; p < strlen (buf); p++)
      if (strncmp ("\\n", &buf[p], 2) == 0) size--;
    lines++;
    if (sep==NULL) break;
  }

  if (size == 0) {
    tag = malloc (1);
    if (!tag) outofmemory ();
    *tag = 0;
    return tag;
  }

  /* Allocate memory */
  tag = malloc (size);
  if (!tag) outofmemory ();

  /* Retrieve tagline */
  tag[0] = 0;
  buf2 = tag;
  tagfile_seek (tf, tf->IndexTable[tf->TagNum]);
  for (p = 0; p < lines; p++) {
    if (p) {
      //strcat (tag, "\n");
      *buf2 = '\n';
      buf2++;
      *buf2 = 0;
    }
    //strcat (tag, ReadLine (tf->FileHandle));
    buf = tagfile_readline(tf);
    for (q = 0; q < strlen (buf); q++) {
      if (strncmp ("\\n", &buf[q], 2) == 0)
	*buf2 = '\n';
      else
	*buf2 = buf[q];
      buf2++;
      *buf2 = 0;
    }
  }

  return tag;
}
///
/// tagfile_ordinal
/* ------------------------------ tagfile_ordinal ------------------------------

 Comment: return the ordinal number of the current tagline

*/

int tagfile_ordinal (struct TagFile *tf)
{
    return tf->TagNum;
}
///

