//=-- SkelFuncs.cpp - Implementation of functions ------------------*- C++ -*--=
//
//  This file defines the functions that may be used in MagicStats skel and
//  config files.  These functions can do a lot of different things, but all
//  get registered here.  This file also implements the SkelFunctionExp class.
//
//=----------------------------------------------------------------------------=
//  This file is copyright (c) 2000 Chris Lattner
//=----------------------------------------------------------------------------=

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

//=---------------------------------------------------------------------------=
//   SkelFunctionExp class
//=---------------------------------------------------------------------------=

typedef VTExp *(*VTFunc)(VTListExp *Params, VarTable &Vars, int *Lint);

// Visual C++ 6.0 is certifiably dumb when instantiating templates... :(
inline ostream &operator<<(ostream &os, const VTFunc) {}

// Use the DataPair class to make a new virtual pair class.  
//
// This should eventually define the following things:
//    Name, NumParams, Parameter Types (set of VTExpTypes), 
//    function pointer.
//
struct FuncRec : DataPair<VTFunc, DataPair<String, int> > {
  FuncRec() : DataPair<VTFunc, DataPair<String, int> >() {}
  FuncRec(VTFunc f, const String &Name, int numargs) 
     : DataPair<VTFunc,DataPair<String, int> >(f, DataPair<String, int>(Name, numargs)) {}
#define FuncRecPtr     P
#define FuncRecName    S.P
#define FuncRecNumArgs S.S
};

static int LookupRefCount = 0;
Table<String, FuncRec> *VTFuncTable;

static VTExp *Map              (VTListExp *Params, VarTable &Vars, int *Lint);
static VTExp *Head             (VTListExp *Params, VarTable &Vars, int *Lint);
static VTExp *Last             (VTListExp *Params, VarTable &Vars, int *Lint);
static VTExp *Reverse          (VTListExp *Params, VarTable &Vars, int *Lint);
static VTExp *Concat           (VTListExp *Params, VarTable &Vars, int *Lint);
static VTExp *ChangeFilterField(VTListExp *Params, VarTable &Vars, int *Lint);
static VTExp *FilterSplit      (VTListExp *Params, VarTable &Vars, int *Lint);

static void InitializeVTFuncTable() {
  VTFuncTable = new Table<String, FuncRec>();
  Table<String, FuncRec> &FT = *VTFuncTable;
  FT["map"]               = FuncRec(Map,               "map",               2);
  FT["head"]              = FuncRec(Head,              "head",              1);
  FT["last"]              = FuncRec(Last,              "last",              1);
  FT["reverse"]           = FuncRec(Reverse,           "reverse",           1);
  FT["concat"]            = FuncRec(Concat,            "concat",            1);
  FT["ChangeFilterField"] = FuncRec(ChangeFilterField, "ChangeFilterField", 3);
  FT["FilterSplit"]       = FuncRec(FilterSplit,       "FilterSplit",       1);

  if (MSOptions::Debug)
    cout << "SkelFuncs Table Created!\n";
}

SkelFunctionExp::SkelFunctionExp(int LineNo, VTExp *function,
                                 VTListExp *ParamList) 
               : VTExp(LineNo, InternalTy), Function(function) {
  assert(ParamList && "Parameter list shouldn't be null!");
  Params = ParamList;

  if (LookupRefCount++ == 0) InitializeVTFuncTable();
}

SkelFunctionExp::~SkelFunctionExp() {
  Params->UnRef();

  if (--LookupRefCount == 0) {
    delete VTFuncTable; VTFuncTable = 0;
    if (MSOptions::Debug)
      cout << "SkelFuncs Table Destroyed!\n";
  }
}

VTExp *SkelFunctionExp::Evaluate(VarTable &Vars, int *Lint) {
  VTExp *FN = Function->Evaluate(Vars, Lint);
  VTFunctionExp *RealFuncExp = 0;

  switch (FN->GetExpType()) {
  case VTExp::StringTy:
    if (!VTFuncTable->InTable(FN->GetStringValue())) {
      String FuncName = FN->GetStringValue();
      if (Lint) *Lint = 1;
      cout << "Unrecognized theme function: '" << FuncName << "'!\n";
      FN->UnRef();
      return new VTStrExp(-1, String("Unrecognized function: ")+FuncName);
    }

    // Refers to a function by name, thus no parameters are evaluated yet...
    RealFuncExp = new VTFunctionExp(FN->GetLineNo(), 
                                    &(*VTFuncTable)[FN->GetStringValue()],
                                    new VTListExp(FN->GetLineNo()));
    FN->UnRef(); // Done with the name...
    break;
  case VTExp::FunctionTy:
    RealFuncExp = (VTFunctionExp*)FN;
    break;
  default:
    if (Lint) *Lint = 1;
    cout << "Invalid type to execute as function!: " << FN << endl;
    FN->UnRef();
    return new VTStrExp(-1, String("Invalid function!"));
  }

  // Evaluate the parameter list so that if any of the elements of the list are
  // expressions, they will get recursively evaluated.
  //
  VTExp *EvalParams = Params->Evaluate(Vars, Lint);
  assert(EvalParams->GetExpType() == VTExp::ListTy);  // A list stays a list!!!
  VTListExp *NewParams = (VTListExp*)EvalParams;

  VTExp *Result = RealFuncExp->EvaluateWith(NewParams, Vars, Lint);
  NewParams->UnRef(); // Free evaluated parameter memory...
  return Result;
}


