//===============================================================
// mmaker.cpp	- makefileMaker Class for VIDE
//
// Copyright (C) 1995,1996, 1997, 1998  Bruce E. Wampler
//
// This file is part of the V C++ GUI Framework, and is covered
// under the terms of the GNU Library General Public License,
// Version 2. This library has NO WARRANTY. See the source file
// vapp.cxx for more complete information about license terms.
//===============================================================

#include <v/vnotice.h>
#include <v/vos.h>
#include "mmaker.h"
#include "videapp.h"

//==================>>>   makefileMaker::fieldLen    <<<============================

    const int makefileMaker::fieldLen = 256;
    static const char SecProjOpts[] = "//% Section 1 - PROJECT OPTIONS";
    static const char SecProjOptsEnd[] = "//%end-proj-opts";
    static const char SecMakefile[] = "//% Section 2 - MAKEFILE";
    static const char SecOptions[] = "//% Section 3 - OPTIONS";
    static const char SecOptionsEnd[] = "//%end-options";
    static const char SecHomeV[] = "//% Section 4 - HOMEV";
    static const char SecTarget[] = "//% Section 5  - TARGET FILE";
    static const char SecObjFiles[] = "//% Section 6  - SOURCE FILES";
    static const char SecObjFilesEnd[] = "//%end-srcfiles";
    static const char SecCompiler[] = "//% Section 7  - COMPILER NAME";
    static const char SecIncDirs[] = "//% Section 8  - INCLUDE DIRECTORIES";
    static const char SecIncDirsEnd[] = "//%end-include-dirs";
    static const char SecLibDirs[] = "//% Section 9 - LIBRARY DIRECTORIES";
    static const char SecLibDirsEnd[] = "//%end-library-dirs";
    static const char SecDefs[] = "//% Section 10  - DEFINITIONS";
    static const char SecDefsPoolEnd[] = "//%end-defs-pool";
    static const char SecDefsEnd[] = "//%end-defs";
    static const char SecCFlags[] = "//% Section 11  - C FLAGS";
    static const char SecLibFlags[] = "//% Section 12  - LIBRARY FLAGS";
    static const char SecSrcDir[] = "//% Section 13  - SRC DIRECTORY";
    static const char SecObjDir[] = "//% Section 14  - OBJ DIRECTORY";
    static const char SecBinDir[] = "//% Section 15 - BIN DIRECTORY";
    static const char SecUserTargets[] = "//% Section 16 - USER TARGETS";
    static const char SecUserTargetsEnd[] = "//%end-user-targets";


//==================>>> makefileMaker::makefileMaker <<<=========================
  makefileMaker::makefileMaker()
  {
    makeName = new char[fieldLen];
    homeV =   new char[fieldLen];
    exeName = new char[fieldLen];
    cc =      new char[fieldLen];
    cFlags =  new char[fieldLen];
    Libs =    new char[fieldLen];
    ObjDir =  new char[fieldLen];
    BinDir =  new char[fieldLen];
    SrcDir =  new char[fieldLen];

    setDefaults();
    cmdw = ((videApp*)theApp)->GetMsgWindow();

  }

//======================>>> makefileMaker::~makefileMaker <<<============================
  makefileMaker::~makefileMaker()
  {
    delete [] makeName;
    delete [] homeV;
    delete [] exeName;
    delete [] cc;
    delete [] cFlags;
    delete [] Libs;
    delete [] BinDir;
    delete [] SrcDir;
    delete [] ObjDir;
  }
 
//======================>>> makefileMaker::srcN2ObjN <<<=========================
  char* makefileMaker::srcN2ObjN(char* n)
  {
    static char objn[fieldLen];
    strcpy(objn, n);
    int len = strlen(objn);
    int ix;

    // replace .cpp with .o
    for (ix = len - 1 ; ix >= 0 ; --ix)
	if (objn[ix] == '.')
	    break;

    objn[ix] = 0;
    strcat(objn,".o");
    return stripDir(objn);
  }

//======================>>> makefileMaker::eraseAll <<============================
  void makefileMaker::eraseAll()
  {
    projOpts.erase();
    incDirs.erase();
    libDirs.erase();

    objFiles.erase();
    defsPool.erase();
    curDefs.erase();
    curOpts.erase();
    rules.erase();
    userTargets.erase();
    strcpy(makeName,"");
    strcpy(homeV,"");
    strcpy(exeName,"");
    strcpy(cc,"");
    strcpy(cFlags,"");
    strcpy(Libs, "");
    strcpy(ObjDir,"");
    strcpy(BinDir,"");
    strcpy(SrcDir,"");
  }

