//=-- SkelRep.cpp - Classes for representing skeleton files --------*- C++ -*--=
//
//  This file defines the data structures used when loading skel files from 
//  the disk.  After calling LoadSkelFile, a dynamically structured skel file
//  is loaded.  This structure has all of the expression forms still intact, 
//  so Evaluating it with different files can return a different resultant 
//  structure.
//
//=----------------------------------------------------------------------------=
//  This file is copyright (c) 1997-2000 Chris Lattner
//=----------------------------------------------------------------------------=

#include <assert.h>
#include "Options.h"
#include "PagePlugin.h"
#include "PluginManager.h"
#include "SkelRep.h"
#include "Date.h"

extern FILE *Skelin;  // Lexer input file descriptor...
extern int Skelparse();
extern SkelData *SkelParserResult;

const char *SkelFilename;


// SkelFileCache - Cache for Skel files to avoid hitting the disk every time
// we want the parsed version of a skel file!
//
static Table<String, const SkelData *> SkelFileCache;



const SkelData *GetSkelFile(const char *Filename) {
  if (SkelFileCache.InTable(Filename)) {
    if (MSOptions::Debug) 
      cout << "\n    \t\tUsing cached skel file: " << Filename;
    return SkelFileCache[Filename];
  }

  if (MSOptions::Debug) 
    cout << "\n    \t\tLoading skel file from disk: " << Filename;

  FILE *F = fopen(Filename, "r");
  if (F == 0) return 0;
  Skelin = F;

  Skelparse();   // Parse the skel file...
	
  Skelin = stdin;  // Restore skel lexer in to default to stdin
  fclose(F);

  return (SkelFileCache[Filename] = SkelParserResult);
}

//=---------------------------------------------------------------------------=
//   VTExp class
//=---------------------------------------------------------------------------=


// IsTrue - Determine the truth value of a string... any string except "0" and 
// "false" (insensitive) is considered True.
//
int VTExp::IsTrue(const VTExp *UVal) {
  if (UVal->GetExpType() == StringTy) {
    const String &Val = ((VTStrExp*)UVal)->GetValue();
    char V0 = Val[0];

    if ((V0 == '0' || V0 == '1') && (Val.Length() == 1))
      return V0 == '1';
  
    if ((V0 == 'f' || V0 == 'F') && (Val.Length() == 5))
      return Val.iCompare("false") != 0;

    return 1;
  } else if (UVal->GetExpType() == ListTy) {
    return ((VTListExp*)UVal)->GetNumElements() != 0;
  }

  return 1;  // Error, but deal with it...
}

void VTExp::Print(ostream &os) const {
  os << GetStringValue();
}


//=---------------------------------------------------------------------------=
//   VTStrExp class
//=---------------------------------------------------------------------------=


//=---------------------------------------------------------------------------=
//   VTListExp class
//=---------------------------------------------------------------------------=

VTListExp::VTListExp(int LineNo) : VTExp(LineNo, ListTy), 
  TheList(*new LinkedList<VTExp*>()) {}

VTListExp::~VTListExp() {
  LinkedList<VTExp*>::Iterator I = TheList.GetIterator();
  for (; I; I++) 
    (*I)->UnRef();   // Free all nodes...

  delete &TheList;   // Free the list itself.
}

VTExp *VTListExp::Evaluate(VarTable &Vars, int *Lint) {
  VTListExp *Result = new VTListExp(GetLineNo());
  LinkedList<VTExp*>::Iterator I = TheList.GetIterator();

  for (; I; I++)
    Result->AddToTail((*I)->Evaluate(Vars, Lint));
  return Result;
}


// GetListElement - Return a numbered element of the expression list, or 0
// if the index is invalid.
//
VTExp *VTListExp::GetListElement(int i) const {
  if (i < 0 || i >= TheList.GetLength()) return 0;

  LinkedList<VTExp*>::Iterator I = TheList.GetIterator();

  while (i > 0 && I) {
    I++; 
    i--;
  }

  if (i == 0 && I) 
    return *I;
  return 0;
}