//------------------------------------------------------------------------------
// VTFunctionExp Class Definition
//------------------------------------------------------------------------------

VTExp *VTFunctionExp::Evaluate(VarTable &, int *) {
  return this->AddRef();
}

String VTFunctionExp::GetStringValue() const {
  return FuncInfo->FuncRecName + "{" + Params->GetStringValue() + "}";
}


// GetNumArgs - Get the number of arguments this (possibly) partially
// evaluated function need before it can really run...
//
int VTFunctionExp::GetNumArgs() {
  return FuncInfo->FuncRecNumArgs - Params->GetNumElements();
}


// EvaluateWith - Apply some more parameters to this partially evaluated
// function.  If the number is too many then a string is returned 
// indicating the error condition.  If the number of parameters is 
// < GetNumArgs() then a VTFunctionExp is returned.  If the number of
// parameters is just right, then the function is executed.
//
VTExp *VTFunctionExp::EvaluateWith(VTListExp *NewParams, VarTable &Vars, 
                                   int *Lint) {
  VTExp *Result;

  // We can pass too few arguments to do partial evaluation, but too many
  // is so very taboo...
  //
  if (NewParams->GetNumElements() > GetNumArgs()) {
    if (Lint) *Lint = 1;
    cout << "Too many parameters to function: '" << this << "': '" 
         << NewParams << "'!\n";
    return new VTStrExp(GetLineNo(), String("Too many params to function!"));
  }

  VTListExp *RealParams = new VTListExp(GetLineNo());
  LinkedList<VTExp*>::Iterator I = Params->GetList().GetIterator();
  for (; I; I++)
    RealParams->AddToTail(*I);
  
  I = NewParams->GetList().GetIterator();
  for (; I; I++)
    RealParams->AddToTail(*I);
  
  if (NewParams->GetNumElements() == GetNumArgs()) {
    Result = FuncInfo->FuncRecPtr(RealParams, Vars, Lint);
    RealParams->UnRef();  // Done with the parameters!
  } else {
    Result = new VTFunctionExp(Params->GetLineNo(), FuncInfo, RealParams);
  }

  return Result;
}


//------------------------------------------------------------------------------
// Skel Function Implementations...
//------------------------------------------------------------------------------


// Map - Apply the function object specified in parameter #1 to each and every
// item of the list specified in parameter 2.  The result is a list with all of
// the elements applied.  Although we probably shouldn't, we let arg2 be a 
// string and apply it to just that string if it is.
//
static VTExp *Map(VTListExp *Params, VarTable &Vars, int *Lint) {
  assert(Params->GetNumElements() == 2);
  VTExp *FuncE = Params->GetListElement(0);
  VTExp *List = Params->GetListElement(1);

  if (FuncE->GetExpType() != VTExp::FunctionTy) {
    cout << "Argument #1 to 'map' function must be a function!\n"
         << "  You gave it: " << FuncE << endl;
    return new VTStrExp(Params->GetLineNo(), 
                        "'map' function: bad argument #1!");
  }

  // Now we KNOW it's a function...
  VTFunctionExp *Func = (VTFunctionExp*)FuncE;
  VTExp *Result;

  switch (List->GetExpType()) {
  case VTExp::StringTy:{// Degenerate case, we shouldn't handle this, but we do.
    VTListExp *NewParam = new VTListExp(List->GetLineNo());
    NewParam->AddToTail(List);
    Result = Func->EvaluateWith(NewParam, Vars, Lint);
    NewParam->UnRef();
    break;
  }
  case VTExp::ListTy: {
    VTListExp *NewParam = new VTListExp(List->GetLineNo());
    VTListExp *SourceList = (VTListExp*)List;
    VTListExp *ResultList = new VTListExp(List->GetLineNo());

    LinkedList<VTExp*>::Iterator I = SourceList->GetList().GetIterator();
    for (; I; I++) {
      NewParam->AddToTail(*I);
      ResultList->AddToTail(Func->EvaluateWith(NewParam, Vars, Lint));
      NewParam->RemoveElement(0);
    }
    Result = ResultList;
    break;
  }
  default :
    cout << "Argument #2 to 'map' function must be a list or string!\n"
         << "  You gave it: "  << List << endl;
    Result = new VTStrExp(List->GetLineNo(), 
                          "'map' function: bad argument #2!");
    break;
  }

  return Result;
}