//======================>>> makefileMaker::setDefaults <<============================
  void makefileMaker::setDefaults()
  {
    //erase previous definitions
    eraseAll();

    // init each one
    strcpy(makeName,"Makefile.v");
    vOS vos;
    if (vos.vGetEnvVal("HOMEV",homeV,fieldLen) < 1)
	strcpy(homeV,"C:/v");
    strcpy(exeName,"foo.exe");
    strcpy(cc,"g++");
    strcpy(cFlags,"-O");
    strcpy(Libs, "-lV -lcomctl32 -mwindows");
    strcpy(ObjDir,".");
    strcpy(BinDir,".");
    strcpy(SrcDir,".");
    libDirs.insert(-1,"$(HOMEV)/lib");
    incDirs.insert(-1,"$(HOMEV)/include");
  }

//======================>>> makefileMaker::stripDir <<<============================
  char * makefileMaker::stripDir(char* n)
  {
    for (int ix = strlen(n) - 1 ; ix >= 0 ; --ix)
      {
	if (n[ix] == '/' || n[ix] == '\\')
	  {
	    return &n[ix+1];
	  }
      }
    return n;
 }

//======================>>> makefileMaker::addFile <<<============================
  BOOL makefileMaker::addFile(char *n)
  {
    //adds a new source file to the list

    if (!*n)
	return false;

    // Filter name here... (add a behavior switch eventually)

    char* addName = stripDir(n);	// strip leading dir part

    for (int ix = 0 ; objFiles.list[ix] != 0 ; ++ix)
	if (strcmp(objFiles.list[ix], addName) == 0)
	    return false; //don't add a obj file twice

    objFiles.insert(-1,addName);
    return true;
  }

//======================>>> makefileMaker::findRule <<<============================
  void makefileMaker::findRule(char* name)
  {
    //finds the rules to build object file pointed by name

    vOS vos;		// To execute g++

    char command[500]; // command line
    char hvbuff[256];

    cmdw = ((videApp*)theApp)->GetMsgWindow();

    strcpy(command,"g++");
    for (int ix = 0 ; incDirs.list[ix] != 0 ; ++ix)
      {
	char *incdir = incDirs.list[ix];
	char *hvp = strstr(incdir,"$(HOMEV)/");

	if (hvp != 0)
	  {
	    // Have $(HOMEV) in the path, so we must replace the
	    // $(HOMEV) with the real homeV path.
	    strcpy(hvbuff,homeV);
	    strcat(hvbuff, hvp+strlen("$(HOMEV)") );
	    incdir = hvbuff;
	  }

	strcat(command," -I");
	strcat(command,incdir);
	if (strlen(command) > 480)
	    break;
      }
    strcat(command," -MM "); strcat(command, name);

    static char makedep[] = "makedep.vtm";
    static char makeerr[] = "makeerr.vtm";
    char currlin[256];

    if (vos.vRunProcess(command, makedep, makeerr, 1, 1) != 0)
      {
CANTMAKE:
	cmdw->AddLine(command);
	vos.vDeleteFile(makedep);
        ifstream em(makeerr);
        if (em)
          {
            while(em.getline(currlin,256,'\n'))
              cmdw->AddLine(currlin);
            em.close();
          }
        vos.vDeleteFile(makeerr);
	return;
      }

    ifstream deps(makedep);
    if (!deps)
	goto CANTMAKE;


    while ( deps.getline(currlin, 256, '\n'))
      {
        rules.insert(-1,currlin);
      }

    deps.close();
    vos.vDeleteFile(makedep);
    vos.vDeleteFile(makeerr);
  }

