//=-- AccessLogManager.cpp - Keeps track of all of the AccessLog variants ----=
//
//  The class organizes all of the AccessLog plugins.  When the init method is
//  called, the AccessLogManager creates AccessLog instances to correspond to
//  each individual log that needs to be read from the system.
//
//=---------------------------------------------------------------------------=
//  This file is copyright (c) 1997-2000 Chris Lattner
//=---------------------------------------------------------------------------=

#include "AccessLogManager.h"
#include "AccessFilterPlugin.h"
#include "ConfigFileSection.h"
#include "Options.h"
#include "AccessFormatPlugin.h"
#include "VarTable.h"

#include "AFLimitations.h"

int AccessLogManager::NumHitsReadToday = 0;
int AccessLogManager::MaxHitsPerDay = -1;

AccessLogManager::AccessLogManager() {
  LastLogRead = 0;
  NumAccesses = 0;
  NumAccessLogs = 0;
}

// Destructor - Free AccessLogs, all of the AccessFilterPlugin's, and
//   clear out the AccessFilters list.
//
AccessLogManager::~AccessLogManager() { 
  Table<String, AccessLogRecord>::Iterator I = AccessLogs.GetStart();

  // Loop through access logs, freeing them...
  while (I) {
    delete I.GetValue().P;
    I.GetValue().P = 0;
    I++;
  }

  AccessLogs.Clear();

#if 0
  LLLink<AccessFilterPlugin*> *Head;
  while ((Head = AccessFilters.GetHead())) {
    delete Head->Data;
    AccessFilters.RemoveHead();
  }
#endif
}


// AddFilter - Add a filter command to the internal list... creating plugins as
// needed.
//
int AccessLogManager::AddFilter(const VTExp *Exp) {
  VTListExp *List;

  if (Exp->GetExpType() == VTExp::StringTy) {
    List = new VTListExp(-1);

    String Value(Exp->GetStringValue()), Token;
    
    while (Value.Length() != 0) {
      Value.GetParameter(Token);               // Get the filter name...
      List->AddToTail(new VTStrExp(-1, Token));
    }

  } else {
    List = (VTListExp*)Exp;
    List->AddRef();                     // Don't free list at end of function
  }

  Exp = List->GetListElement(0);

  if (Exp->GetExpType() != VTExp::StringTy) {
    if (Exp->GetLineNo() != -1) 
      cout << "Line " << Exp->GetLineNo() << ": ";
    cout << "Expected AccessFilter name, instead got: " << Exp << endl;
    List->UnRef();                    // Release list if we just created it...
    return 1;
  }

  String Name = Exp->GetStringValue();
  AccessFilterPlugin *Plugin = AccessFilterPlugin::CreatePlugin(Name);  

  if (Plugin == 0 || Plugin->Initialize(List)) { // Initialize plugin...
    cout << "Could not find Access Filter plugin for statement '" << Exp
         << "' in [Global] section! Ignoring.\n";

    List->UnRef();                    // Release list if we just created it...
    return 1; 
  }

  AccessFilters.AddToTail(Plugin);    // Add filter to end of active list...

  List->UnRef();                      // Release list if we just created it...
  return 0;
}

// Add all of the AccessFilters to our access filter list...
void AccessLogManager::ScanConfigFileForAccessFilters() {
  LinkedList<CFileSection>::Iterator Section = 
                 MSOptions::ConfigFile->GetIterator();

  while (Section) {
    // Scan for the global section...
    if (Section->GetSectionName() == String("Global")) {
      Table<CSString,VTExp*>::Iterator I = Section->GetStatements().GetStart();

      while (I) {
        const CSString &Key = I.GetKey();
        VTExp *Value = I.GetValue();

        if (Key == CSString("Filters")) {
          // Old style?  Just one filter perhaps?
          if (Value->GetExpType() == VTExp::StringTy) {
            AddFilter(Value);
          } else {
            VTListExp *List = (VTListExp*)Value;
            int Index = 0;

            while (1) {
              VTExp *Value = List->GetListElement(Index++);
              if (Value == 0) break;

              AddFilter(Value);
            }
          }

        } else {       // Check for numbered settings...
          CSString Temp = Key;
          Temp.Left(6);    // Truncate to strlen("Filter")
       
          // Check for the Filter# settings...
          if (Temp == CSString("Filter")) 
            AddFilter(Value);
        }

        I++;
      }
    }

    Section++;
  }
}

