// $Id: convmake.cpp 19708 2010-10-29 18:04:21Z d3y133 $

/*
 * Program for converting an NWChem GNUmakefile to an NMAKE file and Visual Studio project files
 *
 * BGJ (7/00)
 */

#include "TokenList.h"
#include <fstream>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <algorithm>
#include <sys/stat.h>

#include <assert.h>
#define ASSERT assert

using namespace std;

string GetFileAsString(const string& Filename);
void Preprocess(string& File);
bool CanAutoGenerate(const string& Filename);

string AutoGenerateMakeFile(const string& GNUmakefile);
string AutoGenerateProjectFile(const string& GNUmakefile, const string& TemplateFile, const string& DirName);

string GetSection(const string& GNUmakefile, const string& SecName);
void ConvertSection(ostream& os, const string& Sec);
void PrintSection(ostream& os, const string& Sec, bool SetNewline = false);
void PrintSectionSpecial(ostream& os, const string& Sec, const string& SecName,
                         bool SetNewline = false);
string ConvertToken(const string& T);
string GetSourceFile(const string& Obj);
bool FileExists(const string& Filename);
string DotDots(const string& GNUmakefile);
void ReplaceAll(string& S, const string& S1, const string& S2);
string MakeSourceSection(const vector<string>& Files);
string MakeDefinesSectionF(const vector<string>& Defs);
string MakeDefinesSectionC(const vector<string>& Defs);
string MakeIncludesSectionF(const vector<string>& Incs);
string MakeIncludesSectionC(const vector<string>& Incs);

bool DoNewline = false;
void ProcessNewline(ostream& os)
{
  if (DoNewline) {
    os << endl;
    DoNewline = false;
  }
}

int main(int argc, char* argv[])
{
  string InFilename = "GNUmakefile";
  string GNUmakefile = GetFileAsString(InFilename);
  Preprocess(GNUmakefile);

  if (argc == 1) {
    // Convert to NMAKE file
    string OutFilename = "MakeFile";
    if (CanAutoGenerate(OutFilename)) {
      ofstream os(OutFilename.c_str());
      ASSERT(os);
      os << AutoGenerateMakeFile(GNUmakefile);
    }
  }
  else if (argc == 3) {
    // Convert to Visual Studio project file
    string TemplateName = argv[1];
    string DirName = *TokenList(argv[2],"/\\").rbegin();
    string Template = GetFileAsString(TemplateName);
    // Check for wrapper vs. real project
    bool DoWrapper = Template.find("DIRWRAP_") != string::npos;
    if (DoWrapper) {
      ReplaceAll(Template, "DIRWRAP_", DirName+"_");
      string OutFilename = DirName + "_wrap.dsp";
      ofstream os(OutFilename.c_str());
      ASSERT(os);
      os << Template;
    }
    else {
      string OutFilename = DirName + ".dsp";
      if (CanAutoGenerate(OutFilename)) {
        cout << "Can generate project file " << OutFilename << endl;
        ofstream os(OutFilename.c_str());
        ASSERT(os);
        os << AutoGenerateProjectFile(GNUmakefile, Template, DirName);
      }
    }
  }

  return 0;
}

string AutoGenerateMakeFile(const string& GNUmakefile)
{
  ostringstream os;

  // Key that tells us a MakeFile has been automatically generated
  string AutoKey = "#\n# NMAKE file automatically generated by convmake - do not edit\n#\n";

  // Identify this MakeFile as auto-generated
  os << AutoKey << endl;

  PrintSection(os,GetSection(GNUmakefile,"SUBDIRS"),true);
  ProcessNewline(os);

  // Easy way to convert commented-out object lists
//  ConvertSection(os,GetSection(GNUmakefile,"OBJ_COMMENT"));
//  ConvertSection(os,GetSection(GNUmakefile,"OBJ_OPTIMIZE_COMMENT"));

  ConvertSection(os,GetSection(GNUmakefile,"OBJ_OPTIMIZE"));
  ConvertSection(os,GetSection(GNUmakefile,"OBJ"));
  ConvertSection(os,GetSection(GNUmakefile,"HEADERS"));

  TokenList TL(GetSection(GNUmakefile,"LIBRARY")," =\\\n");
  if (!TL.empty()) {
    int LibLen = TL[1].length() - 5;
    ASSERT(LibLen > 0);
    os << "LIBRARY = " << TL[1].substr(3,LibLen) << ".lib" << endl;
  }

  PrintSectionSpecial(os,GetSection(GNUmakefile,"LIB_DEFINES"),"LIB_DEFINES");
  PrintSection(os,GetSection(GNUmakefile,"LIB_INCLUDES"));
  os << endl;
//  PrintSection(os,GetSection(GNUmakefile,"LIB_TARGETS"),true);
//  PrintSection(os,GetSection(GNUmakefile,"TESTLIBS"),true);
  ProcessNewline(os);

  os << "!INCLUDE " << DotDots(GNUmakefile) << "config\\NTmakefile.h" << endl;
  os << "!INCLUDE " << DotDots(GNUmakefile) << "config\\NTmakelib.h" << endl;

  return os.str();
}