//======================>>> makefileMaker::saveMakefile <<<============================
  void makefileMaker::saveMakefile()
  {
    //builds the file named makefile using infos stored in project
    cmdw = ((videApp*)theApp)->GetMsgWindow();

    ofstream mkf(makeName);		// where to write makefile
    //header
    char buff[120];
    char date[20];
    vGetLocalTime(buff);
    vGetLocalDate(date);

    cmdw->AddLine("Building Makefile -- Running g++ to generate dependencies...");

    mkf << "#=======================================================================\n";
    mkf << "#@V@:Note: File generated by VIDE makefile maker ";
    mkf << "(" << buff << " " << date << ").\n";
    mkf << "#=======================================================================\n\n";
    mkf << "# Standard and User define options:\n";

    // Options
    int nxtOpts = curOpts.size();

    for (int ix = 0 ; ix < nxtOpts ; ++ix)
      {
	mkf << curOpts.list[ix] << endl;
      }
    mkf << endl;

    //Compiler 
    mkf << "CC     \t=\t" << cc << "\n\n";

    // HomeV
    mkf << "HOMEV\t=\t" << homeV << endl;
    
    //Dirs
    mkf << "VPATH\t=\t$(HOMEV)/include" << endl;
    mkf << "oDir\t=\t" << ObjDir << endl; //Obj Dir
    mkf << "Bin\t=\t" << BinDir << endl; //Bin Dir
    mkf << "Src\t=\t" << SrcDir << endl; //Src Dir

    mkf << "libDirs\t=";
 
    int nxtld = libDirs.size();

    for (int ix = 0 ; ix < nxtld ; ++ix)
      {
	mkf << "\t-L" << libDirs.list[ix];
	if (ix + 1 < nxtld)
	    mkf << " \\";
	mkf << endl;
      }
    mkf << endl;

    mkf << "incDirs\t="; 

    int nxtid = incDirs.size();

    for (int ix = 0 ; ix < nxtid ; ++ix)
      {
	mkf << "\t-I" << incDirs.list[ix];
	if (ix + 1 < nxtid)
	    mkf << " \\";
	mkf << endl;
      }
    mkf << endl;

    //Libs
    mkf << "LIBS\t=\t" << Libs << endl;

    //C_FLAGS & paths to includes

    mkf << "C_FLAGS\t=\t" << cFlags;

    //defines
    int nxtcd = curDefs.size();

    for (int ix = 0 ; ix < nxtcd ; ++ix)
      {
	mkf << "\\\n\t" << curDefs.list[ix];
      }
    mkf << endl << endl;

    //sources
    int nxtof = objFiles.size();

    mkf << "SRCS\t=";

    for (int ix = 0 ; ix < nxtof ; ++ix)
      {
	mkf << "\\\n\t$(Src)/" << objFiles.list[ix];
      }
    mkf << endl << endl;

    //objects

    mkf << "EXOBJS\t=";

    for (int ix = 0 ; ix < nxtof ; ++ix)
      {
	char *objn = srcN2ObjN(objFiles.list[ix]);
	mkf << "\\\n\t$(oDir)/" << objn;
      }
    mkf << endl << endl;

    mkf << "#@# Targets follow ---------------------------------" 
	<< endl << endl;
    //target file
    //all:
    mkf << "all:	$(Bin)/" << exeName << endl << endl;
    //objs:
    mkf << "objs:	$(EXOBJS)\n\n";

    //cleanobjs:
    mkf << "cleanobjs:\n\trm -f $(EXOBJS)" << endl << endl;

    // cleanbin
    mkf << "cleanbin:\n\trm -f $(Bin)/" << exeName << endl << endl;

    // clean
    mkf << "clean:\t cleanobjs" << endl << endl;

    // cleanall
    mkf << "cleanall:\t cleanobjs cleanbin" << endl << endl;

    mkf << "#@# User Targets follow ---------------------------------" 
	<< endl << endl;

    for (int ix = 0 ; userTargets.list[ix] != 0 ; ++ix)
	mkf << userTargets.list[ix] << endl;
    mkf << endl;

    mkf << "#@# Dependency rules follow -----------------------------"
	<< endl << endl;
    //dependency rules
    mkf << "$(Bin)/" << exeName
        << ": $(EXOBJS)" << endl << "\t$(CC) -o $(Bin)/"
        << exeName << " $(EXOBJS) $(incDirs) $(libDirs) $(LIBS)" << endl ;

    for (int ix = 0 ; objFiles.list[ix] != 0 ; ix++)
	findRule(objFiles.list[ix]);

    int lim = rules.size();
    for (int ix = 0 ; ix < lim ; ++ix)
      {
        mkf << endl << "$(oDir)/" << rules.list[ix] << endl;
        // check for continuation lines
   	while (*(rules.list[ix]+strlen(rules.list[ix])-1) == '\\')
          {
            ++ix;
            if (rules.list[ix])
                mkf << rules.list[ix] << endl;
          }
        mkf << "\t$(CC) $(C_FLAGS) $(incDirs) -c -o $@ $<" << endl;
      }

    mkf.close();

    strcpy(buff,makeName); strcat(buff," - Makefile saved.");
    cmdw->AddLine(buff);

  }