// AddToTail - Append an element to the end of the list...
void VTListExp::AddToTail(VTExp *Val) {
  TheList.AddToTail(Val->AddRef());
}

void VTListExp::RemoveElement(unsigned i) {
  if (i >= (unsigned)TheList.GetLength()) return;

  LinkedList<VTExp*>::Node *N = TheList.GetHead();
  while (i--) N = N->GetNext();
  (**N)->UnRef();
  TheList.RemoveNode(N);
}

// GetNumElements - Return the number of elements in the list...
//
int VTListExp::GetNumElements() const {
  return TheList.GetLength();
}

String VTListExp::GetStringValue() const {
  String Result("[");

  LinkedList<VTExp*>::Iterator I = TheList.GetIterator();
  if (!I) return "[]";
  Result += (*I)->GetStringValue();
  I++;

  while(I) {
    Result += ", ";
    Result += (*I)->GetStringValue();
    I++;
  }
  Result += "]";
  return Result;
}


//=---------------------------------------------------------------------------=
//   SkelVarExp class
//=---------------------------------------------------------------------------=

VTExp *SkelVarExp::Evaluate(VarTable &Vars, int *Lint) {
  if (!Vars.InTable(VarName)) {
    if (Lint || MSOptions::Debug) {
      cout << "  Variable '" << VarName << "' is not defined.  It should "
           << "be defined in [VariableDefaults] section!\n";
      if (Lint) *Lint = 1;
    }

    String VN = VarName;
    VN.Left(4);
    if (VN.iCompare("$MS.") == 0) {  // First three letters are "MS."
      Table<CSString,VTExp*> ParamList;

      VN = VarName;
      VN.Right(VN.Length()-4);      // Trim off VarName

      VTExp *Result = 0;
      int Loc;
      
      // Strip off ':Param=Val' seperated strings into ParamList...
      while ((Loc = VN.strrchr(':')) != -1) {
        String Param(VN);
        Param.Right(VN.Length()-Loc-1); // Param is now name=val
        VN.Left(Loc);                   // VN now doesn't have that : on it.
        
        String Name;
        Param.Tokenize("=", Name);
        ParamList[Name] = new VTStrExp(GetLineNo(), Param);
      }
      
      if (SkelInsertData::IsValidToken(VN)) {
//#warning "BUG: Should not hard code update frequency here for some types!"
        Result = new VTStrExp(GetLineNo(), 
                              SkelInsertData::EvaluateToken(VN, ParamList, 
                                                            1, Lint));
      }

      Table<CSString,VTExp*>::Iterator I = ParamList.GetStart();
      for (; I; I++)
        I->GetSecondary()->UnRef();   // Free all VTStrExp memory..
      
      if (Result) return Result;
    }

    return new VTStrExp(GetLineNo(), String("Undefined Variable: ")+VarName);
  }

  return Vars[VarName];
}


//=---------------------------------------------------------------------------=
//   SkelBinExp class
//=---------------------------------------------------------------------------=

SkelBinExp::SkelBinExp(int LineNo, OpTypes op, VTExp *left, VTExp *right)
                    : VTExp(LineNo, InternalTy) {
  Op = op;
  Left = left;
  Right = right;
}

SkelBinExp::~SkelBinExp() {
  Left->UnRef();
  Right->UnRef();
}


