/*****************************************************************************\
*                                                                             *
*  Name   : nechung_oracle                                                    *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*******************************************************************************
* Copyright (c) 1991-$now By Author.  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 2 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
*****************************************************************************/

#include "nechung_oracle.h"

#include <basis/chaos.h>
#include <basis/guards.h>
#include <basis/istring.h>
#include <opsystem/byte_filer.h>
#include <loggers/console_logger.h>
#include <opsystem/filetime.h>
#include <textual/parser_bits.h>

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

#undef LOG
#define LOG(s) program_wide_logger().log(s)

const int MAX_LINE_LENGTH = 2048;

nechung_oracle::nechung_oracle(const istring &nechung_filename,
    const istring &index_filename)
: _randomizer(new chaos),
  _filename_held(new istring(nechung_filename)),
  _index_held(new istring(index_filename)),
  _number_of_fortunes(0)
{ parse_file(); }

nechung_oracle::~nechung_oracle()
{
  WHACK(_randomizer);
  WHACK(_filename_held);
  WHACK(_index_held);
}

void nechung_oracle::parse_file()
{
  FUNCDEF("parse_file");
  // below is code for comparing dates on the fortune file and the index file.
  byte_filer fortune_file(_filename_held->s(), "rb");
#ifdef DEBUG_NECHUNG
  LOG(istring("filename=") + *_filename_held + " idx file=" + *_index_held);
#endif
  if (!fortune_file.good())
    non_continuable_error(class_name(), func, "Cannot open fortune file.");

  byte_array buffer(MAX_LINE_LENGTH + 1);
    // used throughout parsing for line storage.

  byte_filer index_file(_index_held->observe(), "r");
  if (index_file.good()) {
#ifdef DEBUG_NECHUNG
    LOG("index file exists");
#endif
    file_time index_time((FILE *)index_file.file_handle());
    file_time fortune_time((FILE *)fortune_file.file_handle());
    if (index_time >= fortune_time) {
      // need to read in the list of indices
      index_file.getline(buffer, MAX_LINE_LENGTH);
      sscanf((char *)buffer.access(), "%d", &_number_of_fortunes);
#ifdef DEBUG_NECHUNG
      LOG(istring(istring::SPRINTF, "%d entries in index", 
          _number_of_fortunes));
#endif
      return;
    }
  }
  index_file.close();

  // below is code for creating the list.
  enum fortune_states {
    chowing_separators,  // looking for the breaks between fortunes.
    adding_fortunes,     // saw the separator so get ready for a new fortune.
    chowing_fortunes,    // currently in a fortune accumulating lines.
    done_parsing         // finished parsing the fortune file.
  };

  _number_of_fortunes = 0;
  fortune_states state = chowing_separators;

  int posn;
  int_array fortune_posns;  // our list of fortunes.
  while (state != done_parsing) {
#ifdef DEBUG_NECHUNG
    LOG(istring(istring::SPRINTF, "#%d", _number_of_fortunes));
#endif
    if (fortune_file.eof()) {
      // exit from the loop now...
      state = done_parsing;
      continue;
    }
    switch (state) {
      case chowing_separators: {
#ifdef DEBUG_NECHUNG
        LOG("chowseps, ");
#endif
        posn = int(fortune_file.tell());
        if (posn < 0)
          non_continuable_error(class_name(), func, "Cannot get file position.");
        fortune_file.getline(buffer, MAX_LINE_LENGTH);
#ifdef DEBUG_NECHUNG
        LOG(istring("got a line: ") + buffer);
#endif
        if (buffer[0] != NECHUNG_SEPARATION_CHARACTER) state = adding_fortunes;
        else {
          // special casing is for when we see a separator on the line
          // by itself versus when it is the beginning of a line.  if the
          // beginning of a line, we currently take that to mean the rest
          // of the line is the fortune.
          if (strlen((char *)buffer.access()) == 2) posn += 2;
          else posn++;
          state = adding_fortunes;
        }
        break;
      }
      case adding_fortunes: {
#ifdef DEBUG_NECHUNG
        LOG("add forts, ");
#endif
        fortune_posns += posn;
        _number_of_fortunes++;
        state = chowing_fortunes;
        break;
      }
      case chowing_fortunes: {
#ifdef DEBUG_NECHUNG
        LOG("chow forts, ");
#endif
        posn = int(fortune_file.tell());
        if (posn < 0)
          non_continuable_error(class_name(), func, "Cannot get file size.");
        fortune_file.getline(buffer, MAX_LINE_LENGTH);
#ifdef DEBUG_NECHUNG
        LOG(istring(istring::SPRINTF, "got a line: %s", buffer.access()));
        LOG(istring(istring::SPRINTF, "len is %d", strlen((char *)buffer.access())));
#endif
        if ( (buffer[0] == NECHUNG_SEPARATION_CHARACTER)
            && (strlen((char *)buffer.access()) == 2) )
          state = chowing_separators;
        else if (buffer[0] == NECHUNG_SEPARATION_CHARACTER) {
          posn++;
          state = adding_fortunes;
        }
        break;
      }
      case done_parsing: {
        non_continuable_error(class_name(), func, "Illegal state reached.");
      }
    }
  }
  fortune_file.close();

  // make a new index file.
  index_file.open(_index_held->observe(), "w");
  if (!index_file.good())
    non_continuable_error(class_name(), func, "Cannot open index file.");
  istring to_write(istring::SPRINTF, "%d\n", _number_of_fortunes);
  index_file.write((byte *)to_write.s(), to_write.length());
  for (int j = 0; j < _number_of_fortunes; j++) {
    to_write.sprintf("%d\n", fortune_posns[j]);
    index_file.write((byte *)to_write.s(), to_write.length());
  }
  index_file.close();
}

