/* boost script execution program  -------------------------------------------//
 *
 * Copyright Beman Dawes, Jens Maurer 2000
 * Permission to use, copy, modify, sell, and distribute this software
 * is hereby granted without fee provided that the above copyright notice
 * appears in all copies and that both that copyright notice and this
 * permission notice appear in supporting documentation,
 *
 * Beman Dawes and Jens Maurer make no representations about the suitability of
 * this software for any purpose. It is provided "as is" without express or
 * implied warranty.
 */

//  See http://www.boost.org for most recent version including documentation.

//  Revision History
//  
//  02 Feb 01 Major rewrite including replacing files in non-all source lines,
//  30 Dec 00 Initial version (Beman Dawes), with some functions derived from
//            Jens Maurer's regression.cpp.

#include <iostream>
#include <string>
#include <cstdlib>
#include <fstream>
#include <vector>
#include <algorithm>

#include <boost/config.hpp>

# ifdef BOOST_NO_STDC_NAMESPACE
    namespace std { using ::system; using ::strncmp; }
# endif

namespace
{
  struct parm_t
  {
    std::string name, value;
    parm_t(const std::string & n, const std::string & v) : name(n), value(v) {} 
  };

  bool operator==(const parm_t & p1, const parm_t & p2)
    { return p1.name == p2.name; }

  // Choice of container type dictated by desired semantics that parms may be
  // replaced and removed, and are applied in FIFO order.  For the small
  // sizes processed by booster, efficiency is not an issue.
  typedef std::vector<parm_t> parm_list;
  parm_list parms;

  bool execute_command = false;

  std::string files;  // files file converted to a single string
  std::string undecorated_files;  // undecorated files file converted to string
  std::vector<std::string> files_vec; // files file converted to vector of strings
  std::vector<std::string> undecorated_files_vec; // undecorated files file converted to vector of strings

  const char * build_filename = "build.cfg";
  const char * files_filename = "files.cfg";

// replace all instances of name with value
  void replace(std::string & s,
	       const std::string & name, const std::string & value)
  {
    for(;;)
    {
      std::string::size_type p = s.find(name);
      if(p == std::string::npos) break;
      s.replace(p, name.length(), value);
    }
  }

  // replace environment variables with values
  void replace_environment(std::string & s)
  {
    std::string::size_type end = 0;
    for(;;) {
      std::string::size_type pos = s.find('$', end);
      if(pos == std::string::npos)
        break;
      end = s.find_first_not_of("ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_", pos+1);
      const char * env = getenv(s.substr(pos+1, end-pos-1).c_str());
      if(env)
        replace(s, s.substr(pos, end-pos), env);
      else
        break;
    }
  }

  // convert a string to a parm_t and store it in parms,
  // first removing any existing parm with same name.
  void store_parm( const std::string & source )
  {
    if ( source.size() == 0 ) return;
    std::string s( source );
    std::string::size_type separator = s.find("=");
    if (separator == std::string::npos) { s += "="; separator = s.length()-1; }
    parm_t parm(s.substr(0, separator), s.substr(separator+1));
    if (std::remove( parms.begin(), parms.end(), parm ) != parms.end())
      { parms.erase(parms.end()-1); }
    parms.push_back(parm);
  }

  // replace %parm% with value
  void replace_parms( std::string & s )
  {
    for (parm_list::iterator it = parms.begin();
         it != parms.end(); ++it)
    { replace( s, "%" + it->name + "%", it->value); }
  }

  // execute the command, after parm and environment replacement
  void execute(std::string & command)
  {
    replace( command, "%file_list%", files );
    replace( command, "%name_list%", undecorated_files );
    replace_parms( command );
    replace_environment( command );
    if (!execute_command && command.size() && command[command.size()-1]=='\\')
    {
      command.erase(command.size()-1);
      std::cout << command;
      if (command.size() && command[command.size()-1]=='\\') std::cout << "\n"; 
    }
    else std::cout << command << std::endl; // fix: endl ensures cout ordering
    if (execute_command)  std::system(command.c_str());
  }

