//=-- PluginManager.cpp - Maintains plugins, makes sure they all play nicely -=
//
//  The class organizes all of the plugins.  It is the main dispatch class that
//    interfaces with all of the plugin classes.
//
//=---------------------------------------------------------------------------=
//  This file is copyright (c) 1997-2000 Chris Lattner
//=---------------------------------------------------------------------------=

#include "PluginManager.h"
#include "AccessLogManager.h"
#include "Serialize.h"
#include "PagePlugin.h"
#include "Options.h"
#include "MSFilename.h"
#include "SkelRep.h"
#include "ResourceManager.h"
#include "LinkedList.h"
#include "ConfigFileSection.h"
#include "PlatformAbstraction.h"

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

LinkedList<Plugin *> &PluginManager::PluginList() {
  static LinkedList<Plugin *> PList;
  return PList;
}

int PluginManager::AddPlugin(Plugin *P) {
  PluginList().AddToTail(P);
  return 0;
}

int PluginManager::RemovePlugin(Plugin *P) {
  LinkedList<Plugin *>::Node *N = PluginList().SearchList(P);
  if (N) {
    PluginList().RemoveNode(N);
    return 1;
  }
  return 0;
}

// ProcessAccess - Step through the linked list of plugins and let each 
//   plugin have a chance to process the access.
//
void PluginManager::ProcessAccess(AccessFormatPlugin &A) {
  LinkedList<Plugin *>::Iterator I = PluginList().GetIterator();

  for (; I; I++) {
    if ((*I)->GetType() == Plugin::PAGE_PLUGIN) {
      ((PagePlugin*)*I)->MetaProcessAccess(A);
    }
  }
}

PluginManager::PluginManager() : Initialized(0) { }

PluginManager::~PluginManager() {
  if (Initialized) {
    // Free all PluginList memory
    LinkedList<Plugin *> &PList = PluginList();
    LLLink<Plugin *> *Head;
    while ((Head = PList.GetHead())) {
      delete **Head;
      PList.RemoveHead();
    }
  }
}

void PluginManager::LoadDynaPlugins() {
  LinkedList<DataPair<MSFilename,String> > PluginsAvailable = 
       ResourceManager::GetPluginFiles();
  String PluginName;

  // Loop over all directories in plugin directory...
  while (PluginsAvailable.GetHead() != 0) {  
    const MSFilename &Filename = PluginsAvailable.GetHead()->Data.GetPrimary();
    const String     &URL      = PluginsAvailable.GetHead()->Data.GetSecondary();
    String PluginName = Filename.FilenamePortion();

    // Special case of the BuiltIn Plugin Directory
    if (PluginName == String("BuiltIn")) {
      PluginsAvailable.RemoveHead();
      continue;
    }

    // Remove the extension from the plugin to get the name of the plugin
    int DotIdx = PluginName.strrchr('.');
    if (DotIdx != -1) PluginName.Left(DotIdx);

    Plugin::SetPluginSource(Filename, URL);  // So it knows where it came from
    const char *ErrorMessage = PlatformAbstraction::LoadPlugin(Filename);
    
    if (MSOptions::Verbose) {
      if (ErrorMessage) {      // Error loading plugin...
        cout << "Failed to load promising external plugin: " 
             << PluginName << " from: " << Filename << endl 
             << "  Error = " << ErrorMessage << endl;;
      } else {                 // Success loading plugin...
        cout << "Loaded plugin: " << PluginName;

        if (MSOptions::Debug)
          cout << " from: " << Filename;
        cout << endl;
      }
    }
    Plugin::ClearPluginSource();

    PluginsAvailable.RemoveHead();
  }
}

// PluginManager init method - Create and initialize plugins...
//
// Responsibilities:
//   1. Load and update Plugins.dat file from Plugins directory
//   2. Read the page layout files
//
int PluginManager::init() { 
  // 1. Load and update Plugins.dat file from Plugins directory
  LoadDynaPlugins();

  if (MSOptions::PrintPlugins) {
    Plugin::PrintPlugins();
    return 1;
  }

  // 2. Read the page layout files and create the appropriate plugin objects...
  InitOutputFiles();

  Initialized = 1;

  return 0;
}