// init - Create the AccessLog plugin and initialize the AccessFilters list
//
int AccessLogManager::init() {
  AccessLog *AccessLog = 0;
  ReadFirstAccesses = 0;

  // Used to implement the -N option...
  NumHitsReadToday = MSOptions::MaxAccesses;

  // Add all of the AccessFilters to our access filter list...
  ScanConfigFileForAccessFilters();

  VTExp *AL = MSOptions::AccessLog;

  if (AL->GetExpType() == VTExp::StringTy) {
    String Filename = AL->GetStringValue();

    if (!AccessLogs.InTable(Filename)) {
      // See if the AccessLogType is in the table of available AccessLog Plugins
      AccessLog = AccessLog::Create("", Filename, "");

      if (AccessLog == 0) {
        cout << "Couldn't open AccessLog '" << AL << "'!\n";
        return 1;
      }

      NumAccessLogs++;
      AccessLogs[Filename] = AccessLogRecord(AccessLog, 0);
    }
  } else {   // Access log parameter is a list...
    VTListExp *List = (VTListExp*)AL;
    int Index = 0;

    while ((AL = List->GetListElement(Index++))) {
      // Is it just a string, ie not a recursive list?  If so, just create 
      // another accesslog plugin without a domain or type specified... 
      //
      if (AL->GetExpType() == VTExp::StringTy) {
        String Filename = AL->GetStringValue();
        if (!AccessLogs.InTable(Filename)) {
          AccessLog = AccessLog::Create("", Filename, "");
          if (AccessLog == 0) {
            cout << "Couldn't open AccessLog '" << AL << "'!\n";
            return 1;
          }

          NumAccessLogs++;
          AccessLogs[Filename] = AccessLogRecord(AccessLog, 0);
        }

      } else {    // It must be a list of values...
        VTListExp *SubList = (VTListExp*)AL;
        VTExp *Filename   = SubList->GetListElement(0);
        VTExp *Domain     = SubList->GetListElement(1);
        VTExp *PluginName = SubList->GetListElement(2);

        // Filename is required...
        if (!Filename || Filename->GetExpType() != VTExp::StringTy) {
          cout << "  AccessLog statement expected a filename, instead got '" 
               << Filename << "' in AccessLogRecord = " << SubList << endl;
          return 1;
        }

        String SFilename = Filename->GetStringValue();

        if (!AccessLogs.InTable(SFilename)) {
          if ((Domain     &&     Domain->GetExpType() != VTExp::StringTy) || 
              (PluginName && PluginName->GetExpType() != VTExp::StringTy)) {
            cout << "  AccessLog statement expected string parameters for "
                    "optional domain and Access Format parameters.  Value = " 
                 << SubList << endl;
            return 1;
          }

          AccessLog = AccessLog::Create(PluginName ? 
                                            PluginName->GetStringValue() 
                                          : String(""), 
                                        SFilename,
                                        Domain ? 
                                            Domain->GetStringValue()
                                          : String(""));

          if (AccessLog == 0) {
            cout << "Couldn't open AccessLog '" << AL << "'!\n";
            return 1;
          }

          NumAccessLogs++;
          AccessLogs[SFilename] = AccessLogRecord(AccessLog, 0);
        }
      }
    }
  }


  if (AccessFilters.GetLength() == 0) {   // Specify the default plugins...
    VTStrExp *Str = new VTStrExp(-1, "UnEscapeURL");
    AddFilter(Str);
    Str->UnRef();

    Str = new VTStrExp(-1, "RemoveIndexFilename,index.html,"
                       "index.shtml,index.htm,/");
    AddFilter(Str);
    Str->UnRef();
  }

  if (MaxHitsPerDay != -1) {
    AccessFilterPlugin *P = new AFLimitations();

    VTListExp *List = new VTListExp(-1);
    List->AddToTail(new VTStrExp(-1, "AFLimitations"));
    List->AddToTail(new VTStrExp(-1, String::IntToStr(MaxHitsPerDay)));

    P->Initialize(List);
    List->UnRef();
    AccessFilters.AddToTail(P);
  }

  return 0;
}