static VTExp *OperatorPlus(VTExp *LVal, VTExp *RVal) {
  if (LVal->GetExpType() == VTExp::StringTy && 
      RVal->GetExpType() == VTExp::StringTy)
    return new VTStrExp(LVal->GetLineNo(), LVal->GetStringValue() + 
                        RVal->GetStringValue());
  else if (LVal->GetExpType() == VTExp::ListTy && 
           RVal->GetExpType() == VTExp::ListTy) {
    cout << __FILE__ << __LINE__ << "Unimplemented + on lists!\n";
    return new VTStrExp(LVal->GetLineNo(), "Unimplemented!!!");
  } else if (LVal->GetExpType() == VTExp::ListTy && 
             RVal->GetExpType() == VTExp::StringTy) {
    VTListExp *LValList = (VTListExp*)LVal;
    VTListExp *Result   = new VTListExp(LVal->GetLineNo());
    
    if (LValList->GetNumElements() == 0) {
      Result->AddToTail(RVal);  // Make a single element list...
    } else {
      LinkedList<VTExp*>::Iterator I = LValList->GetList().GetIterator();
      VTExp *LastEl = *I;
      for (I++; I; I++) {
        Result->AddToTail(LastEl);
        LastEl = *I;
      }

      // Add the string onto the last element of whatever is at the end of this
      // list...
      Result->AddToTail(OperatorPlus(LastEl, RVal));
    }
    
    return Result;
  } else if (LVal->GetExpType() == VTExp::StringTy && 
             RVal->GetExpType() == VTExp::ListTy) {
    VTListExp *RValList = (VTListExp*)RVal;
    VTListExp *Result   = new VTListExp(RVal->GetLineNo());
    
    if (RValList->GetNumElements() == 0) {
      Result->AddToTail(RVal);  // Make a single element list...
    } else {
      LinkedList<VTExp*>::Iterator I = RValList->GetList().GetIterator();

      // Add the string onto the element of whatever is at the front of this
      // list...
      Result->AddToTail(OperatorPlus(*I, RVal));
      for (I++; I; I++) 
        Result->AddToTail(*I);
    }
    
    return Result;
  } else {
    cout << "Error, cannot add '" << LVal << "' to '" << RVal << "'\n";
    return new VTStrExp(LVal->GetLineNo(), 
                        "Error, invalid parameters to '+' operator!");
  }
}


VTExp *SkelBinExp::Evaluate(VarTable &Vars, int *Lint) {
  VTExp *LVal = Left->Evaluate(Vars, Lint);
  VTExp *RVal = Right->Evaluate(Vars, Lint);

  switch (Op) {
  case And:
    return new VTStrExp(LVal->GetLineNo(), 
                          String::IntToStr(IsTrue(LVal) && IsTrue(RVal)));
  case Or:
    return new VTStrExp(LVal->GetLineNo(), 
                          String::IntToStr(IsTrue(LVal) || IsTrue(RVal)));

  case Plus:
    return OperatorPlus(LVal, RVal);

  case Equals:
    if (LVal->GetExpType() != StringTy || RVal->GetExpType() != StringTy)
      return new VTStrExp(LVal->GetLineNo(), "0");

    return new VTStrExp(LVal->GetLineNo(), 
                          String::IntToStr(LVal->GetStringValue().
                              Compare(RVal->GetStringValue()) == 0));
  case NEquals:
    if (LVal->GetExpType() != StringTy || RVal->GetExpType() != StringTy)
      return new VTStrExp(LVal->GetLineNo(), "1");

    return new VTStrExp(LVal->GetLineNo(),
                          String::IntToStr(LVal->GetStringValue().
                              Compare(RVal->GetStringValue()) != 0));
  }

  return new VTStrExp(-1, "BinExp Error!");
}


//=---------------------------------------------------------------------------=
//   SkelUnaryExp class
//=---------------------------------------------------------------------------=

SkelUnaryExp::SkelUnaryExp(int LineNo, OpTypes op, VTExp *subExp)
                     : VTExp(LineNo, InternalTy) {
  Op = op;
  SubExp = subExp;
}

SkelUnaryExp::~SkelUnaryExp() {
  SubExp->UnRef();
}

VTExp *SkelUnaryExp::Evaluate(VarTable &Vars, int *Lint) {
  switch (Op) {
  case Not:
    return new VTStrExp(GetLineNo(), 
                     String::IntToStr(!IsTrue(SubExp->Evaluate(Vars, Lint))));
  }
  return new VTStrExp(-1, "UnaryExp Error!");
}