// InitOutputFiles - This function does a scan through all of the files used 
// for the .html shell files.  Its goal is to make a copy of the plugins from
// the PluginStubs list into the PluginList list.  These plugins are all 
// initialized and ready to go.
//
void PluginManager::InitOutputFiles() { 
  LLLink<CFileSection> *SLink;
  int Update;

  SLink = MSOptions::ConfigFile->GetHead();

  while (SLink != 0) {
    if (SLink->Data.GetSectionName() == String("Global")) {
      SLink = SLink->GetNext();       // Skip the global section
      continue;
    }

    if (MSOptions::Debug)
      cout << "PluginManager.cpp:" << __LINE__ 
           << " - Scanning section: " << SLink->Data.GetSectionName();
    
    VarTable &KeyTable = SLink->Data.GetStatements();
    int ErrorScanning = 0;

    if (KeyTable.InTable("Input") && KeyTable.InTable("Output")) {
      MSFilename InputFile  = KeyTable["Input"]->GetStringValue();
      MSFilename OutputFile = KeyTable["Output"]->GetStringValue();

      if (KeyTable.InTable("Update"))
        Update = KeyTable["Update"]->GetStringValue().atoi();
      else
        Update = -1;
      
      // Change to the theme directory so that files can be found without 
      // absolute paths... Save the current directory so that we can restore 
      // it.
      //
      MSFilename OldDir = PlatformAbstraction::GetCWD();
      PlatformAbstraction::ChDir(SLink->Data.GetCWD());

      ErrorScanning = ScanFileForPlugins(InputFile, Update, 
                                         SLink->Data.GetVariables(),
                                         SLink->Data.GetCWD());

      if (MSOptions::Debug) cout << endl;

      // Restore the old directory.
      PlatformAbstraction::ChDir(OldDir);
    } else {
      ErrorScanning = 1;
    }

    if (ErrorScanning) {
      if (!KeyTable.InTable("Input"))
        cout << "No input file specified for page '" 
             << SLink->Data.GetSectionName() << "'. Ignoring page.\n";
      if (!KeyTable.InTable("Output"))
        cout << "No output file specified for page '" 
             << SLink->Data.GetSectionName() << "'. Ignoring page.\n";

      // Remove the link.
      //
      if (MSOptions::Debug)
        cout << "PluginManager.cpp:" << __LINE__ << " - Removed section '" 
             << SLink->Data.GetSectionName() << "'\n";

      if (SLink->GetPrev() == 0) {
        MSOptions::ConfigFile->RemoveNode(SLink);
        SLink = MSOptions::ConfigFile->GetHead();
        continue;
      }

      SLink = SLink->GetPrev();
      MSOptions::ConfigFile->RemoveNode(SLink->GetNext());
    }
  
    SLink = SLink->GetNext();       // Move to the next section...
  }  

  if (MSOptions::Debug) {
    cout << "PluginManager.cpp:" << __LINE__ 
         << " - Printing PluginStubs List: " << endl;
    Plugin::PrintPlugins();
  }
}


PagePlugin *PluginManager::GetPagePlugin(const CSString &S) {
  LinkedList<Plugin*>::Iterator L = PluginList().GetIterator();

  for (; L; L++) {
    if ((*L)->GetType() == Plugin::PAGE_PLUGIN) {
      if (S == (*L)->GetName())
        return (PagePlugin*)(*L);
    }
  }

  return 0;
}

PagePlugin *PluginManager::GetPagePluginByFullName(const String &S) {
  LinkedList<Plugin*>::Iterator L = PluginList().GetIterator();

  for (; L; L++) {
    if ((*L)->GetType() == Plugin::PAGE_PLUGIN) {
      PagePlugin *P = (PagePlugin *)(*L);
      if (S == P->GetPluginFullName())
        return P;
    }
  }

  return 0;
}

// ScanFileForPlugins - This function does exactly what it says.  It scans one
//   shell .html file for all MSPlugin tags.  In then fills in the PluginList
//   linked list with the plugins that it finds.  It keeps track of repeated 
//   plugins, making sure that two duplicate plugins do not exist in the list 
//   at any time.
//
int PluginManager::ScanFileForPlugins(const MSFilename &Filename, 
                                      int UpdateFreq, VarTable &Variables,
                                      const String &CWD) {

  // Get a handle to the skel file...
  const SkelData *Data = GetSkelFile(CWD + "/" + Filename);

  if (Data == 0) {
    cout << "Error loading skel file '" << Filename << "'!\n";
    return 1;
  }

  Data->DoScanForPlugins(UpdateFreq, Variables);
  return 0;
}

// LoadPluginData - Loads all persistant data from the MagicStats.dat file.
//   Also initializes global internal state.
//
void PluginManager::LoadPluginData() {
  String PluginName;
  char Buffer[500];
  int FoundPlugin;

  MSFilename DataFile(MSOptions::DataFilename);
  Serialize In(DataFile, 1);
  if (In.Error()) return;

  In.PrintErrors();

  if (MSOptions::Debug)
    cout << "Deserializing plugin data:\n";

  In.NextSection();
  while (!In.eof() && !In.PeekEOF()) {
    In >> Buffer;
    PluginName = Buffer;

    FoundPlugin = false;
    LinkedList<Plugin*>::Iterator I = PluginList().GetIterator();
    
    while (I && (!FoundPlugin)) {
      if (!PluginName.iCompare((*I)->GetPluginFullName())) {
        (*I)->LoadState(In);
        FoundPlugin = true;
      }
      I++;
    }

    if (MSOptions::Debug) {
      if (FoundPlugin)
        cout << "  Deserialized: " << PluginName << endl;
      else
        cout << "  Skipping unused data for: " << PluginName << endl;
    }

    In.NextSection();
  }

  In.Close();
}

// SavePluginData - Saves all persistant data to the MagicStats.dat file.
//   Also saves global internal state.
//
void PluginManager::SavePluginData() {
  MSFilename DataFile(MSOptions::DataFilename);
  Serialize Out(DataFile, 0);
  Out.PrintErrors();

  if (Out.Error()) {
    cout << "Error opening MagicStats data file '" << DataFile 
         << "' for output!\nState has been lost.\n";
    return;
  }

  if (MSOptions::Debug)
    cout << "Serializing plugin data:\n";

  LinkedList<Plugin*>::Iterator I = PluginList().GetIterator();
  while (I) {
    if (MSOptions::Debug) 
      cout << "  Serializing: " << (*I)->GetPluginFullName() << endl;

    Out.NewSection();
    Out << (*I)->GetPluginFullName();           // Section Name
    (*I)->SaveState(Out);               // Save the actual info.

    I++;
  }

  Out.Close();
}

// FlushPluginState - Purges data from plugin's internal buffers.
//
void PluginManager::FlushPluginState(int UpdateFreq) {
  if (UpdateFreq == Plugin::UpdateAll) return;
  LinkedList<Plugin*>::Iterator I = PluginList().GetIterator();

  for (; I; I++)
    if ((*I)->UpdateFreq == UpdateFreq)
      (*I)->ResetState();
}
