/*  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$ */

#ifndef __FILEINFO_H
#define __FILEINFO_H

#include "curdir.h"
#include "rule.h"
#include "md5.h"

#ifdef WIN32
#define OSPATHSEP '\\'
#define OSPATHSEPSTR "\\"
#define OSPATHENVSEP      ';'
#define OSPATHENVSEPSTR   ";"
#else
#define OSPATHSEP '/'
#define OSPATHSEPSTR "/"
#define OSPATHENVSEP      ':'
#define OSPATHENVSEPSTR   ":"
#endif

extern bool g_DumpOnError;
extern bool g_PrintVarsAndRules;
extern bool g_DoNotExecute;
extern bool g_GenProjectTree;
extern bool g_Quiet;
extern bool g_RebuildAll;
extern bool g_PrintAdditionalInfo;
extern bool g_pPrintDependencyCheck;
extern bool g_CheckCircularDeps;
extern bool g_ForceAutoDepRescan;
extern bool g_PrintLexYacc;
extern bool g_Clean;
extern bool g_StopCompiling;
extern bool g_PrintMultipleDefinedRules;

extern const string g_EmptyString;
extern const string g_SpaceString;
extern const string g_QuoteString;

string QuoteFileName(const string &Filename);
string UnquoteFileName(const string &Filename);

template<typename T>
inline string stringify(const T& x)
{
  ostringstream o;
  o << x;
  return o.str();
}

#define TIMESAFETY 3
class mh_time
{
  enum
  {
    DATENOTVALID=0,
    NOTEXISTTIME=1,
    DIRTIME     =2+TIMESAFETY
  };
  unsigned long m_Time;

  bool operator < (const mh_time &Src);
public:
  mh_time(){m_Time=DATENOTVALID;}
  mh_time(time_t Time) : m_Time((unsigned long)Time) {}
  mh_time(unsigned long Time) : m_Time(Time) {}
  mh_time(const mh_time &Time) : m_Time(Time.m_Time) {}

  void SetDir(void)
  {
    m_Time=DIRTIME;
  }
  bool IsDir(void) const
  {
    return m_Time==DIRTIME;
  }
  void SetNotExist(void)
  {
    m_Time=NOTEXISTTIME;
  }
  bool IsExistingFile(void) const
  {
    return m_Time>DIRTIME;
  }
  bool DoesExist(void) const
  {
    return m_Time!=NOTEXISTTIME;
  }
  void Invalidate(void)
  {
    m_Time=DATENOTVALID;
  }
  bool IsDateValid(void) const
  {
    return m_Time!=DATENOTVALID;
  }
  friend ostream& operator<<(ostream& out,const mh_time &Src);

  mh_time &operator = (const mh_time &Src) { m_Time=Src.m_Time; return *this;}

  bool IsOlder(const mh_time &Src) const { return m_Time<Src.m_Time-TIMESAFETY; }

  bool IsNewer(const mh_time &Src) const { return m_Time>Src.m_Time+TIMESAFETY; }
  bool IsNewerOrSame(const mh_time &Src) const { return m_Time>=Src.m_Time-TIMESAFETY; }
};
typedef mh_time mh_time_t;

inline ostream& operator<<(ostream& out,const mh_time &Src)
{
  out << hex << (unsigned long)Src.m_Time;
  return out;
}

class fileinfo : public refbase
{
  string m_AbsFileName;
  bool m_IsPhony;
  int m_BuildStatus;  /* Bit 0 means the target built is started, Bit 1 means the target is still building */
  refptr<rule> m_pRule;
  vector< refptr<fileinfo> > m_Deps;
  mh_time_t m_Date;
  uint32 m_CommandsMd5_32;  // 32-bit Md5 checksum of commands to build this target

  fileinfo(const fileinfo &Src);
  fileinfo(void);
public:

