/**
 *
 * Simple make program implementation.  
 * This software can be used to build software.
 *
 * Author: Matthew William Coan
 * Date: Fri Jan 27 18:22:34 EST 2012
 *
 */

#include <iostream>
#include <fstream>
#include <map>
#include <vector>
#include <string>

#include <cstdlib>
#include <cstring>

#include <sys/types.h>
#include <sys/stat.h>
#include "st.h"

#include <windows.h>

#define MAKE_PROGRAM "make"

using namespace std;

typedef vector< string > string_vector_type;

bool compare_file_time(const string & file1, 
                       const string & file2)
{
   bool ret = false;

   struct stat stat1, stat2;

   if(stat(file1.c_str(), &stat1) == 0) {

      if(stat(file2.c_str(), &stat2) == 0) {

         if(stat1.st_mtime < stat2.st_mtime) {
            ret = true;
         }
      }
      else 
         ret = true;
   }
   else 
      ret = true;

   return ret;
}

inline
string replace(const string & data,
               const string & start,
               const string & end) 
{
   bool found;
   string temp;

   for(size_t i = 0; i < data.size(); i++) {
      found = true;

      for(size_t j = 0; j < start.size(); j++) {
         if(data[i+j] != start[j]) {
            found = false;
            break;
         }
      }
    
      if(found) {
         temp += end;
         i += start.size();
         i--;
      }
      else {
         temp += data[i];
      }
   }

   return temp;
}

#define System _System

#ifdef NO_MAIN
extern int _System(const string & cmd);
#else 
extern int _System(const string & cmd)
{
	int rc = 0;
	cout << cmd << endl << flush;
	rc = system(cmd.c_str());
	return rc;
}
#endif

class Makefile;

class Rule {
   string_vector_type code;
   string_vector_type depend_vec;
   string name;

public:
   Rule();
   Rule(const Rule & r);
   Rule(const string & name);
   ~Rule();

   Rule & operator =(const Rule & r);

   void set_code(const string_vector_type & rule_code);
   void set_name(const string & rname);
   void set_depend(const string_vector_type & dep);
   const string & get_name() const;
   const string_vector_type & get_code() const;
   int run(Makefile & maekfile);
   string_vector_type & depend() { return depend_vec; }
   void replace(const string & src, const string & dest);
};

class Makefile {
public:
   typedef map< string, Rule > rule_map_type;
   typedef map< string, string > string_map_type;

private:
   string file_name;
   string first_rule;
   rule_map_type rule_map;
   string_map_type var_map;

public:
   Makefile(const string & fname);
   ~Makefile();

   bool has_rule(const string & r);
   int load();
   int run(const string & start_rule);
   void error(const string & msg);
   bool update(const string & arg1, const string & arg2);
   const string & get_first_rule() { return first_rule; }
};

/// IMPLEMENTATION

void Rule::replace(const string & src, const string & dest)
{
   for(size_t i = 0; i < code.size(); i++) {
      code[i] = ::replace(code[i], src, dest);
   }
}

bool Makefile::has_rule(const string & r)
{
   return rule_map.find(r) != rule_map.end() ? true : false;
}

void Rule::set_depend(const string_vector_type & vec)
{
   depend_vec = vec; 
}

const string_vector_type & Rule::get_code() const
{
   return code;
}

Rule & Rule::operator =(const Rule & rule)
{
   depend_vec = rule.depend_vec;
   name = rule.get_name();
   code = rule.get_code();
   return *this;
}

Rule::Rule()
{
}

const string & Rule::get_name() const
{
   return name;
}

Rule::Rule(const Rule & r)
{
   code = r.code;
   name = r.name;
}

string_vector_type split(const string & dep)
{
   string_vector_type vec;

   char * buffer = new char[dep.size()+1];

   memset(buffer, 0, dep.size()+1);

   strcpy(buffer, dep.c_str());

   char * ptr = strtok(buffer, " \t");

   while(ptr != NULL) {
      vec.push_back(ptr);

      ptr = strtok(NULL, " \t");
   }

   delete [] buffer;

   return vec;
}