//=---------------------------------------------------------------------------=
//   SkelRawData class (Raw HTML)
//=---------------------------------------------------------------------------=

void SkelRawData::OutputFile(ostream &OutFile, int UpdateFreq, 
                             VarTable &Vars, int *Lint) const {
  OutFile << RawData;
}


//=---------------------------------------------------------------------------=
//   SkelInsertData class   (MSInsert Tag)
//=---------------------------------------------------------------------------=

// BILRefCount - When comes back to zero, remove lookup table.
int SkelInsertData::BILRefCount = 0;
Table <CSString, int> *SkelInsertData::BuiltInLookup = 0;

SkelInsertData::SkelInsertData(int lineNo, VTExp *E, SkelSettingList *L, 
                               SkelData *N) : SkelData(LineNo, N) {
  InsertExp = E;
  Settings = L;
  
  BILRefCount++;
  if (BuiltInLookup == 0) InitializeBuiltinLookup();
}

SkelInsertData::~SkelInsertData() {
  InsertExp->UnRef();
  delete Settings;
  
  if (--BILRefCount == 0) {
    delete BuiltInLookup;
    BuiltInLookup = 0;
  }
}

void SkelInsertData::InitializeBuiltinLookup() {
  BuiltInLookup = new Table<CSString, int>();
  BuiltInLookup->SetDefault(-1);

  (*BuiltInLookup)["Version"       ] = Version;
  (*BuiltInLookup)["ElapsedSeconds"] = ElapsedSeconds;
  (*BuiltInLookup)["MagicStatsURL" ] = MagicStatsURL;
  (*BuiltInLookup)["Date"          ] = cDate;
  (*BuiltInLookup)["UpdateDate"    ] = UpdateDate;
  (*BuiltInLookup)["YesterdayDate" ] = YesterdayDate;
  (*BuiltInLookup)["TomorrowDate"  ] = TomorrowDate;
  (*BuiltInLookup)["NextMonth"     ] = NextMonth;
  (*BuiltInLookup)["LastMonth"     ] = LastMonth;
  (*BuiltInLookup)["MSCopyright"   ] = MSCopyright;
  (*BuiltInLookup)["MSBuiltInURL"  ] = MSBuiltInURL;
}

String SkelInsertData::EvalDate(Date &OutputDate, String Format) {
  String Output;

  if (!Format.iCompare("NUMERIC")) {
    Format = "%Y%m%d";
  } else if (!Format.iCompare("NUMERICMONTHLY")) {
    Format = "%Y%m";
  } else if (!Format.iCompare("LONG")) {
    Format = "%A, %B %d, %Y";
  } else if (!Format.iCompare("SHORT")) {
    Format = "%m/%d/%Y";
  } else if (!Format.iCompare("TIMEDATE")) {
    Format = "%c";
  }

  OutputDate.strftime(Output, Format);
  return Output;
}

void SkelInsertData::OutputFile(ostream &OutFile, int UpdateFreq,
                                VarTable &Variables, int *Lint) const {

  String Result = InsertExp->Evaluate(Variables, Lint)->GetStringValue();

  Table<CSString,VTExp*> ParamList;
  if (Settings) Settings->AddSettingsToTable(ParamList, Variables, Lint);

  Result = EvaluateToken(Result, ParamList, UpdateFreq, Lint);

  if (!Lint) OutFile << Result;
}

