//=-- ConfigRep.cpp - Classes used to represent a configuration file ---------=
//
//  This file contains the definitions of classes that are used to represent a
//  .cfg file structure.  Several classes work together to do this.
//
//=---------------------------------------------------------------------------=
//  This file is copyright (c) 1997-2000 Chris Lattner
//=---------------------------------------------------------------------------=

#include "ConfigRep.h"
#include "Options.h"
#include "ResourceManager.h"
#include "AccessLogManager.h"
#include "PlatformAbstraction.h"

extern FILE *Configin;  // Lexer input file descriptor...
extern int Configparse();
extern int Configlineno;
extern ConfigFileSection *ConfigParserResult;

const char *ConfigFilename;

ConfigFileSection *LoadConfigFile(const MSFilename &Filename) {
  if (MSOptions::Debug) 
    cout << "Loading configuration file from disk: " << Filename << endl;

  FILE *F = fopen(Filename, "r");
  if (F == 0) {
    cout << "Error reading configuration file '" << Filename << "'!\n";
    return 0;
  }

  Configin = F;  // Set the file to read from...

  // Save the filename so it may be reported if a parse error occurs.
  ConfigFilename = Filename;  

  Configlineno = 1;
  ConfigParserResult = 0;

  Configparse();     // Parse the file.  Result goes into ConfigParserResult.

  Configin = stdin;  // Restore skel lexer in to default to stdin
  fclose(F);

  ConfigFilename = 0;

  ConfigFileSection *Result = ConfigParserResult;
  ConfigParserResult = 0;

  if (Result != 0)
    Result->PostLoadFixupScan();

  return Result;
}

//=---------------------------------------------------------------------------= 
//  ConfigFileStatement                                                     
//=---------------------------------------------------------------------------= 

void ConfigFileStatement::EvaluateFile(VarTable &Vars, VarTable &Statements, 
                                       int *Lint) {
  ConfigFileStatement *Cur = this;
  VTExp *Value;

  while (Cur) {
    if (Cur->Element.Length() > 0) {
      Value = Cur->Exp->Evaluate(Vars, Lint);  // Evaluate statement...
      if (Cur->Element[0] == '$') {
        Vars[Cur->Element] = Value;
      } else {
        if (Lint && Statements.InTable(Cur->Element)) {
          cout << "  Statement '" << Cur->Element 
               << "' redefined in the same section!\n";
          *Lint = 1;
        }
        Statements[Cur->Element] = Value;
      }
    }

    Cur = Cur->Next;
  }
}

 
//=---------------------------------------------------------------------------=
//  ConfigFileSection
//=---------------------------------------------------------------------------=

LinkedList<CFileSection> 
    ConfigFileSection::EvaluateFile(const VarTable &Variables, int *Lint) {
  ConfigFileSection *Cur = this;
  LinkedList<CFileSection> Result;
  
  VarTable WorkingVars = Variables;  // Copy to a writable chunk...

  while (Cur) {
    Cur->EvaluateSection(WorkingVars, Result, Lint);
    Cur = Cur->Next;
  }
  return Result;
}

//=---------------------------------------------------------------------------=
//  VarDefaultsConfigFileSection
//=---------------------------------------------------------------------------=

// EvaluateSection - This function causes only the 'this' section to be added
// to the specified linked list... if it's a theme, it should add all of the 
// theme stuff too...
//
void VarDefaultsConfigFileSection::EvaluateSection(VarTable &Variables, 
                                                   LinkedList<CFileSection> &, 
                                                   int *Lint) {

  // If we find the Variable Defaults section, insert the variables into the 
  // VarTable only if they are not already in it.
  //
  VarTable PrivateVars;
  VarTable Statements;
  if (StmtList) StmtList->EvaluateFile(PrivateVars, Statements, Lint);
  
  // Loop through all variables in the VariableDefaults section...
  VarTable::Iterator I = PrivateVars.GetStart();
  while (I) {
    CSString &VarName = I.GetKey();
    
    if (!Variables.InTable(VarName)) {  // Add it if not already defined.
      VTExp *Value = I.GetValue();

      // Does the explaination last a default value?
      if (Value->GetExpType() != VTExp::ListTy) {
        // Default value is an empty string...
        Variables[VarName] = new VTStrExp(-1, "");
        if (Lint) {
          cout << "  Variable '" << I.GetKey() << "' lacks a default value"
                  " in [VariableDefaults] section!\n";
          *Lint = 1;
        }
      } else {
        VTListExp *List = (VTListExp*)Value;
        Value = List->GetListElement(1);  // Strip off description...
        if (Value) {
          Variables[VarName] = Value;
        } else {
          Variables[VarName] = new VTStrExp(-1, "");
        }

        if (Lint && List->GetNumElements() < 2) {
          cout << "  Variable '" << I.GetKey() << "' contains " 
               << List->GetNumElements() << " value instead of the expected "
                  "description, default value in [VariableDefaults] section!\n";
          *Lint = 1;
        }
      }
    } else if (Lint) {
      cout << "  Variable '" << I.GetKey() << "' already defined for "
        << "VariableDefaults table.\n";
      *Lint = 1;
    }
    
    I++;
  }
  
  if (Lint) {       // If linting, print all statements we don't recognize...
    VarTable::Iterator I = Statements.GetStart();
    while (I) {
      cout << "  Statement '" << I.GetKey() << "' unrecognized in " 
              "[VariableDefaults] section!\n";
      I++;
    }
  }
}