// Head - Return the first element of the list passed to it.
//
static VTExp *Head(VTListExp *Params, VarTable &, int *) {
  assert(Params->GetNumElements() == 1);

  VTExp *Param = Params->GetListElement(0);
  if (Param->GetExpType() != VTExp::ListTy || 
      ((VTListExp*)Param)->GetNumElements() == 0) {
    cout << "'head' function expects a nonempty list argument!\n";
    return new VTStrExp(Param->GetLineNo(), "'head'"
                        " function expects a nonempty list argument!\n");
  }

  VTListExp *List = (VTListExp*)Param;
  return List->GetListElement(0)->AddRef();
}


// Last - Return the last element of the list passed to it.
//
static VTExp *Last(VTListExp *Params, VarTable &, int *) {
  assert(Params->GetNumElements() == 1);

  VTExp *Param = Params->GetListElement(0);
  if (Param->GetExpType() != VTExp::ListTy || 
      ((VTListExp*)Param)->GetNumElements() == 0) {
    cout << "'last' function expects a nonempty list argument!\n";
    return new VTStrExp(Param->GetLineNo(), "'last'"
                        " function expects a nonempty list argument!\n");
  }

  VTListExp *List = (VTListExp*)Param;
  return List->GetListElement(List->GetNumElements()-1)->AddRef();
}



// Reverse - Reverse the order of a list or the order of characters in a string
// (This is in the spirit of strings being lists of characters)
//
static VTExp *Reverse(VTListExp *Params, VarTable &, int *) {
  assert(Params->GetNumElements() == 1);

  VTExp *Param = Params->GetListElement(0);
  if (Param->GetExpType() == VTExp::StringTy) {
    const String Tmp(Param->GetStringValue());
    String Result(Tmp);
    int Len = Tmp.Length();

    for (int i = 0; i < Len; i++)
      Result[i] = Tmp[Len-1-i];
    return new VTStrExp(Param->GetLineNo(), Result);
  } else if (Param->GetExpType() == VTExp::ListTy) {
    const VTListExp *ListParam = (VTListExp*)Param;
    VTListExp *Result = new VTListExp(Param->GetLineNo());

    LinkedList<VTExp*>::Iterator I = ListParam->GetList().GetIterator();
    LinkedList<VTExp*> &ResList = Result->GetList();
    for (; I; I++) 
      ResList.AddToHead(*I);
    return Result;
  } else {
    cout << "What kind of type is " << Param->GetExpType() << "!?\n";
    return new VTStrExp(Param->GetLineNo(), 
                        "'reverse' function: bad argument!");
  }
}


// Concat - The first/only parameter is a list.  This function concatenates all
// of the elements of the list together into a single list.  For example, a list
// of strings becomes one big string, a list of lists becomes one big list.
//
static VTExp *Concat(VTListExp *Params, VarTable &, int *) {
  assert(Params->GetNumElements() == 1);

  // Make sure our parameter is a list!
  VTExp *Param = Params->GetListElement(0);
  if (Param->GetExpType() == VTExp::StringTy) 
    return Param;   // No work to concatenate a string!
  else if (Param->GetExpType() != VTExp::ListTy) {
    cout << "What kind of type is " << Param->GetExpType() << "!?\n";
    return new VTStrExp(-1, "'concat' function: bad argument!");
  }

  assert(Param->GetExpType() == VTExp::ListTy);
  VTListExp *List = (VTListExp*)Param;
  if (List->GetNumElements() == 0) 
    return new VTStrExp(List->GetLineNo(), ""); // Empty list = Empty str

  // Ensure homogeneous list...
  LinkedList<VTExp*>::Iterator I = List->GetList().GetIterator();
  int Type = (*I)->GetExpType();
  for (I++; I; I++) {
    if (Type != (*I)->GetExpType()) {
      cout << "'concat' problem: Heterogeneous list!: " << List << endl;
      return new VTStrExp(-1, "'concat' function: heterogenous list argument!");
    }
  }

  // Okay, now we know that the list is uniform, and it has type "Type"
  // concatenate away!
  //
  I = List->GetList().GetIterator();
  switch (Type) {
  case VTExp::StringTy: {
    String Value;

    // Concatenate all of the strings together...
    for (; I; I++)
      Value += (*I)->GetStringValue();

    return new VTStrExp(List->GetLineNo(), Value);
    break;
  }

  case VTExp::ListTy: {
    VTListExp *ResultL = new VTListExp(List->GetLineNo());

    // Concatenate all of the lists together...
    for (; I; I++) {
      VTListExp *El = (VTListExp*)*I;
      LinkedList<VTExp*>::Iterator J = El->GetList().GetIterator();
      for (; J; J++)
        ResultL->AddToTail(*J);
    }
    
    return ResultL;
  }
  default:
    // Make sure it is something that we know how to concatenate...
    cout << "'concat' problem: can't concatenate list!: " << List << endl;
    return new VTStrExp(-1, "'concat' function: unknown list argument!");
  }
}