String SkelInsertData::EvaluateToken(const CSString &Token, 
                                     const Table<CSString,VTExp*> &ParamList,
                                     int UpdateFreq, int *Lint) {
  int Code = (*BuiltInLookup)[Token];
  String Result;

  switch (Code) {
  case -1:
    Result = Token;       // This is the case when we MSInsert=$(variable)
    break;

  case Version:           // MagicStats version #
    Result = MSOptions::Version;
    break;

  case ElapsedSeconds: 
    {
      assert(UpdateFreq >= 0 && UpdateFreq < Plugin::UpdateMaximum);

      Date date;         // Initialized to current date/time
      double Elapsed = date.ElapsedSeconds(MSOptions::StartDate[UpdateFreq]);
      Result = String::DoubleToStr(Elapsed);
    }
    break;

  case MagicStatsURL:     // URL of MagicStats
    Result = MSOptions::MagicStatsURL;
    break;

  case MSBuiltInURL:      // URL of Plugins/BuiltIn directory...
    Result = MSOptions::GetBuiltInPluginResURL();
    break;

  case MSCopyright:
    Result = "<img align=left src='"+MSOptions::GetBuiltInPluginResURL()+
    "/LogoMagicStats.gif'>"
    "This page generated by <a href='" + MSOptions::MagicStatsURL + 
    "'>MagicStats</a> Version " + MSOptions::Version + "<br>\n"
    "Copyright &copy 1997-2000 <a href='http://www.nondot.org/sabre'>Chris "
    "Lattner</a>.\nAll rights reserved.<br>\nSend feature requests, bug "
    "reports, comments, suggestions or anything else to <a "
    "href='mailto:sabre@nondot.org'>me</a>. Thank you.<br>";
    break;


  case cDate:             // Various Date types...
  case UpdateDate:
  case YesterdayDate:
  case TomorrowDate:
  case NextMonth: 
  case LastMonth:
    {
      Date date;

      switch (Code) {
      case cDate:
        date = MSOptions::CurDate;
        break;
      case UpdateDate:
        assert(UpdateFreq >= 0 && UpdateFreq < Plugin::UpdateMaximum);
        date = MSOptions::StartDate[UpdateFreq];
        break;
      case YesterdayDate:
        date = MSOptions::CurDate.Yesterday();
        break;
      case TomorrowDate:
        date = MSOptions::CurDate.Tomorrow();
        break;
      case NextMonth: 
        date = MSOptions::CurDate;
        date.SetDay(1);
        date.SetMonth(date.GetMonth()+1);
        break;
      case LastMonth:
        date = MSOptions::CurDate;
        date.SetDay(1);
        date.SetMonth(date.GetMonth()-1);
        break;
      }

      Result = EvalDate(date, ParamList["FORMAT"]->GetStringValue());
      if (Lint) {
        Table<CSString,VTExp*>::Iterator I = ParamList.GetStart();
        while (I) {
          if (I->GetPrimary() != CSString("Format")) {
            cout << "  Unrecognized Date formatting parameter ('" 
                 << I->GetPrimary() << "') in MSInsert Tag!\n";
            *Lint = 1;
          }

          I++;
        }
      }
    }
    break;

  default:         // In the BuiltIn table, but not recognized here... error!
    Result = "Error, tag '" + Result + "' half recognized!\n";
    cout << "Error, tag '" << Result << "' half recognized!  Value = " 
         << Code << " at " << __FILE__ << ":" << __LINE__ << "\n";
    break;

  }
  return Result;
}

int SkelInsertData::IsValidToken(const CSString &Token) {
  if (BuiltInLookup == 0) InitializeBuiltinLookup();
  return (*BuiltInLookup)[Token] != -1;
}



//=---------------------------------------------------------------------------=
//   SkelPluginData class   (MSPlugin Tag)
//=---------------------------------------------------------------------------=

SkelPluginData::SkelPluginData(int LineNo, char *Name, SkelSettingList *L, 
                               SkelData *N) : SkelData(LineNo, N), 
                                              PluginName(Name) {
  Settings = L;
}

SkelPluginData::~SkelPluginData() {
  delete Settings;
}

