/**
 *
 * Simple make program implementation.  
 * This software can be sed 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>

#define MAKE_PROGRAM ".\\make.exe"

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;

cout << "COMPARE(\"" << file1 << "\",\"" << file2 << "\")" << endl << flush;

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

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

cout << "COMPARE(\"" << stat1.st_mtime << "\",\"" << stat2.st_mtime << "\")" << endl << flush;

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

   return ret;
}

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;
}

int System(const string & cmd) 
{
   cout << cmd << endl << flush;

   return system(cmd.c_str());
}

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;
   void 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();
   void run(const string & start_rule);
   void error(const string & msg);
   bool update(const string & arg1, const string & arg2);
};

/// 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) {
      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';

cout << "RULE: \"" << buffer << "\"" << endl << flush;

            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);
         }
      }
   }
   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;
}

void Rule::run(Makefile & makefile)
{
   bool ret = false;

   for(size_t i = 0; i < depend_vec.size(); i++) {
      if(depend_vec[i].size()) {
         if(makefile.has_rule(depend_vec[i])) {
            makefile.run(depend_vec[i]);
            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++) {
         if(System(code[i].c_str()) != 0) {

         }
      }
   }
}

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()
{
}

void Makefile::run(const string & start_rule) 
{
   rule_map_type::iterator p;

   string temp = start_rule;

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

cout << "START RULE: \"" << temp << "\"" << endl << flush;

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

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

// main entry point
int
main(int argc, char ** argv, char ** evnp)
{
   int rc = 0;

   string start_rule;

   if(argc == 2)
      start_rule = argv[1];

   switch(argc) {
      case 1: {
            Makefile make("Makefile"); 
            make.load();
            make.run(start_rule);
         }
         break;

      case 2: {
            Makefile make("Makefile");
            make.load();
            make.run(start_rule);
         }
         break;

      default:
         cerr << "usage: " << argv[0] << " [ <start_rule> ]" << endl << flush;
   }

   return rc;
}



