//=-- ResourceManager.cpp - Contexts we can load resources from ---*- C++ -*--=
//
//  The ResourceManager class provides a context for loading themes and other 
//  resources.  If AllowUserResources is true (set only by the server 
//  configuration file), then this class first searches the user's local 
//  resource directories for files.  If it is not found there or if 
//  AllowUserResources is false, then the Server Resource locations are 
//  checked.  If the file is not found there either, then an error is returned.
//
//  It is possible for the user to specify many locations in the filesystem to
//  load resources (themes/plugins) from.  Each filesystem location has a 
//  unique location in the filesystem as well as in web server space.  Thus,
//  each location has a path and a URL that corresponds to it.  This class
//  keeps track of this kind of information.
//
//=---------------------------------------------------------------------------=
//  This file is copyright (c) 1997-2000 Chris Lattner
//=---------------------------------------------------------------------------=

#include "ResourceManager.h"
#include "MSFilename.h"
#include "Table.h"
#include "PlatformAbstraction.h"
#include "Options.h"

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

// If compiling with debugging on, look for debugging versions of plugins...
#ifdef _DEBUG
#define PLUGINS_EXT "_g"
#else
#define PLUGINS_EXT ""
#endif

// Value to expand %u into.  Set by constructor
String ResourceManager::CurrentUserName; 

int ResourceManager::AllowUserResources = 1;

// Keep track of directories already checked.
LinkedList<String> *ResourceManager::ResourcesSearched;

// The Key is the theme name.  The first part of the value is the Path, the 
// second portion is the theme URL.
Table<CSString, DataPair<MSFilename,String> > *ResourceManager::ThemesFound[2];

// PluginsFound - Table of ResourceLocations for plugin files...
Table<CSString, DataPair<MSFilename,String> > *ResourceManager::PluginsFound[2];


ResourceManager::ResourceManager() { 
  AllowUserResources = 1; 
  CurrentUserName = PlatformAbstraction::GetUserName();
  ResourcesSearched = new LinkedList<String>();
  ThemesFound[0] = new Table<CSString, DataPair<MSFilename,String> >();
  ThemesFound[1] = new Table<CSString, DataPair<MSFilename,String> >();

  PluginsFound[0] = new Table<CSString, DataPair<MSFilename,String> >();
  PluginsFound[1] = new Table<CSString, DataPair<MSFilename,String> >();
}

ResourceManager::~ResourceManager() { 
  delete ResourcesSearched;
  delete ThemesFound[0];
  delete ThemesFound[1];
  delete PluginsFound[0];
  delete PluginsFound[1];
}

// PrintThemes - Print out all of the themes (with interesting info) in a
// nice format, used for the -T commandline parameter.
//
void ResourceManager::PrintThemes() {
  for (int UsrSvr = 0; UsrSvr <= 1; UsrSvr++) {
    Table<CSString, DataPair<MSFilename,String> >::Iterator I = ThemesFound[UsrSvr]->GetStart();
    
    while (I) {
      cout << endl       << I->GetPrimary() << " Theme:\n";
      cout << "  Path: " << I->GetSecondary().GetPrimary()   << endl;
      cout << "  URL:  " << I->GetSecondary().GetSecondary() << endl;
      
      I++;
    }
  }
}

// AddResourceLocation - The URL field may contain a variable "%u" that is 
// expanded to the current user name.  This is not valid, however, in user 
// config files.  Use the ($ms.Username) variable instead.
//
int ResourceManager::AddResourceLocation(const MSFilename &resPath, 
                                         const String &resURL, 
                                         int ServerLocation) {
  String TmpResPath((const String&)resPath);
  String    ResURL (resURL);

  // Get rid of the %U that may be in the ServerResourcesURL and SymlinkURL vars.
  ResURL.ReplaceSubString("%U", CurrentUserName);
  ResURL.ReplaceSubString("%u", CurrentUserName);

  // Remove all trailing / or \ charaters from the filename, because windows 
  // makes stat return 'not there' for a directory path that ends in / or \.
  while (1) {
    char c = TmpResPath[TmpResPath.Length()-1];
    if (c == '/' || c == '\\')      // Chop off trailing /'s for windows.
      TmpResPath.Left(TmpResPath.Length()-1);  
    else 
      break;    // Done.  Don't chop off the end of the string...
  }

  MSFilename ResPath = TmpResPath;

  // Check to see if it's valid...
  if (!ResPath.Valid() || !ResPath.Exists() || ResURL.Length() == 0) return 1;
  
  // See if we have already searched this directory.
  if (ResourcesSearched->SearchList(ResPath)) 
    return 0;  // Already searched this directory.

  // Latest Resource Paths have highest precedence so that the server paths
  // may be overridden by user paths...
  //
  ResourcesSearched->AddToHead(ResPath);  // Keep track for next time.

  MSFilename ThemeDir = ResPath+"/Themes";

  if (ThemeDir.Exists()) {
    LinkedList<String> ThemesAvailable;

    // Check to see if there are themes in the directory...
    if (!PlatformAbstraction::ScanDirectory(ThemeDir, ThemesAvailable, 
                                            PlatformAbstraction::Directory)) {
      String ThemeName;
      while (ThemesAvailable.GetLength() > 0) {
        ThemeName = ThemesAvailable.GetHead()->Data;
        MSFilename Filename = ThemeDir + "/" + ThemeName + "/" + 
	                      ThemeName + ".cfg";
        
        // If the file exists... add it!
        if (Filename.Exists()) {
          // Calculate the resource URL...
          String URL = ResURL+"/Themes/"+ThemeName+"/";

          ThemesFound[ServerLocation]->SetElementTo(ThemeName, 
                           DataPair<MSFilename, String>(Filename, URL));
        }
        
        ThemesAvailable.RemoveHead();
      }
    }
  }

  MSFilename PluginDir = ResPath+"/Plugins" PLUGINS_EXT;

  if (PluginDir.Exists()) {
    LinkedList<String> PluginsAvailable;
    // Check to see if there are plugins in the directory...
    if (!PlatformAbstraction::ScanDirectory(PluginDir, PluginsAvailable, 
                                            PlatformAbstraction::Directory)) {
      String PluginName;
      while (PluginsAvailable.GetLength() > 0) {
        PluginName = PluginsAvailable.GetHead()->Data;
        MSFilename Filename = PluginDir + "/" + PluginName + "/" + PluginName 
                                        + PLUGINS_EXT +
                                        + PlatformAbstraction::PluginSuffix;
       
        // If the file exists... add it!
        if (Filename.Exists()) {
          // Calculate the resource URL...
          String URL = ResURL+"/Plugins" PLUGINS_EXT "/"+PluginName+"/";

          PluginsFound[ServerLocation]->SetElementTo(PluginName, 
                              DataPair<MSFilename, String>(Filename, URL));
        }

        PluginsAvailable.RemoveHead();
      }
    }
  }

  MSFilename BuiltIn = ResPath+"/Plugins" PLUGINS_EXT "/BuiltIn";
  if (BuiltIn.Exists()) {
    PluginsFound[ServerLocation]->SetElementTo("BuiltIn", 
                        DataPair<MSFilename, String>(BuiltIn, ResURL));

    // If we haven't already found our builtin directory, update the global
    // understanding of where that is...
	if (MSOptions::GetBuiltInPluginResURL() == String("")) {
      ResourceManager::FindPlugin("BuiltIn", 
                                  MSOptions::GetBuiltInPluginResPath(), 
                                  MSOptions::GetBuiltInPluginResURL());
	}
  }

  return 0;
}