//======================>>> makefileMaker::saveProject <<<============================
  void makefileMaker::saveProject (char *nameIn)
  {

    char n[300];

    if (strlen(nameIn) > 296)
	strcpy(n,"BADFILE.tmp");
    else
	strcpy(n,nameIn);

    int len = strlen(n);
    int ix;

    for (ix = len - 1 ; ix >= 0 ; --ix)
      {
	if (n[ix] == '.' || n[ix] == '/' || n[ix] == '\\')
	    break;
      }
    if (n[ix] != '.')	// user didn't supply extension
	strcat(n,".vpj");

    ofstream prjf(n);

    //header
    char buff[40];
    char date[20];
    vGetLocalTime(buff);
    vGetLocalDate(date);

    prjf << "//=======================================================================\n";
    prjf << "//@V@:Note: Project File generated by VIDE: ";
    prjf << "(" << buff << " " << date << ").\n";
    prjf << "//CAUTION! Hand edit only if you know what you are doing!\n";
    prjf << "//=======================================================================\n\n";

    // Project Options
    prjf << SecProjOpts << endl;

    for (int nxtOpts = 0 ; projOpts.list[nxtOpts] != 0 ; ++nxtOpts)
	prjf << projOpts.list[nxtOpts] << endl;

    prjf << SecProjOptsEnd << endl << endl;

    // Makefile name
    prjf << SecMakefile << endl;
    prjf << makeName << endl << endl;

    // Options
    prjf << SecOptions << endl;

    for (int nxtOpts = 0 ; curOpts.list[nxtOpts] != 0 ; ++nxtOpts)
	prjf << curOpts.list[nxtOpts] << endl;

    prjf << SecOptionsEnd << endl << endl;

    // HOMEV
    prjf << SecHomeV << endl;
    prjf << homeV << endl << endl;

    // 1) name of exe target file
    prjf << SecTarget << endl;
    prjf << exeName << endl << endl;
 
    // 2) object files
    prjf << SecObjFiles << endl;
    for (int ix = 0 ; objFiles.list[ix] != 0 ; ++ix)
      {
	prjf <<  objFiles.list[ix] << endl;
      }
    prjf << SecObjFilesEnd << endl << endl;

    // 3) compiler name
    prjf << SecCompiler << endl;
    prjf << cc << endl << endl;

    // 4) include dirs
    prjf << SecIncDirs << endl;

    for (int nxtid = 0 ; incDirs.list[nxtid] != 0 ; ++nxtid)
	prjf << incDirs.list[nxtid] << endl;

    prjf << SecIncDirsEnd << endl << endl;

    // 5) lib dirs
    prjf << SecLibDirs << endl;
    for (int ld = 0 ; libDirs.list[ld] != 0 ; ++ld)
	prjf << libDirs.list[ld] << endl;
    prjf << SecLibDirsEnd << endl << endl;

    // 6) defs
    prjf << SecDefs << endl;
    for (int ix = 0 ; defsPool.list[ix] != 0 ; ++ix)
	prjf << defsPool.list[ix] << endl;
    prjf << SecDefsPoolEnd << endl;

    for (int ix = 0 ; curDefs.list[ix] != 0 ; ++ix)
	prjf << curDefs.list[ix] << endl;
    prjf << SecDefsEnd << endl << endl;

    // 7) cflag
    prjf << SecCFlags << endl;
    prjf << cFlags << endl << endl;

    // 8) libflag 
    prjf << SecLibFlags << endl;
    prjf << Libs << endl << endl;

    // src directory
    prjf << SecSrcDir << endl;
    prjf << SrcDir << endl << endl;

    // 9) object directory
    prjf << SecObjDir << endl;
    prjf << ObjDir << endl << endl;

    //10) bin directory
    prjf << SecBinDir << endl;
    prjf << BinDir << endl << endl;
    // User Targets
    prjf << endl << "//% User targets section. Following lines will be" << endl;
    prjf << "//% inserted into Makefile right before the generated cleanall target." << endl;
    prjf << "//% The Project File editor does not edit these lines - edit the .vpj" << endl;
    prjf << "//% directly. You should know what you are doing." << endl;

    prjf << SecUserTargets << endl;
    for (int ld = 0 ; userTargets.list[ld] != 0 ; ++ld)
	prjf << userTargets.list[ld] << endl;
    prjf << SecUserTargetsEnd << endl << endl;

    prjf.close();


  }