string AutoGenerateProjectFile(const string& GNUmakefile, const string& TemplateFile, const string& DirName)
{
  string Proj = TemplateFile;

  // Give project the proper name
  ReplaceAll(Proj, "NWCHEM_DIRNAME", DirName);

  // Put the library in the proper dir
  string Dots = DotDots(GNUmakefile);
  Dots.erase(Dots.length()-1, 1);
  ReplaceAll(Proj, "DOTDOTS", Dots);

  // Put the source files in the project
  vector<string> v;
  TokenList TL(GetSection(GNUmakefile,"OBJ_OPTIMIZE")," =\\\n");
  if (TL.size() > 1)
    copy(TL.begin()+1, TL.end(), back_inserter(v));
  TL.assign(GetSection(GNUmakefile,"OBJ")," =\\\n");
  if (TL.size() > 1)
    copy(TL.begin()+1, TL.end(), back_inserter(v));
  sort(v.begin(), v.end());
  for (vector<string>::iterator i = v.begin(); i != v.end(); ++i)
    *i = GetSourceFile(*i);
  Proj.insert(Proj.find("# End Group"), MakeSourceSection(v));

  // Put the header files in the project
  TL.assign(GetSection(GNUmakefile,"HEADERS")," =\\\n");
  if (TL.size() > 1) {
    v.clear();
    copy(TL.begin()+1, TL.end(), back_inserter(v));
    Proj.insert(Proj.rfind("# End Group"), MakeSourceSection(v));
  }

  // Put extra defines into the project
  TL.assign(GetSection(GNUmakefile,"LIB_DEFINES")," \\\n");
  v.clear();
  if (TL.size() > 1) {
    TokenList::const_iterator iTL = TL.begin();
    bool Stop;
    do {
      Stop = *(iTL->rbegin()) == '=';
      ++iTL;
    } while (!Stop && iTL != TL.end());
    if (iTL != TL.end())
      copy(iTL, TL.end(), back_inserter(v));
  }
  ReplaceAll(Proj, "EXTRA_DEFINES_F", MakeDefinesSectionF(v));
  ReplaceAll(Proj, "EXTRA_DEFINES_C", MakeDefinesSectionC(v));

  // Put extra include dirs into the project
  TL.assign(GetSection(GNUmakefile,"LIB_INCLUDES")," =\\\n");
  v.clear();
  if (TL.size() > 1)
    copy(TL.begin()+1, TL.end(), back_inserter(v));
  ReplaceAll(Proj, "EXTRA_INCLUDES_F", MakeIncludesSectionF(v));
  ReplaceAll(Proj, "EXTRA_INCLUDES_C", MakeIncludesSectionC(v));

  return Proj;
}

string GetFileAsString(const string& Filename)
{
  // Create string of correct length at the outset - avoids inefficiency
  // of frequent resizing as the string grows
  struct _stat stats;
  int ret = _stat(Filename.c_str(), &stats);
  ASSERT(ret == 0);
  string s(stats.st_size, ' ');

  // Read the file contents into the pre-set string
  ifstream is(Filename.c_str());
  ASSERT(is);
  string::iterator i = s.begin();
  while (is.get(*i++))
    ;

  return s;
}

void Preprocess(string& File)
{
  string KeyBeg = "BEGIN_WIN32_IGNORE";
  string KeyEnd = "END_WIN32_IGNORE";

  int PosBeg, PosEnd;
  while ((PosBeg = File.find(KeyBeg)) != string::npos) {
    PosEnd = File.find(KeyEnd);
    ASSERT(PosEnd != string::npos);
    File.erase(PosBeg, PosEnd-PosBeg+KeyEnd.length());
  }
}

bool CanAutoGenerate(const string& Filename)
{
  // Check whether output file already exists, and if so, whether it was auto-generated
  bool PreExists = true, AutoGenerated = false;

  {
    ifstream is(Filename.c_str());
    PreExists = is.good();
  }

  if (PreExists) {
    // Key that tells us a file has been automatically generated
    string AutoKey = "generated by convmake - do not edit";
    string S = GetFileAsString(Filename);
    string::size_type pos = S.find(AutoKey);
    AutoGenerated = pos != string::npos;
    if (!AutoGenerated)
      cout << "Special-case " << Filename << " exists" << endl;
  }

  return !PreExists || AutoGenerated;
}

string GetSection(const string& File, const string& Sec)
{
  istringstream is(File);
  ASSERT(is);

  TokenList TL;
  bool Found = false;
  do {
    is >> TL;
    if (!TL.empty()) {
      TokenList TL2(TL[0],"=\\");
      Found = TL2[0] == Sec;
    }
  } while(!Found && is);

  if (Found) { // Get the rest of the lines in the section
    while (*(TL.rbegin()->rbegin()) == '\\') {
      TokenList TL2;
      is >> TL2;
      TL += TL2;
    }
  }

  return TL.getString();
}