istring nechung_oracle::pick_random()
{
  FUNCDEF("pick_random");
#ifdef DEBUG_NECHUNG
  LOG(istring("got to ") + func);
#endif

  byte_filer fortune_file(_filename_held->s(), "rb");
  if (!fortune_file.good())
    non_continuable_error(class_name(), func, "Cannot open data file.");
  int to_display = _randomizer->inclusive(0, _number_of_fortunes - 1);

/////
///hmmm: this bit could be more efficient by just jumping to the Nth line
///      instead of reading through up to the Nth line.
/////
  byte_filer index_file(_index_held->observe(), "r");
  int chosen_posn = 0;  // which position to read the chosen line at.
  if (index_file.good()) {
    program_wide_logger().eol(log_base::NO_ENDING);
    byte_array buffer(MAX_LINE_LENGTH + 1);
    for (int i = 0; i <= to_display; i++) {
#ifdef DEBUG_NECHUNG
      LOG(istring(istring::SPRINTF, "#%d: ", i));
#endif
      index_file.getline(buffer, MAX_LINE_LENGTH);
      sscanf((char *)buffer.access(), "%d", &chosen_posn);
#ifdef DEBUG_NECHUNG
      LOG(istring(istring::SPRINTF, "%d, ", chosen_posn));
      if ((i + 1) % 5 == 0) LOG(log_base::platform_ending());
#endif
    }
#ifdef DEBUG_NECHUNG
    program_wide_logger().eol(log_base::pick_ending_for_platform());
    LOG(log_base::platform_ending());
#endif
    
  } else {
    non_continuable_error(class_name(), func, \
        istring("Could not open the index file \"") + *_index_held + "\"");
  }
  index_file.close();
/////

#ifdef DEBUG_NECHUNG
  LOG(istring(istring::SPRINTF, "about to seek @ num %d and "
      "index %d", to_display, chosen_posn);
#endif
  if (!fortune_file.seek(chosen_posn, byte_filer::FROM_START))
    non_continuable_error(class_name(), func, "Cannot seek to indexed position.");
#ifdef DEBUG_NECHUNG
  LOG("after seek");
#endif
  istring to_return;
  byte_array temp(MAX_LINE_LENGTH + 1);
  while (!fortune_file.eof()) {
    int chars_read = fortune_file.getline(temp, MAX_LINE_LENGTH);
    if (!chars_read) {
      if (!fortune_file.eof())
        non_continuable_error(class_name(), func, "Error while reading fortune.");
      else break;
    }
    if (temp[0] == NECHUNG_SEPARATION_CHARACTER) break;
    else to_return += istring((char *)temp.access());
  }
  return to_return;
}

void nechung_oracle::display_random()
{
  istring to_show = pick_random();
///  program_wide_logger().eol(log_base::NO_ENDING);
  while (parser_bits::is_eol(to_show[to_show.end()]))
    to_show.zap(to_show.end(), to_show.end());
  LOG(to_show);
///  program_wide_logger().eol(log_base::pick_ending_for_platform());
}

