/*

Windows C macro pre-parser.

Author: Matthew William Coan
Date: Wed Nov 26 19:04:12 EST 2008

*/

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

#include <cstdlib>
#include <cstring>
#include <cctype>

#include "str_tok.h"

using namespace std;

const char * include_dir[] = {
   ".\\include\\",
   ".\\",
   NULL
};

class cpp {
public:

typedef map< string, string > string_map_type;

string_map_type define_map;
string_map_type param_map;

string
file_name(const string & macro)
{
//cout << "file_name..." << endl << flush;
   string the_file_name;

   const char * p = macro.c_str();

   while(*p) {
      if(*p == '<' || *p == '\"') {
         char ch = (*p);
         p++;

         while(*p) {
            if((*p == '>' || *p == '\"') && ch == *p) {
               break;
            }

            the_file_name += *p;

            p++;
         }

         break;
      }

      p++;
   }

   return the_file_name;
}

string
get_file_name(const string & inc)
{
//cout << "get_file_name" << endl << flush;
   string ret;

   size_t i = 0;

   while(i < inc.size()) {
      if(isspace(inc[i])) {
         i++;
         continue;
      }

      if(inc[i] == '<' || inc[i] == '\"') {
         i++;

         while(inc[i] != '>' && inc[i] != '\"') {
            ret += inc[i];

            i++;
         }

         break;
      }

      i++;
   }

   return ret;
}

string_map_type include_map;

void
include_file(const string & inc_file_name, 
             ofstream & output)
{
//cout << "include_file" << endl << flush;
   bool found = false;
   string fn = get_file_name(inc_file_name);
   char ch;
   int lineno = 1;

   for(size_t i = 0; include_dir[i] != NULL; i++) {
      string the_file_name = string(include_dir[i]) + fn;

      if(include_map.find(the_file_name) == include_map.end()) {
         include_map[the_file_name] = "TRUE";
      }
      else {
         return;
      }

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

      if(!fin) {
         cerr << "unable to open file..." << endl << flush;
         cerr << "file: " << the_file_name << endl << flush;
         continue;
      }

      found = true;

      string str;
      bool comment = false;
      bool did_something = false;
      string macro;

      while(true) {
         ch = fin.get();

         if(!fin) {
            break;
         }

         if(ch == '#' && !comment) {
            macro = "#";
   
            while((ch = fin.get()) != '\n') {
               macro += ch;
            }

            process_macro(macro, output, lineno, fn);

            lineno++;
   
            did_something = true;
         }
         else if(ch == '/' && fin.peek() == '*') {
            ch = fin.get();

            comment = true;
   
            did_something = true;
         }
         else if(ch == '*' && fin.peek() == '/') {
            ch = fin.get();

            comment = false;
   
            did_something = true;
         }
         else if(comment) {
            did_something = true;
         }
         else if(ch == '\n') {
            lineno++;
            string temp = expand(str);
            str = "";
            output << temp << endl << flush;
         }
         else {
            if(!skip) {
               str += ch;
            }
         }
      }

      //output << expand(str) << flush; 

      fin.close();
   }

   if(!found) {
      cerr << "include file " 
           << fn 
           << " not found..." << endl;

      exit(1);
   }
}

void
macro_define(const string & macro)
{
//cout << "macro_define" << endl << flush;
   const char * p = macro.c_str();

   p += 8;

   string def;
   string val;
   string param;
   bool get_val = false;
   bool in = false;

   while(*p) {
      if((*p) == '\n') {
         break;
      }
      else if((*p) == ' ') {
         if(get_val)
            val += ' ';
         get_val = true;
      }
      else if((*p) == '(' && !get_val) {
         p++;
         while(*p && *p != ' ' && *p != ';' && *p != '\r' && *p != '\n' && *p != ')') {
            param += *p;
            p++;
         }
         if(*p == ')')
            p++;
         if(*p == ' ')
            p++;
         while(*p && *p != ';' && *p != '\r' && *p != '\n') {
            val += *p; 
            p++;
         }
         break;
      }
      else {
         if(get_val) {
            val += (*p);
         }
         else {
            def += (*p);
         }
      }

      p++;
   }

   define_map[def] = val;
   param_map[def] = param;
}

bool
ifndef(const string & macro)
{
//cout << "ifndef" << endl << flush;
   const char * p = macro.c_str();

   p += 8;

   string def;

   while(*p) {
      def += (*p);

      p++;
   }

   bool ret = false;

   if(define_map.find(def) == define_map.end()) {
      ret = true;
   }

   return ret;
}

void 
undef(const string & macro)
{
   //cout << "undef" << endl << flush;

   const char * p = macro.c_str();

   if(macro.size() < 7)
      return;

   p += 7;

   string def;

   while(*p) {
      def += (*p);

      p++;
   }

   bool ret = false;

   if(define_map.find(def) != define_map.end()) {
      define_map.erase(define_map.find(def));
   }
}

bool
ifdef(const string & macro) 
{
//cout << "ifdef" << endl << flush;
   const char * p = macro.c_str();

   if(macro.size() < 7)
      return false; 

   p += 7;

   string def;

   while(*p) {
      def += (*p);

      p++;
   }

   bool ret = false;

   if(define_map.find(def) != define_map.end()) {
      ret = true;
   }

   return ret;
}

bool skip;

bool
endif(const string & macro)
{
//cout << "endif" << endl << flush;
   bool ret = true;

   return ret;
}

typedef stack< bool > skip_stack_type;

skip_stack_type skip_stack;

void
process_macro(const string & macro,
              ofstream & output,
              int lineno,
              string file)
{
//cout << "process_macro" << endl << flush;
   //skip = false;

   if(strstr(macro.c_str(), "#define") == macro.c_str()) {
      macro_define(macro);
   }
   else if(strstr(macro.c_str(), "#undef") == macro.c_str()) {
      undef(macro);
   }
   else if(strstr(macro.c_str(), "#include") == macro.c_str()) {
      string temp = file;
      lineno++;
      int ln = lineno;
      file = macro.substr(10, macro.size()-10-1);
      output << "# 1 \"" << file << "\"" << endl;
      include_file(macro, output);
      output << "# " << ln+1 << " \"" << temp << "\"" << endl;
      lineno = ln;
      file = temp;
   }
   else if(strstr(macro.c_str(), "#ifdef") == macro.c_str()) {
      skip_stack.push(skip);
      if(ifdef(macro)) {
         skip = false;
      }
      else {
         skip = true;
      }
   }
   else if(strstr(macro.c_str(), "#ifndef") == macro.c_str()) {
      skip_stack.push(skip);

      if(ifndef(macro)) {
         skip = false;
      }
      else {
         skip = true;
      }
   }
   else if(strstr(macro.c_str(), "#endif") == macro.c_str()) {
      endif(macro);

      skip = skip_stack.top();

      skip_stack.pop();
   }
   else {
      output << macro << endl << flush;
   }
}

void
swap(string & str1, 
     string & str2)
{
//cout << "swap" << endl << flush;
   string temp = str1;

   str1 = str2;

   str2 = temp;
}

bool
isalnum2(const char ch)
{
   bool ret = false;

   if((ch >= 'a' && ch <= 'z') 
      || (ch >= 'A' && ch <= 'Z')
      || (ch >= '0' && ch <= '9')
      || ch == '_') {
      ret = true;
   }

   return ret;
}

string
expand2(string & str, string & first, string & second)
{
//cout << "expand2(\"" << str << "\",\"" << first << "\",\"" << second << "\")" << endl << flush;
//cin.get();cin.get();
   const char * ptr = str.c_str();
   const char * p = ptr;
   string ret;

   while((ptr = strstr(ptr, first.c_str())) != NULL) {
      if(isalnum2(*(ptr-1)) || isalnum2(*(ptr+first.size()))) {
         ptr++;
         continue;
      }

      for(; p != ptr; p++) {
         ret += (*p);
      }

      ret += second;

      p += first.size();

      ptr = p;
   }

   while(*p) {
      ret += *p;

      p++;
   }

   return ret;
}

string
expand(string & str)
{
//cout << "expand" << endl << flush;

   for(string_map_type::iterator p = define_map.begin(); p != define_map.end(); p++) {
//cout << p->first << " == " << p->second << endl << flush;
      for(size_t pos = 0; (pos = str.find(p->first, pos)) != string::npos; pos++) {
         if(isalnum2(str[pos - 1]) || isalnum2(str[pos + p->first.size()]))
            continue;

         string args;
         bool first = true;
         int n = 0;
         int count = 0;
         while(true) {
            if(str[pos+n] == '(') {
               if(count == 0) {
                  args = "";
               }
               else 
                  args += "(";
               count++;
            }
            else if(str[pos+n] == ')') {
               count--;
               if(count == 0) {
                  break;
               }
               else
                  args += ")";
            }
            else 
               args += str[pos+n];
            n++;
            if(pos+n >= str.size()) {
               break;
            }
         }

         //n -= p->first.size();

//cout << "first: \"" << p->first << "\"" << endl << flush;
//cout << "second: \"" << p->second << "\"" << endl << flush;
//cout << "args: \"" << args << "\"" << endl << flush;

         string the_str = p->second;
//cout << "the_str: \"" << the_str << "\"" << endl << flush;
//cin.get(); cin.get();
         string arg = param_map[p->first];
         if(arg.size()) {
            string_tokenizer tok1(args, ",");
            string_tokenizer tok2(arg, ",");
            while(tok1.has_next() && tok2.has_next()) {
               string arg1 = tok1.get_next();
               string arg2 = tok2.get_next();
               the_str = expand2(the_str, arg2, arg1);
            }
            str.replace(pos, n+1, the_str);
         }
         else {
//cout << "PREPLACE(" << p->first << "," << the_str << ")" << endl << flush;
//cin.get();cin.get();
            str.replace(pos, p->first.size(), the_str);
         }
      }
   }

   return str;
}

int
main_loop(int argc,
          char ** argv,
          char ** envp)
{
cout << "main_loop" << endl << flush;
   string macro;
   char ch;

   //string inc = string(argv[0]) + "/include";

   //include_dir[0] = inc.c_str();

   if(argc < 3) {
      cerr << "error: usage: " << argv[0] 
           << " <input> <output>" << endl;

      exit(1);
   }

   const char * infile = argv[1];
   const char * outfile = argv[2];

   string dir;

/*
   char * p = strrchr(infile, '/');
   char * p0 = strrchr(outfile, '/');

   if(p0) {
      outfile = p0;
      outfile++;
   }

   if(p) {
      for(const char * p0 = infile; p0 != p; p0++) {
         dir += *p0; 
      }
      infile = p;
      infile++;
cout << "dir: \"" << dir << "\"" << endl << flush;
      chdir(dir.c_str());
   }
*/

   if(infile == NULL) {
      cerr << "no input file found..." << endl;
      exit(1);
   }

   if(outfile == NULL) {
      cerr << "no output file found..." << endl;
      exit(1);
   }

cout << "infile: \"" << infile << "\"" << endl;
cout << "outfile: \"" << outfile << "\"" << endl;
   string file_name0 = string(infile);

   string file_name1 = string(outfile);

   string temp_file = string("temp_file_name.c");
   int lineno = 1;
   string file = infile;

   while(true) {
   
      ifstream input(file_name0.c_str());

      if(!input) {
         cerr << "error: unable to open input file..." << endl;
         cerr << "file: " << file_name0 << endl;

         exit(1);
      }

      ofstream output(file_name1.c_str());

      if(!output) {
         cerr << "error: unable to open output file..." << endl;
         cerr << "file: " << file_name1 << endl;

         exit(1);
      }

      skip = false;

      bool comment = false;

      bool did_something = false;

      string code;

      while(input) {
         ch = input.get();

         if(!input) {
            break;
         }

         if(ch == '#' && !comment && input.peek() != ' ') {
            macro = "#";

            while((ch = input.get()) != '\n') {
               macro += ch;
            }

            process_macro(macro, output, lineno, file);

            cout << "MACRO: " << macro << endl;

            did_something = true;
         } 
         else if(ch == '/' && input.peek() == '*') {
            ch = input.get();

            comment = true;

            did_something = true;
         }
         else if(ch == '*' && input.peek() == '/') {
            ch = input.get();

            comment = false;

            did_something = true;
         }
         else if(comment) {
            did_something = true;
         }
         else {
            if(!skip) {
               code += ch;
            }
         }
      }

      expand(code); 

      output << code << flush;

      output.flush();

      output.close();
      input.close();

      if(!did_something) {
         break;
      }

      if(file_name0 == temp_file) {
         swap(file_name0, file_name1);
      }
      else if(file_name1 == temp_file) {
         swap(file_name0, file_name1);
      }
      else {
         file_name0 = file_name1;
         file_name1 = temp_file;
      }
   }

   if(file_name1 == temp_file) {
      ofstream out(argv[2], ios::trunc | ios::ate | ios::out);
      ifstream in(temp_file.c_str(), ios::in);
      if(in && out) {
         char ch;
         ch = in.get();
         while(in) {
            out << ch;
            ch = in.get();
         }
         out << flush;
         in.close();
         out.close();
      }
   }

   return 0;
}

};


int
main(int argc,
     char ** argv,
     char ** envp)
{
   cpp c_pre_processor;

   register int rc = c_pre_processor.main_loop(argc, argv, envp);
   
   return rc;
}