//===================>>> makefileMaker::getSectionEntry <<<===========================
  BOOL makefileMaker::getSectionEntry(ifstream &prjf, const char* head, char* value)
  {
    // read section head into value
    char skipStr[256];
    if (!prjf)
	return false;
    for (prjf.getline(skipStr, 256, '\n') ;	// skip line
	 prjf && strcmp(skipStr,head) != 0 ;
	 prjf.getline(skipStr, 256, '\n'))
	;

    for (prjf.getline(skipStr, 256, '\n') ;	// skip blank lines
	 prjf && (*skipStr == 0 || *skipStr == ' ') ;
	 prjf.getline(skipStr, 256, '\n'))
	;

    strcpy(value,skipStr);			// copy to output

    if (!prjf)
	return false;
    else
	return true;

  }

//===================>>> makefileMaker::getSectionList <<<============================
  BOOL makefileMaker::getSectionList(ifstream &prjf, const char* head, const char* term, vSList& sl)
  {
    char skipStr[256];
    if (!prjf)
	return false;
    if (head && *head)	// allow no head - assumes at right location
      {
	for (prjf.getline(skipStr, 256, '\n') ;	// skip line
	 	prjf && strcmp(skipStr,head) != 0 ;
	 	prjf.getline(skipStr, 256, '\n'))
	    ;
      }

    // get list now
    for (prjf.getline(skipStr, 256, '\n') ;	// skip blank lines
	 prjf && strcmp(skipStr, term) != 0 ;
	 prjf.getline(skipStr, 256, '\n'))
      {
	if (*skipStr != ' ')
	    sl.insert(-1,skipStr);		// add to end of list
      }

    if (!prjf)
	return false;
    else
	return true;

  }

//======================>>> makefileMaker::loadProject <<<============================
  void makefileMaker::loadProject (char *n)
  {
    //loads infos contained in project file n into makefileMaker member vars

    ifstream prjf(n);
    if (!prjf)
	return;

    eraseAll();
  

    // Project Options
    if (!getSectionList(prjf, SecProjOpts, SecProjOptsEnd, projOpts))
	return;

    // Makefile
    if (!getSectionEntry(prjf, SecMakefile, makeName))
	return;

    //0) options
    if (!getSectionList(prjf, SecOptions, SecOptionsEnd, curOpts))
	return;

    // HOMEV
    if (!getSectionEntry(prjf, SecHomeV, homeV))
	return;

    //1) exe name
    if (!getSectionEntry(prjf, SecTarget, exeName))
	return;

    //2) objs
    if (!getSectionList(prjf, SecObjFiles, SecObjFilesEnd, objFiles))
	return;

    //3) compiler name
    if (!getSectionEntry(prjf, SecCompiler, cc))
	return;

    //4) include dirs
    if (!getSectionList(prjf, SecIncDirs, SecIncDirsEnd, incDirs))
	return;

    //5) lib dirs
    if (!getSectionList(prjf, SecLibDirs, SecLibDirsEnd, libDirs))
	return;

    //6) defs
    if (!getSectionList(prjf, SecDefs, SecDefsPoolEnd, defsPool))
	return;
    if (!getSectionList(prjf, "", SecDefsEnd, curDefs))
	return;

    //7) cflag
    if (!getSectionEntry(prjf, SecCFlags, cFlags))
	return;

    //8) libflag 
    if (!getSectionEntry(prjf, SecLibFlags, Libs))
	return;

    //  SRC directory
    if (!getSectionEntry(prjf, SecSrcDir, SrcDir))
	return;

    // 9) object directory
    if (!getSectionEntry(prjf, SecObjDir, ObjDir))
	return;

    //10) bin directory
    if (!getSectionEntry(prjf, SecBinDir, BinDir))
	return;

    // User targets.
    if (!getSectionList(prjf, SecUserTargets, SecUserTargetsEnd, userTargets))
	return;

  }
