/*===========================================================================*
 *                                                                           *
 *  sflfort.c - Fortune cookie functions                                     *
 *                                                                           *
 *  Copyright (c) 1991-2010 iMatix Corporation                               *
 *                                                                           *
 *  ------------------ GPL Licensed Source Code ------------------           *
 *  iMatix makes this software available under the GNU General               *
 *  Public License (GPL) license for open source projects.  For              *
 *  details of the GPL license please see www.gnu.org or read the            *
 *  file license.gpl provided in this package.                               *
 *                                                                           *
 *  This program is free software; you can redistribute it and/or            *
 *  modify it under the terms of the GNU General Public License as           *
 *  published by the Free Software Foundation; either version 3 of           *
 *  the License, or (at your option) any later version.                      *
 *                                                                           *
 *  This program is distributed in the hope that it will be useful,          *
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of           *
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the            *
 *  GNU General Public License for more details.                             *
 *                                                                           *
 *  You should have received a copy of the GNU General Public                *
 *  License along with this program in the file 'license.gpl'; if            *
 *  not, see <http://www.gnu.org/licenses/>.                                 *
 *                                                                           *
 *  You can also license this software under iMatix's General Terms          *
 *  of Business (GTB) for commercial projects.  If you have not              *
 *  explicitly licensed this software under the iMatix GTB you may           *
 *  only use it under the terms of the GNU General Public License.           *
 *                                                                           *
 *  For more information, send an email to info@imatix.com.                  *
 *  --------------------------------------------------------------           *
 *===========================================================================*/

#include "prelude.h"                    /*  Universal header file            */
#include "sflfile.h"                    /*  File-handling functions          */
#include "sflstr.h"                     /*  String-handling functions        */
#include "sflmem.h"                     /*  Memory-handling functions        */
#include "sflcomp.h"                    /*  Compression functions            */
#include "sflfort.h"                    /*  Prototypes for functions         */
#include "sflsock.h"                    /*  Required for htons 'etc'         */


/*  These constants define the maximum sizes of blocks in the fortune file
 *  and the maxium number of blocks in such a file.  You should not change
 *  these unless you're prepared to rebuild all fortune files.
 */

#define BLOCK_SIZE  32000U              /*  Max. size of output block        */
#define MAX_PARAS   250                 /*  Max. paragraphs per block        */
#define MAX_BLOCKS  65000U              /*  Max. blocks in file              */

static void have_end_of_paragraph (Bool compress);
static void have_end_of_block     (Bool compress);

static FILE
    *input,                             /*  Input file stream                */
    *output,                            /*  Output file stream               */
    *scratch;                           /*  Temporary copy of input          */
static int
    *sizes;                             /*  Size of each block               */
static dbyte
    insize,
    outsize,
    nbr_blocks,
    nbr_paras,
    output_dbyte;
static qbyte
    output_qbyte;
static byte
    *inbuf,
    *outbuf;


/*  ---------------------------------------------------------------------[<]-
    Function: fortune_build

    Synopsis: Builds an indexed fortune file from a formatted text file.
    The text file contains paragraphs separated by lines containing '%%'.
    If okay, returns 0, else returns -1, see errno for the cause.
    ---------------------------------------------------------------------[>]-*/

int
fortune_build (
    const char *infile,                 /*  Name of file to compress         */
    const char *outfile,                /*  Output file, created             */
    Bool        compress)               /*  Compress yes/no                  */
{
    char
        line [LINE_MAX + 1];            /*  Line from input file             */
    int
        block_nbr;
    long
        offset;

    inbuf   = mem_alloc (BLOCK_SIZE);
    outbuf  = mem_alloc (BLOCK_SIZE);
    sizes   = mem_alloc (MAX_BLOCKS * sizeof (int));
    input   = file_open (infile, 'r');
    scratch = ftmp_open (NULL);

    if (!inbuf || !outbuf || !sizes || !input || !scratch)
      {
        mem_free (inbuf);
        mem_free (outbuf);
        mem_free (sizes);
        return (-1);
      }

    nbr_blocks = 0;
    nbr_paras  = 0;                     /*  Number of paragraphs in block    */
    insize     = 2;                     /*  Leave room for nbr_paras         */

    while (file_read (input, line))
      {
        if (streq (line, "%%"))
            have_end_of_paragraph (compress);
        else
          {
            memcpy (inbuf + insize, line, strlen (line));
            insize += strlen (line);
            inbuf [insize++] = '\n';
          }
      }
    have_end_of_paragraph (compress);
    if (nbr_paras)
        have_end_of_block (compress);

    fclose (input);                     /*  Finished with input file         */

    output = fopen (outfile, "wb");
    fprintf (output, "IFF%s -- http://www.imatix.com/ --\n%c",
                     compress? "CMP": "TXT", 26);

    output_dbyte = htons (nbr_blocks);
    ASSERT (fwrite (&output_dbyte, 2, 1, output));
    offset = ftell (output) + nbr_blocks * 4;

    for (block_nbr = 0; block_nbr < nbr_blocks; block_nbr++)
      {
        output_qbyte = htonl (offset);
        ASSERT (fwrite (&output_qbyte, 4, 1, output));
        offset += sizes [block_nbr];
      }
    fseek (scratch, 0, SEEK_SET);       /*  Back to start of scratch file    */
    while ((outsize = fread (outbuf, 1, BLOCK_SIZE, scratch)) != 0)
        ASSERT (fwrite (outbuf, 1, outsize, output));

    file_close (output);
    ftmp_close (scratch);
    mem_free (sizes);
    mem_free (inbuf);
    mem_free (outbuf);

    return (0);                         /*  No errors                        */
}