//=---------------------------------------------------------------------------=
//  PlainConfigFileSection
//=---------------------------------------------------------------------------=

PlainConfigFileSection::PlainConfigFileSection(int LineNo, 
                                               const String &SectionName, 
                                               ConfigFileStatement *SList, 
                                               ConfigFileSection *Next) 
                     : ConfigFileSection(LineNo, SectionName, SList, Next),
                       Path(PlatformAbstraction::GetCWD()) {
  }


// EvaluateSection - This function causes only the 'this' section to be added
// to the specified linked list... if it's a theme, it should add all of the 
// theme stuff too...
//
void PlainConfigFileSection::EvaluateSection(VarTable &Variables, 
                                             LinkedList<CFileSection> &List, 
                                             int *Lint) {
  
  // If we find the Variable Defaults section, insert the variables into the 
  // VarTable only if they are not already in it.
  //
  VarTable Statements;
  if (StmtList) StmtList->EvaluateFile(Variables, Statements, Lint);
  
  if (Lint) {         // If linting, print all statements we don't recognize...
    VarTable::Iterator I = Statements.GetStart();
    while (I) {

      // Check to see if we know it.
      I++;
    }
  }

  List.AddToTail(CFileSection(SectionName, Variables, Statements, Path));
}

//=---------------------------------------------------------------------------=
//  ThemeConfigFileSection
//=---------------------------------------------------------------------------=

// EvaluateSection - This function causes only the 'this' section to be added
// to the specified linked list... if it's a theme, it should add all of the 
// theme stuff too...
//
void ThemeConfigFileSection::EvaluateSection(VarTable &Variables, 
                                             LinkedList<CFileSection> &List, 
                                             int *Lint) {
  
  // If we find the Variable Defaults section, insert the variables into the 
  // VarTable only if they are not already in it.
  //
  VarTable Statements;
  if (StmtList) StmtList->EvaluateFile(Variables, Statements, Lint);
  if (ThemeConfigFile == 0) return;

  // Private variables used in theme are seperate from flowing variables...
  VarTable ThemeVars = Variables;

  // Pass the Theme specific variables into the theme files...
  //
  ThemeVars["$MS.ThemeURL"   ] = new VTStrExp(-1, ThemeURL);
  ThemeVars["$MS.ThemeResURL"] = new VTStrExp(-1, ThemeURL); // Deprecated...
  ThemeVars["$MS.ThemeName"  ] = new VTStrExp(-1, ThemeName);
  
  // Loop through theme, adding all of the appropriate sections to the current 
  // user's config file...
  //
  ConfigFileSection *Cur = ThemeConfigFile;
  while (Cur) {
    Cur->EvaluateSection(ThemeVars, List, Lint);
    Cur = Cur->GetNext();
  }
  
/*  
  cout << "Evaluating theme section: " << endl;
  ThemeConfigFile->Print(ThemeVars); 
*/

  if (Lint) {        // If linting, print all statements we don't recognize...
    VarTable::Iterator I = Statements.GetStart();
    while (I) {
      cout << "  Unrecognized setting in the [[" << ThemeName 
           << "]] section\n    " << I.GetKey() << " = " << I.GetValue() << endl;
      // Check to see if we know it.
      I++;
    }
  }
}