  // get a string line, ignoring empty lines and comment lines
  void getstringline( std::ifstream & is, std::string & s )
  {
    do { std::getline( is, s ); }
      while ( is.good()
        && (!s.size() || (s.size() >= 2 && s[0] == '/' && s[1] == '/')) );
  }

  // extract undecorated name from a file path name
  void extract_name(const std::string & file, std::string & name)
  {
    name = "";
    std::string::size_type end = file.find_last_of('.');
    if (end == std::string::npos) return;
    std::string::size_type pos = file.find_last_not_of(
      "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_-", end-1);
    name = file.substr(pos+1, end-pos-1);
  //std::cout << "name: " << name << std::endl;
  }

  //  process_command_request()  ---------------------------------------------//

  int process_command_request( const std::string & command_request )
  {

    std::ifstream build_config(build_filename);
    std::string line;
    while (build_config.good() && line != command_request)
    {
      getstringline(build_config, line);
    }
    if (line != command_request)
    {
      std::cerr << command_request << ": error: command not found\n";
      return 1;
    }

     // for each command in the command-config set
    for(;;)
    {
      getstringline(build_config, line);

      if ( !build_config.good() || line == "end" ) break;
    
      // all: execute command for each file in file_config
      if ( line.find("all: ") == 0 )
      {
        line.erase(0, 5);
        for (int i = 0; i < files_vec.size(); ++i)
        {
          std::string tmp_line(line);
          replace(tmp_line, "%file%", files_vec[i]);
          replace(tmp_line, "%name%", undecorated_files_vec[i]);
          execute(tmp_line);
        }
      }

      // call: call some other command
      else if ( line.find("call: ") == 0 )
      {
        line.erase(0, 6);
        replace_parms( line );
        int result = process_command_request( line );
        if ( result ) return result;
      }

      // set: set a parms name/value
      else if ( line.find("set: ") == 0 )
      {
        replace_parms( line );
        store_parm( line.substr(5) );
      }

      // execute line once
      else
      {
        execute(line);
      }
    }
    return 0;
  } // process_command_request

} // unnamed namespace

// 	main()  ------------------------------------------------------------------//      

int main(int argc, char * argv[])
{
  for (; argc > 1 && *argv[1] == '-'; ++argv, --argc )
  {
    if (std::strncmp( argv[1]+1, "b=", 2) == 0) build_filename = argv[1]+3; 
    else if (std::strncmp( argv[1]+1, "f=", 2) == 0) files_filename = argv[1]+3;
    else if (*(argv[1]+1) == 'x') execute_command = true; 
    else { std::cerr << argv[1] << ": unrecognized argument\n"; return 1; } 
  } 

  if (argc < 2)
  {
    std::cerr << " usage: booster [option] command [macroname=value ...]\n"
                 " options:\n"
                 "   -b=filename   Build configuration file.  Default: build.cfg\n"
                 "   -f=filename   Files configuration file.  Default: files.cfg\n"
                 "   -x            Execute the generated command.\n"
                 " example usage: booster -f=short_test.cfg * path=.. opt=3\n";
    return 1;
  }

  const std::string command_request(argv[1]);

  // build parms
  for (; argc >= 3; ++argv, --argc ) store_parm( argv[2] );

  // store the files file in several formats for later use
  std::ifstream file_config(files_filename);
  std::string file;
  std::string name_only;
  while (file_config.good())
  {
    getstringline(file_config, file);
    if (!file_config.good()) break;
    extract_name(file, name_only);
    if ( files.size() )
    {
      files += " ";
      undecorated_files += " ";
    }
    files += file;
    undecorated_files += name_only;
    files_vec.push_back( file );
    undecorated_files_vec.push_back( name_only );
  }

  return process_command_request( command_request );
}