static void
have_end_of_paragraph (Bool compress)
{
    if (insize)                         /*  If there is a paragraph waiting  */
      {
        inbuf [insize++] = '\0';        /*  Terminate paragraph with null    */
        nbr_paras++;
        if ((nbr_paras == MAX_PARAS)
        || (insize > BLOCK_SIZE - 2000))
          {
            have_end_of_block (compress);
            nbr_paras = 0;
            insize = 2;
          }
      }
}

static void
have_end_of_block (Bool compress)
{
    output_dbyte = htons (nbr_paras);
    *(dbyte *) inbuf = output_dbyte;

    if (compress)
      {
        outsize = compress_block (inbuf, outbuf, insize);
        output_dbyte = htons (outsize);
        ASSERT (fwrite (&output_dbyte, 2, 1, scratch));
        ASSERT (fwrite (outbuf, 1, outsize, scratch));
        sizes [nbr_blocks] = outsize + 2;
      }
    else
      {
        output_dbyte = htons (insize);
        ASSERT (fwrite (&output_dbyte, 2, 1, scratch));
        ASSERT (fwrite (inbuf, 1, insize, scratch));
        sizes [nbr_blocks] = insize + 2;
      }
    insize = 2;                         /*  Clear input buffer               */
    nbr_paras = 0;
    nbr_blocks++;
}


/*  ---------------------------------------------------------------------[<]-
    Function: fortune_read

    Synopsis: Reads a random paragraph from the specified fortune file.  The
    fortune file is located in the current directory or on the current path.
    The paragraph of text is returned in a freshly-allocated block of memory
    that you should free afterwards using mem_free().  Returns NULL if the
    fortune file could not be opened.
    ---------------------------------------------------------------------[>]-*/

char *
fortune_read (const char *fortune_file)
{
    static Bool
        first_time = TRUE;
    FILE
        *fortunes;                      /*  FFF input stream                 */
    byte
        *inbuf,                         /*  Block read from file             */
        *outbuf;                        /*  And after decompression          */
    int
        nbr_blocks,                     /*  Number of blocks in data file    */
        nbr_paras,                      /*  Number of paragraphs in block    */
        paragraph;
    dbyte
        input_dbyte = 0,
        block_size;                     /*  Size of current block            */
    qbyte
        input_qbyte = 0,
        block_offset;                   /*  Offset of block in file          */
    char
        *para_start;                    /*  Start of paragraph               */
    Bool
        compressed;                     /*  Compressed fortune data?         */

    /*  Initialise random-number generator if necessary                      */
    if (first_time)
      {
        randomize ();
        first_time = FALSE;
      }

    /*  Look for fortunes file                                               */
    fortunes = file_locate ("PATH", fortune_file, NULL);
    if (fortunes == NULL)
        return (NULL);

    /*  Allocate working buffers                                             */
    inbuf  = mem_alloc (BLOCK_SIZE);
    outbuf = mem_alloc (BLOCK_SIZE);
    if (inbuf == NULL || outbuf == NULL)
      {
        mem_free (inbuf);
        mem_free (outbuf);
        return (NULL);
      }

    /*  Indexed Fortune File format starts with IFFCMP or IFFTXT             */
    file_read (fortunes, (char *) inbuf);
    if (memcpy (inbuf, "IFFTXT", 6) == 0)
        compressed = FALSE;
    else
        compressed = TRUE;

    while (fgetc (fortunes) != 26);     /*  Skip past file header            */

    /*  Get total number of blocks in file                                   */
    ASSERT (fread (&input_dbyte, 2, 1, fortunes));
    nbr_blocks = ntohs (input_dbyte);

    /*  Look at random block address in Toc                                  */
    fseek (fortunes, random (nbr_blocks) * 4, SEEK_CUR);
    ASSERT (fread (&input_qbyte, 4, 1, fortunes));
    block_offset = ntohl (input_qbyte);

    /*  Go read, then decompress the block                                   */
    fseek (fortunes, block_offset, SEEK_SET);
    ASSERT (fread (&input_dbyte, 2, 1, fortunes));
    block_size = ntohs (input_dbyte);

    if (compressed)
      {
        ASSERT (fread (inbuf, 1, block_size, fortunes));
        expand_block (inbuf, outbuf, block_size);
      }
    else
        ASSERT (fread (outbuf, 1, block_size, fortunes));

    /*  Chose random paragraph from block                                    */
    input_dbyte = *(dbyte *) outbuf;
    nbr_paras   = ntohs (input_dbyte);
    paragraph   = random (nbr_paras);
    para_start  = (char *) outbuf + 2;
    while (paragraph--)
        para_start = strchr (para_start, '\0') + 1;

    para_start = mem_strdup (para_start);

    /*  Release allocated memory                                             */
    fclose (fortunes);
    mem_free (inbuf);
    mem_free (outbuf);

    return (para_start);
}