void SkelPluginData::OutputFile(ostream &OutFile, int UpdateFreq,
                                VarTable &Variables, int *Lint) const {

  PagePlugin *PluginMatch;
  String PluginFullName;

  VarTable ParamList; ParamList.SetDefault(0);
  if (Settings) Settings->AddSettingsToTable(ParamList, Variables, Lint);

  PluginMatch = PluginManager::GetPagePlugin(PluginName);
      
  if (PluginMatch == 0 || Lint) {
    // Can't find the name in the PluginList list, it must 
    // not be a plugin name that we know about.  Since we already 
    // told the user we didn't find it, don't freak out again.
  } else {
    PluginMatch->GetFullName(ParamList, PluginFullName, UpdateFreq);
    PluginMatch = PluginManager::GetPagePluginByFullName(PluginFullName);
      
    if (PluginMatch == 0) {
      cout << "SkelRep.cpp:" << __LINE__ 
           << " - INTERNAL ERROR: Could not find plugin fullname '" 
           << PluginFullName << "'!! Please verify themes for correctness.\n";
    } else {
      // Make output from the plugin
      PluginMatch->OutputHTML(OutFile, ParamList);
    }
  }
}

void SkelPluginData::ScanForPlugins(int UpdateFreq, VarTable &Variables) const {
  String PluginFullName;

  VarTable ParamList;
  if (Settings) Settings->AddSettingsToTable(ParamList, Variables);
  
  // See if we already have this plugin loaded...
  PagePlugin *PluginMatch = PluginManager::GetPagePlugin(PluginName);
  if (PluginMatch)  // If we have something of that name... check out it's fullname
    PluginMatch->GetFullName(ParamList, PluginFullName, UpdateFreq);
  
  // If we didn't find any of that type, or nothing that matched the fullname,
  //   then attempt to create a new plugin of that type...
  if (!PluginMatch || !PluginManager::GetPagePluginByFullName(PluginFullName)) {
    
    // Attempt to create a new plugin...
    PagePlugin *NewCopy = PagePlugin::CreatePlugin(PluginName);
    
    if (NewCopy) {               // The create succeeded!
      
      // Load the plugin's settings from the Parameter list...
      NewCopy->MetaLoadSettings(&ParamList, UpdateFreq);
    } else {                     // No plugin mathing PluginName found!!!
      
      // Can't find the name in the PluginStubs list,
      // it must not be a plugin name that we know about...
      cout << "Could not find plugin '" << PluginName 
           << "'!!  Please verify Skel file for correctness.\n";
    }
  }
  
  ParamList.Clear();
}


//=---------------------------------------------------------------------------=
//   SkelIfThenElseData class   (MSIF/MSELSEIF/MSELSE/MSENDIF Tags)
//=---------------------------------------------------------------------------=

SkelIfThenElseData::SkelIfThenElseData(int LineNo, VTExp *exp, SkelData *ThenC, 
                   SkelData *ElseC, SkelData *N) : SkelData(LineNo, N) {
  Exp = exp;
  Then = ThenC;
  Else = ElseC;
}

SkelIfThenElseData::~SkelIfThenElseData() {
  Exp->UnRef();
  delete Then;
  delete Else;
}

void SkelIfThenElseData::OutputFile(ostream &OutFile, int UpdateFreq,
                                    VarTable &Variables, int *Lint) const {

  VTExp *Val = Exp->Evaluate(Variables, Lint);

  if (VTExp::IsTrue(Val))
    if (Then) Then->DoOutputFile(OutFile, UpdateFreq, Variables, Lint);
  else
    if (Else) Else->DoOutputFile(OutFile, UpdateFreq, Variables, Lint);
}

void SkelIfThenElseData::ScanForPlugins(int UpdateFreq, 
                                        VarTable &Variables) const {
  VTExp *Val = Exp->Evaluate(Variables);

  if (VTExp::IsTrue(Val))
    if (Then) Then->DoScanForPlugins(UpdateFreq, Variables);
  else
    if (Else) Else->DoScanForPlugins(UpdateFreq, Variables);
}
