//=-- AccessLog.cpp - access_log file manipulator -----------------------------=
//
//   This class encapsulates the notion of a single access_log file.  The 
//   source of the file is abstracted away through the FileStream plugin 
//   interface, and the format of the log is abstracted away through the 
//   AccessFormatPlugin interfaces.  This class simply coordinates access 
//   between the two.
//
//=----------------------------------------------------------------------------=
//  This file is copyright (c) 1997-2000 Chris Lattner
//=----------------------------------------------------------------------------=

#include "AccessLog.h"
#include "Options.h"
#include "FileStreamPlugin.h"
#include "AccessFormatPlugin.h"
#include "Serialize.h"

#include <sys/types.h>       // Needed for sys/stat.h
#include <sys/stat.h>        // stat

AccessLog::AccessLog(const String &PluginName, const MSFilename &filename, 
                     const String &Domain, int &Error) : Filename(filename) {
  AccessFormat = 0;
  NumAccesses = 0;

  struct stat StatBuf;
  if (stat(Filename, &StatBuf) == 0) {
    LogChangedTime = StatBuf.st_mtime;   // Save last modified time of log...
    if (StatBuf.st_size == 0) {   // File is zero length.
      AccessFormat = 0;
      File = 0;
      Error = 0;
      return;
    }
  }


  // Open the access log file
  File = FileStreamPlugin::OpenFileStream(Filename,FileStreamPlugin::OPEN_READ);

  if (File == 0) {
    cout << "Error opening Access Log file '" << Filename << "'!!\n";
    Error = 1;
    return;
  }


  // Figure out what type of access filter we should be using...
  if (PluginName.Length() != 0) {        // Access filter explicitly specified?
    AccessFormat = AccessFormatPlugin::CreatePlugin(PluginName);

    if (AccessFormat == 0) {
      cout << "Error creating AccessFormat plugin '" << PluginName 
           << "' for access log '" << Filename << "'!\n";
      Error = 1;
      return;
    }
  } else {
    // Read in a line... to use for autodetection of an AccessFormat
    char Buffer[10000];

    // Don't worry about where this leaves the file position, this gets reset
    // later anyways before the constructor is left...
    File->getline(Buffer, 10000);

    if (File->eof()) {
      cout << "Access Log file '" << Filename << "' is empty!!\n";
      Error = 1;
      return;
    }

    // Access the AccessFormat plugin mechanism to select a filter that 
    // recognizes the specified line of the log file...
    //
    String Line(Buffer);
    if (Line[Line.Length()-1] == '\r')
      Line.Left(Line.Length()-1);  // MSDOS log files end with \r\n

    AccessFormat = AccessFormatPlugin::SelectPlugin(Line);

    if (AccessFormat == 0) {
      cout << "Error creating AccessFormat plugin for access log '" 
           << Filename << "'!\n  Unrecognized access_log format: Could not"
              " parse first line:\n" << Buffer << endl << endl;
      Error = 1;
      return;
    }    
  }

  AccessFormat->SetDomain(Domain);
  AccessFormat->SetAccessLogFilename(Filename);

  if (MSOptions::CatchupAccessLog >= 0) {
    char Buffer[10000];
    if (!File->seek(0, FileStreamPlugin::SeekEnd)) { // Seek to the end of file.
      long FileLength = File->tell();

      File->seek(MSOptions::CatchupAccessLog*FileLength/100);
      File->getline(Buffer, 10000, '\n');        // Get to the end of the line
    } else {
      cout << "AccessLog: " << Filename << " could not calculate file length, "
              "catchup command ignored.\n";
    }
  } else {
    if (File->seek(0))
      cout << "AccessLog: " << Filename << " error seeking to zero byte!\n";
  }

  LastStreamPosition = File->tell();
  Error = 0;
}

AccessLog::~AccessLog() {
  if (MSOptions::Verbose && File == 0)
    cout << "  " << Filename << " has been completed.\n";

  //  delete File;

  // PluginManager reclaims plugins when they die...
  //  delete AccessFormat;
  
  if (MSOptions::Verbose && NumAccesses > 1)
    cout << "  Parsed " << (NumAccesses-1) << " accesses from " 
         << Filename << "\n";
}

// ReadAccess - Read one line from the access log and return a pointer to the
// AccessFormatPlugin that it corresponds to.  If EOF is hit, a null pointer 
// is returned...
//
AccessFormatPlugin *AccessLog::ReadAccess() {
  if (File == 0) return 0;         // File is closed, in LoadState()

  char Buffer[10000];
  String Line;

  Buffer[0] = Buffer[1] = -1;

  do {
    if (MSOptions::Debug) 
      if (Buffer[0] != -1 && Buffer[1] != -1)
        cout << "BAD ACCESS LINE: '" << Buffer << "'\n";

    LastStreamPosition = File->tell();   // Save from before we read an access.

    File->getline(Buffer, 10000);  // Read in a line...

    if (File->eof())               // EOF?
      return 0;

    NumAccesses++;
    Line = Buffer;

    if (Line[Line.Length()-1] == '\r')
      Line.Left(Line.Length()-1);  // MSDOS log files end with \r\n

  } while (AccessFormat->ParseAccess(Line));

  return AccessFormat;
}

// LoadState - Load current state from serialization record...
void AccessLog::LoadState(Serialize &I) {
  int EOFFound;

  I >> EOFFound;

  // Last time, we hit end of file... if file is unmodified, ignore it.
  if (EOFFound) { 
    struct stat StatBuf;
    I >> LogChangedTime;

    // If we hit the end of log file last time we were reading it, and if the
    // file has not been modified since then, just ignore the file, because it
    // can't have any new accesses, and this avoids doing an extremely expensive
    // seek operation on files that take a long time to seek through (.gz files)
    //
    if (stat(Filename, &StatBuf) == 0 && LogChangedTime == StatBuf.st_mtime) {
      I >> LastStreamPosition;   // Just read it 'cause it's there.

      if (MSOptions::Debug)
        cout << "Accesslog: " << Filename << " is " << LastStreamPosition 
             << " bytes long, but has been read completely.\n";
      delete File;               // Close the file...
      File = 0;                  // Mark the file as not around anymore...
      return;
    }
  }

  I >> LastStreamPosition;

  if (MSOptions::Debug)
    cout << "Accesslog: " << Filename << " starting at byte: " 
         << LastStreamPosition << endl;

  // Don't seek back to original position if catching up...
  if (MSOptions::CatchupAccessLog < 0 && File != 0) {  
    if (File->seek(LastStreamPosition)) { // Seek to the starting position
      if (MSOptions::Debug)
        cout << "         : " << Filename << " could not seek to starting "
                "position.\n           Starting from the beginning.\n";

      LastStreamPosition = 0;             // Log got rotated or something.
      if (File->seek(0)) {
        cout << "AccessLog: " << Filename << " COULD NOT SEEK TO '0' byte!\n";
      }
    }
  }
}


// SaveState - Save current state out to a serialization record...
//
void AccessLog::SaveState(Serialize &O) {
  if (File == 0) {                       // Still at EOF?
    O << (int)1 << LogChangedTime;       // Save last modified time of log...

  } else if (File->eof()) {              // EOF?
    struct stat StatBuf;
    if (stat(Filename, &StatBuf) == 0)
      O << (int)1 << StatBuf.st_mtime;   // Save last modified time of log...
    else
      O << (int)0;
  } else {
    O << (int)0;                         // Not at end of stream!
  }
  O << LastStreamPosition;
  
  if (MSOptions::Debug)
    cout << "Accesslog " << Filename << " stopping at byte: " 
         << LastStreamPosition << endl;
}