// PostLoadFixupScan - This function is called immediately after all of the
// config file is loaded.  This is used here to load the theme file this 
// section is refering to.  Because the parser is not reentrant, we cannot
// load it from the constructor...
//
void ThemeConfigFileSection::PostLoadFixupScan() {
  if (ResourceManager::FindTheme(ThemeName, ThemePath, ThemeURL)) {
    cout << " Could not find theme '" << ThemeName 
         << "' ignoring theme request.\n";
    ThemeConfigFile = 0;
  } else {
    MSFilename ThemeFNPath = ThemePath;
    
    // 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(ThemeFNPath.DirectoryPortion());
    ThemeConfigFile = LoadConfigFile(ThemeFNPath.FilenamePortion());
    
    // Restore the old directory.
    PlatformAbstraction::ChDir(OldDir);
  }

  if (Next) Next->PostLoadFixupScan();
}


//=---------------------------------------------------------------------------=
//  GlobalConfigFileSection
//=---------------------------------------------------------------------------=

// EvaluateSection - Incorporate the statements in the section parameter
// into the global variables in this Options object.
//
void GlobalConfigFileSection::EvaluateSection(VarTable &Variables,
                                              LinkedList<CFileSection> &List,
                                              int *Lint) {

  VarTable Statements;
  if (StmtList) StmtList->EvaluateFile(Variables, Statements, Lint);
  
  List.AddToTail(CFileSection(SectionName, Variables, Statements, ""));
}

static void AddResourcesList(VTListExp *List) {
  int i, Length = List->GetNumElements();

  for (i = 0; i < Length; i++) {
    VTExp *Element = List->GetListElement(i);
    if (Element && Element->GetExpType() == VTExp::ListTy) {
      AddResourcesList((VTListExp*)Element);    // Recursively add resources...
    } else {    // Must be a pair of values...
      VTExp *Element2 = List->GetListElement(i+1);
      if (Element == 0 || Element2 == 0 || 
          Element2->GetExpType() != VTExp::StringTy) {
        cout << "'Resources' list expected a Pair of values: '"
             << Element << "', '" << Element2 << "'!\n";
      } else {
        if (ResourceManager::AddResourceLocation(Element->GetStringValue(), 
                                                 Element2->GetStringValue(), 
                                                 0)) {
          cout << "Error adding resource directory '"  
               << Element->GetStringValue()
               << "' from [Global] section of configuration file, key "
               << "'Resources'!  Ignoring.\n";
        }
        i++;   // Advance past second read value...
      }
    }
  }
}

GlobalConfigFileSection::GlobalConfigFileSection(int LineNo, 
                        ConfigFileStatement *SList, ConfigFileSection *N)
                        : ConfigFileSection(LineNo, "Global", SList, N) {
  VarTable Statements, Variables;
  if (StmtList) StmtList->EvaluateFile(Variables, Statements, 0);

  Table<CSString,VTExp*>::Iterator I = Statements.GetStart();

  if (!I) return;  // No Keys in table...

  // Use this strange order so we can use 'continue' in the statements below.
  I--;
  do {
     I++;

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

     // Check for the 'Access Log' setting...
     if (Key == CSString("AccessLog")) {
       MSOptions::AccessLog->UnRef();  // Release it...
       MSOptions::AccessLog = Value->AddRef();
       continue;

     // Check for the 'Data Filename' setting...
     } else if (Key == CSString("DataFilename")) {
       if (Value->GetExpType() != VTExp::StringTy) 
         cout << "String value expected for 'DataFilename' setting!\n";
       else 
         MSOptions::DataFilename = Value->GetStringValue();
       continue;

     } else if (Key == CSString("Resources")) {
       if (Value->GetExpType() != VTExp::ListTy) {
         cout << "List value expected for 'Resources' setting!\n";
         continue;
       }

       AddResourcesList((VTListExp*)Value);
       continue;

     } else {       // Check for numbered settings...
       CSString Temp = Key;
       Temp.Left(8);    // Truncate to strlen("Resource")

       // Check for the 'Resource#' settings...
       if (Temp == CSString("Resource")) {
         String ResURL(Value->GetStringValue()), path;
         ResURL.Tokenize(",", path);
         MSFilename ResPath = path;

         if (ResourceManager::AddResourceLocation(ResPath, ResURL, 0)) {
           cout << "Error adding resource directory '" << ResPath 
                << "' from [Global] section of configuration file, key '" 
                << Key << "'!  Ignoring.\n";
         }
         continue;
       }

       Temp.Left(6);    // Truncate more to strlen("Filter")
       
       // Check for the Filter# settings...
       if (Temp == CSString("Filter")) 
         continue;     // Filter[0-9s]* are handled later by AccessLogManager
       cout << "Temp = '" << Temp << "' != 'Filter'!\n";
     } 

     if (MSOptions::Verbose) {
       cout << "  Unrecognized setting in the [Global] section: '" 
            << Key << ": " << Value << ";\n";
     }
  } while (I);
}
