/*  This file is part of mhmake.
 *
 *  Copyright (C) 2001-2009 Marc Haesen
 *
 *  Mhmake is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  Mhmake is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with Mhmake.  If not, see <http://www.gnu.org/licenses/>.
*/

/* $Rev$ */

#include "stdafx.h"

#include "util.h"
#include "mhmakefileparser.h"
#include "rule.h"

static const string s_QuoteString("\"");

funcdef mhmakefileparser::m_FunctionsDef[]= {
  {"filter",     &mhmakefileparser::f_filter}
 ,{"call",       &mhmakefileparser::f_call}
 ,{"subst",      &mhmakefileparser::f_subst}
 ,{"patsubst",   &mhmakefileparser::f_patsubst}
 ,{"concat",     &mhmakefileparser::f_concat}
 ,{"if",         &mhmakefileparser::f_if}
 ,{"findstring", &mhmakefileparser::f_findstring}
 ,{"firstword",  &mhmakefileparser::f_firstword}
 ,{"wildcard",   &mhmakefileparser::f_wildcard}
 ,{"basename",   &mhmakefileparser::f_basename}
 ,{"notdir",     &mhmakefileparser::f_notdir}
 ,{"dir",        &mhmakefileparser::f_dir}
 ,{"shell",      &mhmakefileparser::f_shell}
 ,{"relpath",    &mhmakefileparser::f_relpath}
 ,{"toupper",    &mhmakefileparser::f_toupper}
 ,{"tolower",    &mhmakefileparser::f_tolower}
 ,{"exist",      &mhmakefileparser::f_exist}
 ,{"filesindirs",&mhmakefileparser::f_filesindirs}
 ,{"fullname"   ,&mhmakefileparser::f_fullname}
 ,{"addprefix"  ,&mhmakefileparser::f_addprefix}
 ,{"addsuffix"  ,&mhmakefileparser::f_addsuffix}
 ,{"filter-out" ,&mhmakefileparser::f_filterout}
 ,{"word"       ,&mhmakefileparser::f_word}
 ,{"words"      ,&mhmakefileparser::f_words}
 ,{"strip"      ,&mhmakefileparser::f_strip}
 ,{"which"      ,&mhmakefileparser::f_which}
};

map<string,function_f> mhmakefileparser::m_Functions;

bool mhmakefileparser::m_FunctionsInitialised;