int Makefile::load()
{
   int rc = 0;

   ifstream fin(file_name.c_str(), ios::in);

   if(!fin.fail()) {
      enum { MAX_BUFFER = 1024 };
      char buffer[MAX_BUFFER];
      char * ptr, * depend;
      Rule rule;
      string_vector_type depend_vec;

      memset(buffer, 0, MAX_BUFFER);

      string_vector_type code;

      while(fin.getline(buffer, MAX_BUFFER-1)) {
         if((ptr = strchr(buffer, '=')) != NULL) {
            char * name = buffer;

            *ptr = '\0';

            char * value = ptr+1;

            var_map[name] = value;
         }
         else if((ptr = strchr(buffer, ':')) != NULL) {
            *ptr = '\0';


            ptr++;

            depend = ptr;

            depend_vec = split(depend);

            rule.set_depend(depend_vec);

            if(first_rule.size() == 0)
               first_rule = buffer;

            rule.set_name(buffer);

            memset(buffer, 0, MAX_BUFFER);

            while(fin.getline(buffer, MAX_BUFFER-1)) {
               if(strlen(buffer) == 0 || buffer[0] != '\t') 
                  break;

               code.push_back(buffer+1);

               memset(buffer, 0, MAX_BUFFER);
            }

            rule.set_code(code);

            code.clear();

            rule_map[rule.get_name()] = rule;
         }
      }

      fin.close();

      for(rule_map_type::iterator p = rule_map.begin(); p != rule_map.end(); p++) {
         Rule & r = p->second;
         for(string_map_type::iterator p0 = var_map.begin(); p0 != var_map.end(); p0++) {
            r.replace("${" + p0->first + "}", p0->second);
            r.replace("$(" + p0->first + ")", p0->second);
         }
      }
   }
   else {
      error(("unable to open file \"" + file_name + "\" for reading...").c_str());
      rc = 1;
   }


   return rc;
}

bool Makefile::update(const string & arg1,
                      const string & arg2)
{
   if(compare_file_time(arg1, arg2)) {
      return true;
   }

   bool ret = false;

   if(rule_map.find(arg2) != rule_map.end()) {
      Rule r = rule_map[arg2];
      for(size_t i = 0; i < r.depend().size(); i++) {
         if(update(arg1, r.depend()[i])) {
            ret = true;
            break;
         }
      }
   }

   return ret;
}

int Rule::run(Makefile & makefile)
{
   bool ret = false;
   int rc = 0;

   for(size_t i = 0; i < depend_vec.size(); i++) {
      if(depend_vec[i].size()) {
         if(makefile.has_rule(depend_vec[i])) {
            rc = makefile.run(depend_vec[i]);
            if(rc != 0)
               break;
            if(makefile.update(name, depend_vec[i])) {
               ret = true;
            }
         }
         else if(compare_file_time(name, depend_vec[i])) {
            ret = true;
         }
      }
   }

   if(depend_vec.size() == 0) {
      ret = true;
   }

   if(ret) {
      for(size_t i = 0; i < code.size(); i++) {
         //cout << code[i] << endl;
         if(System(code[i].c_str()) != 0) {
            rc = 1;
            break;
         }
      }
   }
   
   return rc;
}

void Rule::set_name(const string & the_name)
{
   name = the_name;
}

void Rule::set_code(const string_vector_type & rule_code)
{
   code = rule_code;
}

Rule::~Rule()
{
}

Makefile::Makefile(const string & fname)
:file_name(fname)
{
}

Makefile::~Makefile()
{
}

int Makefile::run(const string & start_rule) 
{
   int rc = 0;
   rule_map_type::iterator p;

   string temp = start_rule;

   if(temp.size() == 0)
       temp = first_rule;


   if((p = rule_map.find(temp)) == rule_map.end()) {
      error(("unknown make file rule: \"" + temp + "\"").c_str());
      rc = 1;
   }
   else {
      rc = p->second.run(*this);
   }
   return rc;
}

void Makefile::error(const string & msg)
{
   cerr << msg << endl << flush;
   //exit(1);
}

#ifndef NO_MAIN
int main(int argc, char ** argv)
#else 
extern "C" {
extern int make_main(int argc, char ** argv);
}
int make_main(int argc, char ** argv)
#endif
{
   int rc = 0;

   string start_rule;
   string file = "Makefile";

   for(int i = 1; i < argc; i++) {
      if(strcmp(argv[i], "-f") == 0) {
         i++;
         if(i < argc) {
            file = argv[i];
         }
      }
      else {
         start_rule = argv[i];
      }
   }

   if(argc >= 2) {
      start_rule = argv[1];
      Makefile make(file); 
      make.load();
      rc = make.run(start_rule);
   }
   else if(argc == 1) {
      Makefile make(file); 
      make.load();
      rc = make.run(make.get_first_rule());
   }
   else {
      for(int i = 1; i < argc; i++) {
         Makefile make(file); 
         make.load();
         rc = make.run(argv[i]);
      }
   }

   return rc;
}