  fileinfo(const string &AbsFileName,uint32 Md5_32)
  {
    m_IsPhony=false;
    m_BuildStatus=0;
    m_AbsFileName=UnquoteFileName(AbsFileName);
    InvalidateDate();
    m_CommandsMd5_32=Md5_32;
    #ifdef _DEBUG
    if (g_PrintAdditionalInfo)
      cout << "Initialising Md5 of "<<GetQuotedFullFileName().c_str()<<" to 0x"<<hex<<Md5_32<<endl;
    #endif
  }
  fileinfo(const string &AbsFileName)
  {
    new (this) fileinfo(AbsFileName,0);
  }
  /* The following constructor is only used for name comparisons, and should only be used for that */
  fileinfo(int Dummy,const string &AbsFileName)
  {
    m_AbsFileName=UnquoteFileName(AbsFileName);
  }

  fileinfo(const char *szFile)
  {
    new (this) fileinfo(string(szFile));
  }
  fileinfo(const char *szFile,uint32 Md5_32)
  {
    new (this) fileinfo(string(szFile),Md5_32);
  }
  ~fileinfo()
  {
  }
  const string &GetFullFileName(void) const
  {
    return m_AbsFileName;
  }
  string GetQuotedFullFileName(void) const
  {
    return QuoteFileName(m_AbsFileName);
  }
  void SetFullFileName(const string &strAbsName)
  {
    m_AbsFileName=UnquoteFileName(strAbsName);
    // If the last char is path sep strip it
    if (!m_AbsFileName.empty() && m_AbsFileName[m_AbsFileName.length()-1]==OSPATHSEP)
      m_AbsFileName.resize(m_AbsFileName.length()-1);
  }
  fileinfo &operator = (const fileinfo &Src)
  {
    new (this) fileinfo(Src);
    return *this;
  }

  refptr<fileinfo> GetDir(void) const;
  string GetName() const;
  bool IsDir() const;

  string GetErrorMessageDuplicateRule(const refptr<rule> &pRule);

  void SetRule(refptr<rule> &pRule)
  {
    #if defined(_DEBUG) && defined(_MSC_VER)
    if (m_pRule && m_pRule->GetCommands().size() && !IsBuilding()) {
      DebugBreak();
    }
    #endif
    m_pRule=pRule;
  }

  void SetRuleIfNotExist(refptr<rule> &pRule)
  {
    if (pRule)
    {
      if (!m_pRule)
      {
        SetRule(pRule);
        pRule->AddTarget(this);
      }
      #ifdef _DEBUG
      else
      {
        if (*m_pRule!=*pRule)
        {
          throw(GetErrorMessageDuplicateRule(pRule));
        }
        else if (g_PrintMultipleDefinedRules)
        {
          cerr<<GetErrorMessageDuplicateRule(pRule);
        }
      }
      #endif
    }
  }

  refptr<rule> GetRule(void)
  {
    return m_pRule;
  }
  void AddDep(const refptr<fileinfo> &Dep)
  {
    if (&*Dep==this)
    {
      #ifdef _DEBUG
      cout << GetQuotedFullFileName()<<" is directly dependent on itself\n";
      #endif
      return;
    }
    m_Deps.push_back(Dep);
  }
  void AddDeps(vector< refptr<fileinfo> > &Deps);

  void InsertDeps(vector< refptr<fileinfo> > &Deps)
  {
    vector< refptr<fileinfo> > NewDeps;
    vector< refptr<fileinfo> >::const_iterator It=Deps.begin();
    vector< refptr<fileinfo> >::const_iterator ItEnd=Deps.end();
    while (It!=ItEnd)
    {
      if (&**It==this)
      {
        #ifdef _DEBUG
        cout << GetQuotedFullFileName()<<" is directly dependent on itself\n";
        #endif
      }
      else
        NewDeps.push_back(*It);
      It++;
    }
    if (NewDeps.size())
      m_Deps.insert(m_Deps.begin(),NewDeps.begin(),NewDeps.end());
  }
  void AddMainDep(refptr<fileinfo> &MainDep)
  {
    if (&*MainDep==this)
    {
      #ifdef _DEBUG
      cout << GetQuotedFullFileName()<<" is directly dependent on itself\n";
      #endif
      return;
    }
    m_Deps.insert(m_Deps.begin(),MainDep);
  }
  vector< refptr<fileinfo> > &GetDeps(void)
  {
    return m_Deps;
  }
  string GetPrerequisits(void) const;
  void SetPhony(void)
  {
    m_IsPhony=true;
    m_Date.SetNotExist(); // This will sure that this target will always be build (even if a corresponding file exists)
  }
  bool IsPhony(void)
  {
    return m_IsPhony;
  }
  mh_time_t realGetDate(void);
#ifdef _DEBUG
  void SetDateToNow(void);
#endif