void ConvertSection(ostream& os, const string& Sec)
{
  if (Sec.length() == 0) return;
  TokenList TL(Sec," =\\\n");
  string S1, S2;
  for (TokenList::const_iterator i = TL.begin()+1; i != TL.end(); ++i) {
    S1 = (i == TL.begin()+1) ? (TL[0] + " = ") : " ";
    S2 = (i == TL.end()-1) ? "" : " \\";
    left(os);
    os << setw(15) << S1 << ConvertToken(*i) << S2 << endl;
  }
  os << endl;
}

void PrintSection(ostream& os, const string& Sec, bool SetNewline)
{
  if (Sec.length() == 0) return;
  TokenList TL(Sec," =\\\n");
  // Don't print an empty list
  if (TL.size() < 2) return;
  os << TL[0] << " =";
  for (TokenList::const_iterator i = TL.begin()+1; i != TL.end(); ++i)
    os << " " << ConvertToken(*i);
  os << endl;
  if (SetNewline) DoNewline = true;
}

void PrintSectionSpecial(ostream& os, const string& Sec, const string& SecName,
                         bool SetNewline)
{
  if (Sec.length() == 0) return;
  TokenList TL(Sec," \\\n");
  TokenList::const_iterator i = TL.begin();
  bool Stop;
  do {
    Stop = *(i->rbegin()) == '=';
    ++i;
  } while (!Stop && i != TL.end());
  // Don't print an empty list
  if (i == TL.end()) return;
  os << SecName << " =";
  for (; i != TL.end(); ++i)
    os << " " << ConvertToken(*i);
  os << endl;
  if (SetNewline) DoNewline = true;
}

string ConvertToken(const string& T)
{
  string S = T;
  string S1(""), S2("");
  if (T.length() > 2 && T.substr(T.length()-2,2) == ".o") {
    S1 = "$(OBJDIR)\\";
    S2 = "bj";
  }
  for (string::iterator i = S.begin(); i != S.end(); ++i)
    if (*i == '/') *i = '\\';
  return S1 + S + S2;
}

string DotDots(const string& GNUmakefile)
{
  string IncludeSec = GetSection(GNUmakefile,"include");
  TokenList TL(IncludeSec," /");
  string DD;
  for (TokenList::const_iterator i = TL.begin()+1; *i == ".."; ++i)
    DD += "..\\";
  return DD;
}

void ReplaceAll(string& S, const string& S1, const string& S2)
{
  // Replace all occurrences in S of S1 with S2
  int pos;
  while ((pos = S.find(S1)) != string::npos)
    S.replace(pos, S1.length(), S2);
}

string GetSourceFile(const string& Obj)
{
  string Base = Obj.substr(0,Obj.rfind('.')+1);
  vector<string> Suff;
  Suff.push_back("F");
  Suff.push_back("f");
  Suff.push_back("c");
  for (vector<string>::const_iterator i = Suff.begin(); i != Suff.end(); ++i)
    if (FileExists(Base+*i))
      return Base+*i;
  cout << "Could not find source file for " << Obj << endl;
  return "";
}

bool FileExists(const string& Filename)
{
  ifstream is(Filename.c_str());
  return is.good();
}

string MakeSourceSection(const vector<string>& Files)
{
  string Section;
  for (vector<string>::const_iterator i = Files.begin(); i != Files.end(); ++i)
    Section += "# Begin Source File\n\nSOURCE=.\\" + *i + "\n# End Source File\n";
  return Section;
}

string MakeDefinesSectionF(const vector<string>& Defs)
{
  string Section;
  for (vector<string>::const_iterator i = Defs.begin(); i != Defs.end(); ++i) {
    if (i != Defs.begin()) Section += " ";
    Section += "/define:\"" + string(i->begin()+2,i->end()) + "\"";
  }
  return Section;
}

string MakeDefinesSectionC(const vector<string>& Defs)
{
  string Section;
  for (vector<string>::const_iterator i = Defs.begin(); i != Defs.end(); ++i) {
    if (i != Defs.begin()) Section += " ";
    Section += "/D \"" + string(i->begin()+2,i->end()) + "\"";
  }
  return Section;
}

string MakeIncludesSectionF(const vector<string>& Incs)
{
  string Section;
  for (vector<string>::const_iterator i = Incs.begin(); i != Incs.end(); ++i) {
    if (i != Incs.begin()) Section += " ";
    Section += "/include:\"" + string(i->begin()+2,i->end()) + "\"";
  }
  return Section;
}

string MakeIncludesSectionC(const vector<string>& Incs)
{
  string Section;
  for (vector<string>::const_iterator i = Incs.begin(); i != Incs.end(); ++i) {
    if (i != Incs.begin()) Section += " ";
    Section += "/I \"" + string(i->begin()+2,i->end()) + "\"";
  }
  return Section;
}