// ChangeFilterField{from, to, filter} - Changes the field that a filter spec
// refers to from "FROM" to "TO".  This only happens if the old field matches
// from or if fram is "".
//
static VTExp *ChangeFilterField(VTListExp *Params, VarTable &, int *Lint) {
  assert(Params->GetNumElements() == 3);
  if (Params->GetListElement(0)->GetExpType() != VTExp::StringTy ||
      Params->GetListElement(1)->GetExpType() != VTExp::StringTy || 
      Params->GetListElement(2)->GetExpType() != VTExp::StringTy) {      
    if (Lint) *Lint = 1;
    cout << "'ChangeFilterField' function takes exactly three string argume"
         << "nts!\n  I got: '" << Params << "'!\n";
    return new VTStrExp(-1, "'ChangeFilterField' function: bad argument!");
  }

  VTExp *Filter = Params->GetListElement(2);
  String FilterStr(Filter->GetStringValue());
  const CSString FieldMat(Params->GetListElement(0)->GetStringValue());
  CSString NewField(Params->GetListElement(1)->GetStringValue());
  CSString OldField;

  if (FilterStr[0] == '!') {
    FilterStr.Tokenize("!", OldField);
    if (FieldMat.Length() != 0 && OldField != FieldMat)
      NewField = OldField;  // Don't change it!!
    FilterStr = "!"+NewField+"!"+FilterStr;
  } else if (FilterStr[0] == '|') {
    FilterStr.Tokenize("|", OldField);
    if (FieldMat.Length() != 0 && OldField != FieldMat)
      NewField = OldField;  // Don't change it!!
    FilterStr = "|"+NewField+"|"+FilterStr;
  } else {
    if (FieldMat.Length() == 0 || CSString("URL") == FieldMat)
      FilterStr = "|"+NewField+"|"+FilterStr+"|";
  } 

  return new VTStrExp(Params->GetLineNo(), FilterStr);
}


// FilterSplit - Take a string that represents a filter, and split it into a 
// list of its corresponding individual filter components.  For example,
// FilterSplit{"|URL|xyz|!HOST!bzq!"} = ["|URL|xyz|", "!HOST!bzq!"]
//
static VTExp *FilterSplit(VTListExp *Params, VarTable &, int *Lint) {
  assert(Params->GetNumElements() == 1);
  VTExp *Param0 = Params->GetListElement(0);

  if (Param0->GetExpType() != VTExp::StringTy) {
    if (Lint) *Lint = 1;
    cout << "'FilterSplit' function takes a string argument!\n";
    return new VTStrExp(-1, "'FilterSplit' function: bad argument!");
  }

  VTListExp *Result = new VTListExp(Param0->GetLineNo());
  String Param = Param0->GetStringValue();

  while (Param.Length() != 0) {
    int End = 0;
    switch (Param[0]) {
    case '|':      // Unix fileglob...
      End = Param.strchr('|', Param.strchr('|', 1)+1);
      if (End > 0) {
        String F(Param);
        F.Left(End+1);
        Result->AddToTail(new VTStrExp(Param0->GetLineNo(), F));
        Param.Right(Param.Length()-End-1);
      } else {
        delete Result;
        cout << "Malformed filter: " + Param;
        return new VTStrExp(-1, "Malformed filter: " + Param);
      }
      break;

    case '!':      // Regular expression...
      End = Param.strchr('!', Param.strchr('!', 1)+1);
      if (End > 0) {
        String F(Param);
        F.Left(End+1);
        Result->AddToTail(new VTStrExp(Param0->GetLineNo(), F));
        Param.Right(Param.Length()-End-1);
      } else {
        delete Result;
        cout << "Malformed filter: " + Param;
        return new VTStrExp(-1, "Malformed filter: " + Param);
      }
      break;

    default:       // Unix Fileglob till end of string.
      Result->AddToTail(new VTStrExp(Param0->GetLineNo(), Param));
      Param = "";
      break;
    }
  }

  return Result;
}