// PrintResourcePaths - Print out all of the resource paths currently being 
// used.  Each path is printed on a seperate line with "Indent" spaces before
// each.
//
void ResourceManager::PrintResourcePaths(int Indent) {
  if (ResourcesSearched == 0) return;

  String IndentStr;
  IndentStr.Fill(' ', Indent);

  LinkedList<String>::Iterator I = ResourcesSearched->GetIterator();

  while (I) {
    cout << IndentStr << *I << endl;
    I++;
  }
}


// FindTheme - Searches the user and server paths and returns the filename 
// of the theme.  If the theme isn't found, an nonzero value is returned.
// The result of the search is contained in the ReturnPath and ReturnURL
// parameters...
//
int ResourceManager::FindTheme(const String &ThemeName,
                               String &ReturnPath, String &ReturnURL) {
  Table<CSString, DataPair<MSFilename,String> > *TableFound = 0;

  if (AllowUserResources && ThemesFound[UserResource]->InTable(ThemeName)) {
    TableFound = ThemesFound[UserResource];
  } else {
    if (ThemesFound[ServerResource]->InTable(ThemeName)) 
      TableFound = ThemesFound[ServerResource];
  }

  if (TableFound == 0) return -1;  // Not found in either user or server stuff...

  DataPair<MSFilename, String> Theme((*TableFound)[ThemeName]);
  ReturnPath = Theme.GetPrimary();
  ReturnURL  = Theme.GetSecondary();
  return 0;
}


// FindPlugin - Searches the user and server paths and returns the filename 
// of the specified plugin.  If the plugin isn't found, an nonzero value is 
// returned.  The result of the search is passed back in the ReturnPath and 
// ReturnURL parameters...
//
int ResourceManager::FindPlugin(const String &PluginName,
                                String &ReturnPath, String &ReturnURL) {
  Table<CSString, DataPair<MSFilename,String> > *TableFound = 0;

  if (AllowUserResources && PluginsFound[UserResource]->InTable(PluginName)) {
    TableFound = PluginsFound[UserResource];
  } else {
    if (PluginsFound[ServerResource]->InTable(PluginName)) 
      TableFound = PluginsFound[ServerResource];
  }

  if (TableFound == 0) 
    return -1;                  // Not found in either user or server stuff...

  DataPair<MSFilename, String> &PluginRec = (*TableFound)[PluginName];
  ReturnPath = PluginRec.GetPrimary();
  ReturnURL  = PluginRec.GetSecondary() + "/Plugins" PLUGINS_EXT "/" + 
               PluginName + "/";
  return 0;
}


// GetPluginFiles - Get a list of all of the plugins found in resource 
// directories...
//
LinkedList<DataPair<MSFilename,String> > ResourceManager::GetPluginFiles() {
  LinkedList<DataPair<MSFilename,String> > Result;
  if (PluginsFound[ServerResource] == 0) return Result;

  if (AllowUserResources) {    // Search the user's resources first...
    Table<CSString, DataPair<MSFilename,String> >::Iterator I 
      = PluginsFound[UserResource]->GetStart();

    while (I) {
      Result.AddToTail(I.GetValue());
      I++;
    }
  }

  Table<CSString, DataPair<MSFilename,String> >::Iterator I 
    = PluginsFound[ServerResource]->GetStart();
  
  while (I) {  // Search Server stuff last...
    Result.AddToTail(I.GetValue());
    I++;
  }
  
  return Result;
}