///////////////////////////////////////////////////////////////////////////////
// Loop over a list of filenames
static string IterList(const string &List,string (*iterFunc)(const string &FileName,void *pArg), void *pArg=NULL)
{
  const char *pTmp=List.c_str();
  string Ret=g_EmptyString;
  bool first=true;
  while (*pTmp)
  {
    if (!first)
    {
      Ret+=g_SpaceString;
    }
    else
    {
      first=false;
    }
    string Item;
    pTmp=NextItem(pTmp,Item);
    Item=iterFunc(Item,pArg);
    if (Item.empty())
      first=true;  // Do not add space the next iteration
    else
      Ret+=Item;
  }

  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
void mhmakefileparser::InitFuncs(void)
{
  for (int i=0; i<sizeof(m_FunctionsDef)/sizeof(funcdef); i++)
    m_Functions[m_FunctionsDef[i].szFuncName]=m_FunctionsDef[i].pFunc;
}

///////////////////////////////////////////////////////////////////////////////
static string TrimString(const string &Input)
{
  unsigned Start=0;
  while (strchr(" \t",Input[Start])) Start++;
  if (Start>=Input.size())
    return g_EmptyString;
  size_t Stop=Input.size()-1;
  while (strchr(" \t",Input[Stop])) Stop--;
  return Input.substr(Start,Stop-Start+1);
}

///////////////////////////////////////////////////////////////////////////////

static string filter(const string &FileName, void *pvFilter)
{
  string *pFilter=(string*)pvFilter;
  if (PercentMatchList(UnquoteFileName(FileName),*pFilter))
    return FileName;
  else
    return g_EmptyString;
}
///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_filter(const string & Arg) const
{
  size_t ipos=Arg.find(',');
  #ifdef _DEBUG
  if (ipos==string::npos) {
    throw string("filter func should have 2 arguments: ")+Arg;
  }
  #endif
  string Str=TrimString(Arg.substr(0,ipos));
  string List=Arg.substr(ipos+1);

  if (Str.empty())
    return Str;

  return IterList(List,filter,(void*)&Str);
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_filterout(const string & Arg) const
{
  size_t ipos=Arg.find(',');
  #ifdef _DEBUG
  if (ipos==string::npos) {
    throw string("filter func should have 2 arguments: ")+Arg;
  }
  #endif
  string Str=TrimString(Arg.substr(0,ipos));
  string List=Arg.substr(ipos+1);

  if (Str.empty())
    return Str;

  bool First=true;
  string Ret;
  char *pTok=strtok((char*)List.c_str()," \t");   // doing this is changing string, so this is very dangerous
  while (pTok)
  {
    string Item(pTok);
    if (!PercentMatchList(Item,Str))
    {
      if (First)
      {
        Ret=Item;
        First=false;
      }
      else
      {
        Ret+=g_SpaceString;
        Ret+=Item;
      }
    }
    pTok=strtok(NULL," \t");
  }
  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_call(const string & Arg) const
{
  const char *pTmp=Arg.c_str();

  bool LastCharIsComma=Arg[Arg.length()-1]==',';

  string Func;
  pTmp=NextCharItem(pTmp,Func,',');
  map<string,string>::const_iterator pFunc=m_Variables.find(Func);
  #ifdef _DEBUG
  if (pFunc==m_Variables.end())
  {
    throw string("call to non existing function ")+Func;
  }
  #endif
  Func=pFunc->second;
  int i=0;
  while (*pTmp || LastCharIsComma) {
    if (!*pTmp)
      LastCharIsComma=false; /* To stop the loop */
    string Repl;
    pTmp=NextCharItem(pTmp,Repl,',');
    i++;
    char Tmp[10];
    ::sprintf(Tmp,"$(%d)",i);
    size_t Len=::strlen(Tmp);
    size_t Pos=Func.find(Tmp);
    while (Func.npos!=Pos)
    {
      Func=Func.substr(0,Pos)+Repl+Func.substr(Pos+Len);
      Pos=Func.find(Tmp,Pos+Len);
    }
  }

  return ExpandExpression(Func);
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_subst(const string & Arg) const
{
  const char *pTmp=Arg.c_str();

  string FromString;
  pTmp=NextCharItem(pTmp,FromString,',');
  #ifdef _DEBUG
  if (!*pTmp) {
    throw string("Wrong number of arguments in function subst");
  }
  #endif

  string ToString;
  pTmp=NextCharItem(pTmp,ToString,',');
  string Text;
  NextCharItem(pTmp,Text,',');

  if (FromString.empty())
    return Text;

  string Ret;
  size_t Pos=Text.find(FromString);
  size_t PrevPos=0;
  while (Pos!=string::npos)
  {
    Ret+=Text.substr(PrevPos,Pos-PrevPos);
    Ret+=ToString;
    PrevPos=Pos+FromString.length();
    Pos=Text.find(FromString,PrevPos);
  }
  Ret+=Text.substr(PrevPos);

  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_patsubst(const string & Arg) const
{
  const char *pTmp=Arg.c_str();

  string FromString;
  pTmp=NextCharItem(pTmp,FromString,',');
  #ifdef _DEBUG
  if (!*pTmp) {
    throw string("Wrong number of arguments in function subst");
  }
  #endif

  string ToString;
  pTmp=NextCharItem(pTmp,ToString,',');
  string Text;
  NextCharItem(pTmp,Text,',');

  if (FromString.empty())
    return Text;

  return Substitute(Text,FromString,ToString);
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_concat(const string & Arg) const
{
  const char *pTmp=Arg.c_str();

  string JoinString;
  pTmp=NextCharItem(pTmp,JoinString,',');

  string List;
  pTmp=NextCharItem(pTmp,List,',');

  if (JoinString.empty() && List.empty())
  {
    /* assume as $(concat ,,items) construct */
    JoinString=",";
    pTmp=NextCharItem(pTmp,List,',');
  }

  bool First=true;
  string Ret;
  char *pTok=strtok((char*)List.c_str()," \t");   // doing this is changing string, so this is very dangerous
  while (pTok)
  {
    if (First)
    {
      First=false;
    }
    else
    {
      Ret+=JoinString;
    }
    Ret+=pTok;
    pTok=strtok(NULL," \t");
  }
  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_if(const string & Arg) const
{
  const char *pTmp=Arg.c_str();

  string Cond;
  pTmp=NextCharItem(pTmp,Cond,',');
  string Ret;
  if (Cond.empty())
  {
    pTmp=NextCharItem(pTmp,Ret,',');
  }
  NextCharItem(pTmp,Ret,',');
  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_findstring(const string & Arg) const
{
  const char *pTmp=Arg.c_str();
  string find;
  pTmp=NextCharItem(pTmp,find,',');
  string instr;
  NextCharItem(pTmp,instr,',');

  if (instr.find(find) != instr.npos)
    return find;
  else
    return g_EmptyString;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_firstword(const string & Arg) const
{
  string FirstWord;
  NextItem(Arg.c_str(),FirstWord);
  return FirstWord;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_wildcard(const string & Arg) const
{
  refptr<fileinfo> FileSpec=GetFileInfo(TrimString(Arg),m_MakeDir); /* Use GetFileInfo to make the relative path absolute */
  refptr<fileinfo> Dir=FileSpec->GetDir();
#ifdef WIN32
  struct _finddata_t FileInfo;
  intptr_t hFile=_findfirst(FileSpec->GetFullFileName().c_str(),&FileInfo);
  if (hFile==-1)
    return g_EmptyString;

  string Ret=g_EmptyString;

  /* We have to verify with percentmatch since the find functions *.ext also matches the functions *.extbrol */
  string CheckSpec=FileSpec->GetName();
  if (PercentMatch(FileInfo.name,CheckSpec,NULL,'*'))
  {
    Ret=GetFileInfo(FileInfo.name,Dir)->GetQuotedFullFileName();
  }
  while (-1!=_findnext(hFile,&FileInfo))
  {
    if (PercentMatch(FileInfo.name,CheckSpec,NULL,'*'))
    {
      Ret+=g_SpaceString;
      Ret+=GetFileInfo(FileInfo.name,Dir)->GetQuotedFullFileName();
    }
  }
  _findclose(hFile);
#else
  glob_t Res;
  if (glob (FileSpec->GetFullFileName().c_str(), GLOB_ERR|GLOB_NOSORT|GLOB_MARK, NULL, &Res))
    return g_EmptyString;

  string Ret=g_EmptyString;
  string SepStr=g_EmptyString;
  string CheckSpec=FileSpec->GetName();
  for (int i=0; i<Res.gl_pathc; i++)
  {
    if (PercentMatch(Res.gl_pathv[i],CheckSpec,NULL,'*'))
    {
      Ret+=SepStr;
      Ret+=GetFileInfo(Res.gl_pathv[i],Dir)->GetQuotedFullFileName();
      SepStr=g_SpaceString;
    }
  }

  globfree(&Res);
#endif
  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_exist(const string & Arg) const
{
  string File=TrimString(Arg);
  refptr<fileinfo> pFile=GetFileInfo(File,m_MakeDir);
  if (pFile->Exists())
  {
    return string("1");
  }
  else
    return g_EmptyString;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_filesindirs(const string & Arg) const
{
  const char *pTmp=Arg.c_str();

  string strFiles;
  pTmp=NextCharItem(pTmp,strFiles,',');
  #ifdef _DEBUG
  if (!*pTmp) {
    throw string("Wrong number of arguments in function filesindirs");
  }
  #endif
  string strDirs;
  NextCharItem(pTmp,strDirs,',');

  vector< refptr<fileinfo> > Dirs;
  SplitToItems(strDirs,Dirs);

  pTmp=strFiles.c_str();
  string Ret=g_EmptyString;
  bool first=true;
  while (*pTmp)
  {
    string File;
    refptr<fileinfo> pFile;
    pTmp=NextItem(pTmp,File);

    vector< refptr<fileinfo> >::iterator It=Dirs.begin();
    vector< refptr<fileinfo> >::iterator ItEnd=Dirs.end();
    while (It!=ItEnd)
    {
      pFile=GetFileInfo(File,*It++);
      if (pFile->Exists())
      {
        break;
      }
      pFile=NullFileInfo;
    }
    if (!pFile)
      continue;

    if (!first)
    {
      Ret+=g_SpaceString;
    }
    else
    {
      first=false;
    }
    Ret+=pFile->GetQuotedFullFileName();
  }

  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_fullname(const string & Arg) const
{
  string File=TrimString(Arg);
  refptr<fileinfo> pFile=GetFileInfo(File,m_MakeDir);
  return pFile->GetQuotedFullFileName();
}

///////////////////////////////////////////////////////////////////////////////
static string basename(const string &FileName,void*)
{
  string Ret=UnquoteFileName(FileName);
  Ret=Ret.substr(0,Ret.find_last_of('.'));
  return QuoteFileName(Ret);
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_basename(const string & FileNames) const
{
  return IterList(FileNames,basename);
}

///////////////////////////////////////////////////////////////////////////////
static string notdir(const string &FileName,void*)
{
  string Ret=UnquoteFileName(FileName);
  size_t Pos=Ret.find_last_of(OSPATHSEP);
  if (Pos==string::npos)
  {
    return FileName;
  }
  else
  {
    Ret=Ret.substr(Pos+1);
    return QuoteFileName(Ret);
  }

}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_notdir(const string & FileNames) const
{
  return IterList(FileNames,notdir);
}

///////////////////////////////////////////////////////////////////////////////
static string addprefix(const string &FileName,void *pPrefix)
{
  return *(const string *)pPrefix+FileName;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_addprefix(const string & Arg) const
{
  const char *pTmp=Arg.c_str();
  string PreFix;
  pTmp=NextCharItem(pTmp,PreFix,',');
  #ifdef _DEBUG
  if (!*pTmp) {
    throw ("Wrong number of arguments in function addprefix");
  }
  #endif
  string FileNames;
  pTmp=NextCharItem(pTmp,FileNames,',');
  return IterList(FileNames,addprefix,&PreFix);
}

///////////////////////////////////////////////////////////////////////////////
static string addsuffix(const string &FileName,void *pSuffix)
{
  return FileName+*(const string *)pSuffix;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_addsuffix(const string & Arg) const
{
  const char *pTmp=Arg.c_str();
  string SufFix;
  pTmp=NextCharItem(pTmp,SufFix,',');
  #ifdef _DEBUG
  if (!*pTmp) {
    throw string("Wrong number of arguments in function addsuffix");
  }
  #endif
  string FileNames;
  pTmp=NextCharItem(pTmp,FileNames,',');
  return IterList(FileNames,addsuffix,&SufFix);
}

///////////////////////////////////////////////////////////////////////////////
// Returns the n-th word number
string mhmakefileparser::f_word(const string & Arg) const
{
  const char *pTmp=Arg.c_str();

  string strNum;
  pTmp=NextCharItem(pTmp,strNum,',');

  int WordNbr=atoi(strNum.c_str());

  #ifdef _DEBUG
  if (!WordNbr)
  {
    if (!WordNbr) {
      throw string ("Expecting a number bigger then 0 for the word function");
    }
  }
  #endif

  int CurWord=0;
  while (*pTmp)
  {
    string Word;
    pTmp=NextItem(pTmp,Word);
    CurWord++;
    if (CurWord==WordNbr)
      return Word;
  }

  return g_EmptyString;
}

///////////////////////////////////////////////////////////////////////////////
// Returns the number of words
string mhmakefileparser::f_words(const string & Arg) const
{
  const char *pTmp=Arg.c_str();
  int NrWords=0;
  char szNumber[10];
  while (*pTmp)
  {
    string Word;
    pTmp=NextItem(pTmp,Word);
    NrWords++;
  }
  sprintf(szNumber,"%d",NrWords);

  return szNumber;
}

///////////////////////////////////////////////////////////////////////////////
// Search for a command in the enivornment path
string mhmakefileparser::f_which(const string & Arg) const
{
  return SearchCommand(Arg);
}

///////////////////////////////////////////////////////////////////////////////
// Removes leading and trailing space
string mhmakefileparser::f_strip(const string & Arg) const
{
  string::const_iterator pFirst=Arg.begin();
  string::const_iterator pLast=Arg.end();
  while (strchr(" \t\r\n",*pFirst) && pFirst!=pLast) pFirst++;
  if (pFirst==pLast)
    return "";
  while (strchr(" \t\r\n",*(--pLast)));
  pLast++;
  return Arg.substr(pFirst-Arg.begin(),pLast-pFirst);
}

///////////////////////////////////////////////////////////////////////////////
static string dir(const string &FileName, void *)
{
  size_t Pos=FileName.find_last_of(OSPATHSEP);
  if (Pos==string::npos)
  {
    return g_EmptyString;
  }
  else
  {
    string Ret=g_EmptyString;
    Ret+=FileName.substr(0,Pos+1);
    if (FileName[0]=='"' && FileName.end()[-1]=='"')
      Ret+=s_QuoteString;
    return Ret;
  }

}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_dir(const string & FileNames) const
{
  return IterList(FileNames,dir);
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_shell(const string & Command) const
{
  string Output;

#ifdef _DEBUG
  if (g_PrintAdditionalInfo)
    cout << "shell: executing: Command '"<<Command<<"'"<<endl;
#endif

  ((mhmakefileparser*)this)->ExecuteCommand(string("@")+Command,&Output);  // Make sure that the command is not echoed
#ifdef _DEBUG
  if (g_PrintAdditionalInfo)
    cout << "shell returned '"<<Output<<"'"<<endl;
#endif
  return Output;
}

///////////////////////////////////////////////////////////////////////////////
static string relpath(const string &FileName,void *pvDir)
{
  const refptr<fileinfo> pDir=*(const refptr<fileinfo> *)pvDir;
  refptr<fileinfo> Path=GetFileInfo(FileName,pDir);
  const char *pCur=pDir->GetFullFileName().c_str();
  const char *pPath=Path->GetFullFileName().c_str();

  const char *pLast=pPath;
  while (*pCur==*pPath)
  {
    char Char=*pPath;
    if (!Char)
    {
      return "."; // Means that FileName is the same as the current directory
    }
    if (Char==OSPATHSEP)
      pLast=pPath+1;
    pCur++;
    pPath++;
  }
  if (*pPath==OSPATHSEP && !*pCur)
    pLast=pPath+1;
  string retPath;
  if (*pCur==OSPATHSEP) {
    bool first=true;
    pCur++;
    retPath="..";
    while (*pCur)
    {
      if (*pCur==OSPATHSEP)
        retPath+=OSPATHSEPSTR"..";
      pCur++;
    }
    if (pPath)
      retPath=retPath+OSPATHSEPSTR+pLast;
  }
  else
  {
    if (*pCur)
      retPath=".."OSPATHSEPSTR;
    while (*pCur)
    {
      if (*pCur==OSPATHSEP)
        retPath+=".."OSPATHSEPSTR;
      pCur++;
    }
    retPath+=pLast;
  }
  return QuoteFileName(retPath);
}

///////////////////////////////////////////////////////////////////////////////
// Make a path name relative to the current directory
string mhmakefileparser::f_relpath(const string & FileNames) const
{
  return IterList(FileNames,relpath,(void*)&m_MakeDir);
}

///////////////////////////////////////////////////////////////////////////////
static string makeupper(const string &FileName,void *)
{
  string Ret=FileName;
  string::const_iterator pSrc=FileName.begin();
  string::iterator pDest=Ret.begin();
  while (pSrc!=FileName.end())
  {
    *pDest++ = toupper(*pSrc++);
  }
  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_toupper(const string & FileNames) const
{
  return IterList(FileNames,makeupper);
}

///////////////////////////////////////////////////////////////////////////////
static string makelower(const string &FileName, void *)
{
  string Ret=FileName;
  string::const_iterator pSrc=FileName.begin();
  string::iterator pDest=Ret.begin();
  while (pSrc!=FileName.end())
  {
    *pDest++ = tolower(*pSrc++);
  }
  return Ret;
}

///////////////////////////////////////////////////////////////////////////////
string mhmakefileparser::f_tolower(const string & FileNames) const
{
  return IterList(FileNames,makelower);
}