// ReadAccess - Load an access from the plugin and then apply all of the
//   required AccessFilterPlugins to the access.
//
AccessFormatPlugin *AccessLogManager::ReadAccess() {
  AccessFormatPlugin *Result = 0;

  if (!ReadFirstAccesses) {
    Table<String, AccessLogRecord>::Iterator I = AccessLogs.GetStart();

    // Loop through access logs, reading the first accesses from the logs...
    while (I) {
      I.GetValue().S = I.GetValue().P->ReadAccess();
      I++;
    }

    ReadFirstAccesses = 1;
  }

  if (LastLogRead) {
    // Just read from a log, update that log to the next record in the log 
    // file...  At the end of the log file, ReadAccess returns null.
    //
    LastLogRead->S = LastLogRead->P->ReadAccess();
  }

  do {            // Loop until something isn't filtered out...
    // Simulate EOF if passed limit set by -N command line option...
    if (--NumHitsReadToday == -1) 
      return 0;

    Table<String,AccessLogRecord>::Iterator I = AccessLogs.GetStart();
    AccessLogRecord *EarliestFound = 0;
    Date EarliestDate;

    // Loop through all of the access logs, looking for the earliest date...
    while (I) {
      AccessFormatPlugin *AF = I.GetValue().S;

      if (AF && (EarliestFound == 0 ||
          AF->GetDate() < EarliestDate)) {
        EarliestDate = AF->GetDate();
        EarliestFound = &I.GetValue();
      }

      I++;
    }

    if (EarliestFound == 0)     // All logs are now empty...
      return 0;

    LinkedList<AccessFilterPlugin*>::Iterator AFI = AccessFilters.GetIterator();
    Result = EarliestFound->S;

    LastLogRead = EarliestFound;

    while (AFI && Result) {          // Loop through all of the filters
      if ((*AFI)->FilterAccess(*Result)) {
        // Consume the access... so that it won't come up next time...
        LastLogRead->S = LastLogRead->P->ReadAccess();

        // If a filter returns true, discard it...
        Result = 0;
      }
      AFI++;
    }

    NumAccesses++;

  } while (Result == 0);               // this access and go to the next one.

  return Result;
}


// LoadState - Load current state from serialization record...
void AccessLogManager::LoadState(Serialize &I) {
  String Filename;
  int i;
  I >> i;   // Read the number of access logs serialized to the file...

  while (i--) {
    I >> Filename;
    if (AccessLogs.InTable(Filename)) {
      AccessLogs[Filename].P->LoadState(I);
    } else {
      // How do I skip over serialized data?  I might need to 
      // enhance serialization..."
#ifndef WINDOWS
#warning "Not finished!"
#endif
    }
  }
}


// SaveState - Save current state out to a serialization record...
void AccessLogManager::SaveState(Serialize &O) {
  Table<String, AccessLogRecord>::Iterator I = AccessLogs.GetStart();

  O << NumAccessLogs;

  // Loop through access logs, serializing them... so we remember where they 
  // were.  Notice that access logs expect that you will read one extra entry
  // from the log that is never used... as such, they serialize their state 
  // to that prior to reading the last element...
  //
  while (I) {
    O << I.GetKey();
    I.GetValue().P->SaveState(O);
    I++;
  }
}