  void SetDate(mh_time_t Date)
  {
    m_Date=Date;
  }

  bool IsDateValid() const
  {
    return m_Date.IsDateValid();
  }
  void InvalidateDate(void)
  {
    m_Date.Invalidate();
  }

  mh_time_t GetDate(void)
  {
    if (m_Date.IsDateValid())
      return m_Date;
    else
      return realGetDate();
  }
  void SetNotExist(void)
  {  // this is used to make sure that this item is rebuild, even if it really exists
    m_Date.SetNotExist();
  }
  bool Exists(void)
  {
    return GetDate().DoesExist();
  }
  bool IsBuildStarted(void) const
  {
    return (m_BuildStatus&1)==1;
  }
  bool IsBuild(void) const
  {
    return m_BuildStatus==1;
  }
  void SetBuild(void)
  {
    m_BuildStatus=1;
  }
  bool IsBuilding(void) const
  {
    return (m_BuildStatus&2)==2;
  }
  void SetBuilding(void)
  {
    m_BuildStatus|=2;
  }
  void ClearBuilding(void)
  {
    m_BuildStatus&=~2;
  }
  bool IsAutoDepExtention(void) const;

  void SetCommandsMd5_32(uint32 Md5_32)
  {
    #ifdef _DEBUG
    if (g_PrintAdditionalInfo)
      cout << "Setting Md5 of "<<GetQuotedFullFileName()<<" to 0x"<<hex<<Md5_32<<endl;
    #endif
    m_CommandsMd5_32=Md5_32;
  }
#ifdef _DEBUG
  uint32 GetCommandsMd5_32(void) const
  {
    return m_CommandsMd5_32;
  }
#endif
  bool CompareMd5_32(uint32 Md5_32) const
  {
    return m_CommandsMd5_32==Md5_32;
  }
  void WriteMd5_32(FILE *pFile) const
  {
    fwrite(&m_CommandsMd5_32,sizeof(m_CommandsMd5_32),1,pFile);
  }
};

struct less_refptrfileinfo : public binary_function <refptr<fileinfo>, refptr<fileinfo>, bool>
{
  bool operator()(const refptr<fileinfo>& _Left, const refptr<fileinfo>& _Right) const
  {
    return less<string>().operator ()(_Left->GetFullFileName(),_Right->GetFullFileName());
  }
};

struct less_fileinfo : public binary_function <const fileinfo*, const fileinfo*, bool>
{
  bool operator()(const fileinfo *_Left, const fileinfo *_Right) const
  {
    return less<string>().operator ()(_Left->GetFullFileName(),_Right->GetFullFileName());
  }
};

extern const string NullString;
extern refptr<fileinfo> NullFileInfo;

const refptr<fileinfo> &GetFileInfo(const string &szName,const refptr<fileinfo> &pRelDir);

extern set<refptr<fileinfo>,less_refptrfileinfo > g_FileInfos;

inline const refptr<fileinfo> &GetAbsFileInfo(const string &strAbsName)
{
  static refptr<fileinfo> SearchFileInfo(new fileinfo(""));
  SearchFileInfo->SetFullFileName(strAbsName);
  /* Using find is just an optimalisation, you could use insert immediately */
  set<refptr<fileinfo>,less_refptrfileinfo >::const_iterator pFind=g_FileInfos.find(SearchFileInfo);
  if (pFind==g_FileInfos.end())
  {
    pair <set<refptr<fileinfo>,less_refptrfileinfo >::iterator, bool> pPair=g_FileInfos.insert(new fileinfo(SearchFileInfo->GetFullFileName()));
    return *(pPair.first);
  }
  else
    return *pFind;
}

inline const refptr<fileinfo> &GetFileInfo(const char *szName,const refptr<fileinfo> &RelDir)
{
  return GetFileInfo(string(szName),RelDir);
}

string &NormalizePathName(string &Name);
void PrintFileInfos();

#endif