//-----------------------------------------------------------
// Copyright Christian Arnault LAL-Orsay CNRS
// arnault@lal.in2p3.fr
// See the complete license in cmt_license.txt "http://www.cecill.info". 
//-----------------------------------------------------------

#include "cmt_deps_builder.h"
#include "cmt_system.h"
#include "cmt_use.h"
#include "cmt_include.h"
#include "cmt_symbol.h"
#include "cmt_log.h"

//
//  While parsing a C++ file, these are the possible usefull
// states we can reach.
//  Each state correspond to a specific state action function.
//
enum state_def
{
  at_start,                                // beginning of the file
  in_line,                                // along a line
  in_string,                        // inside a quoted string
  in_char,                                // inside a quoted char
  in_comment,                        // inside a multi-line comment
  in_string_comment,        // inside a quoted string in a comment
  in_char_comment,                // inside a quoted char in a comment
  in_line_comment                // inside a single-line comment
};

//--------------------------------------------------
static int build_deps (const cmt_string& name,
                       const cmt_string& dir_name,
                       int current_path_index,
                       const CmtSystem::cmt_string_vector& include_paths,
                       const CmtSystem::cmt_string_vector& substitutions,
                       CmtSystem::cmt_string_vector& all_deps,
                       CmtSystem::cmt_string_vector& deps);


//--------------------------------------------------
static void header_file_action (const char* header_file,
                                const cmt_string& dir_name,
                                int current_path_index,
                                const CmtSystem::cmt_string_vector& include_paths,
                                const CmtSystem::cmt_string_vector& substitutions,
                                CmtSystem::cmt_string_vector& all_deps,
                                CmtSystem::cmt_string_vector& deps)
{
  bool found = false;

  for (int i = 0; i < all_deps.size (); i++)
    {
      if (all_deps[i] == header_file)
        {
          found = true;
          break;
        }
    }
  
  if (!found)
    {
      all_deps.push_back (header_file);
      
      int path_index = build_deps (header_file,
                                   dir_name,
                                   current_path_index,
                                   include_paths,
                                   substitutions,
                                   all_deps,
                                   deps);
      
      if (path_index >= 0)
        {
          cmt_string full_name;
          
          if (path_index == 1)
            {
              full_name = dir_name;
              full_name += CmtSystem::file_separator ();
              
              if (current_path_index >= 2)
                {
                  full_name.replace (include_paths[current_path_index - 2],
                                     substitutions[current_path_index - 2]);
                }
            }
          else if (path_index > 1)
            {
              full_name  = substitutions[path_index - 2];
              full_name += CmtSystem::file_separator ();
            }
          
          full_name += header_file;
          
          deps.push_back (full_name);
        }
    }
}


//--------------------------------------------------
static char* at_start_action (char* ptr,
                              state_def& state,
                              const cmt_string& dir_name,
                              int current_path_index,
                              const CmtSystem::cmt_string_vector& include_paths,
                              const CmtSystem::cmt_string_vector& substitutions,
                              CmtSystem::cmt_string_vector& all_deps,
                              CmtSystem::cmt_string_vector& deps)
{
  char term = 0;

  if (*ptr == '#')
    {
      ptr++;
      while ((*ptr == ' ') || (*ptr == '\t')) ptr++;
      if (!strncmp (ptr, "include", 7))
        {
          ptr += 7;

          while (*ptr == ' ') ptr++;
          if (*ptr == '<')
            {
              term = '>';
              ptr++;
            }
          else if (*ptr == '"')
            {
              term = '"';
              ptr++;
            }
          else
            {
              state = in_line;
              ptr += strlen (ptr);
              return (ptr);
            }
        }
      else
        {
          state = in_line;
          ptr += strlen (ptr);
          return (ptr);
        }
    }
  else if (!strncmp (ptr, "      include", 13))
    {
      ptr += 13;

      while ((*ptr == ' ') || (*ptr == '\t')) ptr++;
      if (*ptr == '\'')
        {
          term = '\'';
          ptr++;
        }
      else
        {
          state = in_line;
          return (ptr);
        }
    }
  else if (!strncmp (ptr, "\tinclude", 8))
    {
      ptr += 8;

      while ((*ptr == ' ') || (*ptr == '\t')) ptr++;
      if (*ptr == '\'')
        {
          term = '\'';
          ptr++;
        }
      else
        {
          state = in_line;
          return (ptr);
        }
    }
  else
    {
      state = in_line;
      return (ptr);
    }

  char* end;

  end = strchr (ptr, term);
  if (end != 0)
    {
      *end = 0;
    }
  
  const char* header_file = ptr;
  
  header_file_action (header_file,
                      dir_name,
                      current_path_index,
                      include_paths,
                      substitutions,
                      all_deps,
                      deps);
  
  if (end != 0)
    {
      *end = term;
    }

  state = in_line;
  ptr += strlen (ptr);
  
  return (ptr);
}

//--------------------------------------------------
static char* in_line_action (char* ptr, state_def& state)
{
  char* pattern = &ptr[strlen (ptr)];

  char* pos = strchr (ptr, '"');
  if (pos != 0)
    {
      if (pos < pattern)
        {
          state = in_string;
          pattern = pos;
        }
    }

  pos = strchr (ptr, '\'');
  if (pos != 0)
    {
      if (pos < pattern)
        {
          state = in_char;
          pattern = pos;
        }
    }

  pos = strstr (ptr, "/*");   //*/
  if (pos != 0)
    {
      if (pos < pattern)
        {
          state = in_comment;
          pattern = pos + 1;
        }
    }

  pos = strstr (ptr, "//");
  if (pos != 0)
    {
      if (pos < pattern)
        {
          state = in_line_comment;
          pattern = pos + 1;
        }
    }

  if (state != in_line)
    {
      ptr = pattern + 1;
    }
  else
    {
      ptr += strlen (ptr);
    }

  return (ptr);
}

//--------------------------------------------------
static char* in_string_action (char* ptr, state_def& state)
{
  char* pos = strchr (ptr, '"');
  if (pos == 0)
    {
        // This string is not finished till the end of the line..
        // we expect it continues to the nex line...
      ptr += strlen (ptr);
    }
  else
    {
      pos--;
      if (*pos == '\\')
        {
          ptr = pos + 2;
        }
      else
        {
          ptr = pos + 2;
          state = in_line;
        }
    }

  return (ptr);
}

//--------------------------------------------------
static char* in_char_action (char* ptr, state_def& state)
{
  char* pos = strchr (ptr, '\'');
  if (pos == 0)
    {
        // This string is not finished till the end of the line..
        // we expect it continues to the nex line...
      ptr += strlen (ptr);
    }
  else
    {
      pos--;
      if (*pos == '\\')
        {
          ptr = pos + 2;
        }
      else
        {
          ptr = pos + 2;
          state = in_line;
        }
    }

  return (ptr);
}

//--------------------------------------------------
static char* in_comment_action (char* ptr, state_def& state)
{
  char* pattern = &ptr[strlen (ptr)];
  char* pos = strchr (ptr, '"');
  if (pos != 0)
    {
      if (pos < pattern)
        {
          state = in_string_comment;
          pattern = pos;
        }
    }
  pos = strchr (ptr, '\'');
  if (pos != 0)
    {
      if (pos < pattern)
        {
          state = in_char_comment;
          pattern = pos;
        }
    }
  pos = strstr (ptr, "*/");
  if (pos != 0)
    {
      if (pos < pattern)
        {
          state = in_line;
          pattern = pos + 1;
        }
    }

  if (state == in_comment)
    {
      ptr += strlen (ptr);
    }
  else
    {
      ptr = pattern + 1;
    }

  return (ptr);
}

//--------------------------------------------------
static char* in_string_comment_action (char* ptr, state_def& state)
{
  char* pos = strchr (ptr, '"');
  if (pos == 0)
    {
        // This string is not finished till the end of the line..
        // we expect it continues to the nex line...
      ptr += strlen (ptr);
    }
  else
    {
      pos--;
      if (*pos == '\\')
        {
          ptr = pos + 2;
        }
      else
        {
          ptr = pos + 2;
          state = in_comment;
        }
    }

  return (ptr);
}

//--------------------------------------------------
static char* in_char_comment_action (char* ptr, state_def& state)
{
  char* pos = strchr (ptr, '\'');
  if (pos == 0)
    {
        // This string is not finished till the end of the line..
        // we expect it continues to the nex line...
      ptr += strlen (ptr);
    }
  else
    {
      pos--;
      if (*pos == '\\')
        {
          ptr = pos + 2;
        }
      else
        {
          ptr = pos + 2;
          state = in_comment;
        }
    }

  return (ptr);
}

//--------------------------------------------------
static char* in_line_comment_action (char* ptr, state_def& state)
{
  ptr += strlen (ptr);

  return (ptr);
}

//--------------------------------------------------
static void build_deps_stream (istream& input,
                               const cmt_string& dir_name,
                               int current_path_index,
                               const CmtSystem::cmt_string_vector& include_paths,
                               const CmtSystem::cmt_string_vector& substitutions,
                               CmtSystem::cmt_string_vector& all_deps,
                               CmtSystem::cmt_string_vector& deps)
{
  Log;

  if (input)
    {
      log << "CMT> build_deps_stream dir_name="
	  << dir_name << log_endl;

      while (!input.eof ())
        {
          char line[16384];

          input.getline (line, sizeof (line));
          char* ptr = &line[0];
          state_def state = at_start;

	  log << "CMT> build_deps_stream2 line=[" 
	      << line << "]" << log_endl;

          while (strlen (ptr) > 0)
            {
              switch (state)
                {
                  case at_start:
                    ptr = at_start_action (ptr,
                                           state,
                                           dir_name,
                                           current_path_index,
                                           include_paths,
                                           substitutions,
                                           all_deps,
                                           deps);
                    break;
                  case in_line:
                    ptr = in_line_action (ptr, state);
                    break;
                  case in_string:
                    ptr = in_string_action (ptr, state);
                    break;
                  case in_char:
                    ptr = in_char_action (ptr, state);
                    break;
                  case in_comment:
                    ptr = in_comment_action (ptr, state);
                    break;
                  case in_string_comment:
                    ptr = in_string_comment_action (ptr, state);
                    break;
                  case in_char_comment:
                    ptr = in_char_comment_action (ptr, state);
                    break;
                  case in_line_comment:
                    ptr = in_line_action (ptr, state);
                    break;
                }
            }
        }
    }
}

//--------------------------------------------------
static int build_deps (const cmt_string& name,
                       const cmt_string& dir_name,
                       int current_path_index,
                       const CmtSystem::cmt_string_vector& include_paths,
                       const CmtSystem::cmt_string_vector& substitutions,
                       CmtSystem::cmt_string_vector& all_deps,
                       CmtSystem::cmt_string_vector& deps)
{
  Log;

  int result = -1;
  cmt_string new_dir;

  log << "CMT> build_deps name=" << name << " dir_name=" 
      << dir_name << log_endl;

    //
    // Return 0 when the file is found in the current directory
    //
  if (CmtSystem::test_file (name))
    {
      ifstream input (name.c_str ());
      if (input)
        {
          CmtSystem::dirname (name, new_dir);
          build_deps_stream (input, new_dir, current_path_index,
                             include_paths, substitutions,
                             all_deps, deps);
          return (0);
        }
    }

  cmt_string full_name;

  full_name = dir_name;
  full_name += CmtSystem::file_separator ();
  full_name += name;

    //
    // Return 1 when the file is found in the directory of the
    // upper level source file
    //
  if (CmtSystem::test_file (full_name))
    {
      ifstream input (full_name.c_str ());
      if (input)
        {
          CmtSystem::dirname (full_name, new_dir);
          build_deps_stream (input, new_dir, current_path_index,
                             include_paths, substitutions,
                             all_deps, deps);
          return (1);
        }
    }

  int path_index = -1;

    //
    // Return [path_index + 2] when the include file is found at one of
    // the include_paths
    //
  for (path_index = 0; path_index < include_paths.size (); path_index++)
    {
      full_name  = include_paths[path_index];
      full_name += CmtSystem::file_separator ();
      full_name += name;

      log << "CMT> build_deps2 full_name=" << full_name << log_endl;

      if (CmtSystem::test_file (full_name))
        {
          ifstream in (full_name.c_str ());
          if (in)
            {
              CmtSystem::dirname (full_name, new_dir);

	      log << "CMT> build_deps3 new_dir=" << new_dir << log_endl;

              build_deps_stream (in,
                                 new_dir,
                                 path_index + 2,
                                 include_paths,
                                 substitutions,
                                 all_deps,
                                 deps);

              return (path_index + 2);
            }
        }
    }

  log << "CMT> build_deps3" << log_endl;

  return (-1);
}

//--------------------------------------------------------------------------
void DepsBuilder::clear ()
{
  m_include_paths.clear ();
  m_substitutions.clear ();
}

//--------------------------------------------------------------------------
void DepsBuilder::add (const cmt_string& path, const cmt_string& substitution)
{
  if (path[path.size () - 1] == CmtSystem::file_separator ())
    {
      cmt_string p = path;
      p.erase (path.size () - 1);
      m_include_paths.push_back (p);
    }
  else
    {
      m_include_paths.push_back (path);
    }

  m_substitutions.push_back (substitution);
}

//--------------------------------------------------------------------------
void DepsBuilder::add_includes (const Use& use)
{
  Log;

  const Include::IncludeVector& includes = use.includes;
  int include_number;

  for (include_number = 0;
       include_number < includes.size ();
       include_number++)
    {
      const Include& include = includes[include_number];

      cmt_string temp = include.name;
      cmt_string pattern;
      cmt_string name;
      char end_pattern;

      int start = 0;

      for (;;)
        {
          int begin;

          begin = temp.find (start, "${");
          if (begin != cmt_string::npos)
            {
              end_pattern = '}';
            }
          else
            {
              begin = temp.find (start, "$(");
              if (begin != cmt_string::npos)
                {
                  end_pattern = ')';
                }
              else
                {
                  break;
                }
            }

          start = begin + 2;

          int end;
          end = temp.find (start, end_pattern);
          if (end == cmt_string::npos) break;
          if (end < begin) break;
          start = end + 1;

          temp.substr (begin, end - begin + 1, pattern);
          temp.substr (begin + 2, end - begin - 2, name);

          Symbol* macro = Symbol::find (name);
          if (macro != 0)
            {
              cmt_string value = macro->resolve_macro_value ();
              value += CmtSystem::file_separator ();
              temp.replace_all (pattern, value);
            }
          else
            {
              cmt_string value = CmtSystem::getenv (name);
              value += CmtSystem::file_separator ();
              temp.replace_all (pattern, value);
            }
        }

      log << "include = " << temp << log_endl;

      add (temp, include.name);
    }
}

//--------------------------------------------------------------------------
CmtSystem::cmt_string_vector& DepsBuilder::run (const cmt_string& file_name)
{
  m_deps.clear ();
  m_all_deps.clear ();

  cmt_string preprocessor;
  Symbol* macro = Symbol::find ("preprocessor_command");
  if (macro != 0)
    {
      preprocessor = macro->resolve_macro_value ();
    }

  if (preprocessor == "")
    {
        //
        //   Since no preprocessor command is defined,
        // we use the internal mechanism provided here.
        //
      cmt_string new_dir;

      CmtSystem::dirname (file_name, new_dir);

      build_deps (file_name,
                  new_dir,
                  0,
                  m_include_paths,
                  m_substitutions,
                  m_all_deps,
                  m_deps);
    }
  else
    {
        //
        //  An external preprocessor command is defined. We expect it
        // to follow a "standard" syntax for its output, ie:
        //   o It starts with:
        //       <module>.o: ...
        //   o There may be many lines with trailing back-slashes
        //   o All entries are space-separated
        //   o One of the entries is the source file name itself
        //
        //  The preprocessor command expects the list of -I options
        // (resolved from the "includes" macro) and the list of 
        // -D/-U options (resolved from the "*_pp_*flags" macros)
        //

        //
        // Building the complete command (still the pp_*flags are
        // missing)
        //
      preprocessor += " ";
      macro = Symbol::find ("includes");
      preprocessor += macro->resolve_macro_value ();
      preprocessor += " ";
      preprocessor += file_name;
      
      cmt_string output;
      
      CmtSystem::execute (preprocessor, output);

        //
        // Make the output as one single big line.
        //

      output.replace_all ("\n", " ");
      output.replace_all ("\\ ", " ");
      
      CmtSystem::cmt_string_vector files;
      
      CmtSystem::split (output, " \t", files);

        //
        // Analyze each entry
        //
      
      for (int i = 1; i < files.size (); i++)
        {
          const cmt_string& file = files[i];
          if (file == file_name) continue;
          
          cmt_string dir;
          cmt_string name;
          cmt_string full_name;
          
          CmtSystem::dirname (file, dir);

            //
            // Only declared include_paths will be taken into account
            // Others are considered as system include paths.
            //
          
          for (int j = 0; j < m_include_paths.size (); j++)
            {
              const cmt_string& p = m_include_paths[j];
              if (dir == p)
                {
                  CmtSystem::basename (file, name);
                  full_name = m_substitutions[j];
                  full_name += name;

                    //
                    // We add in the "m_deps" list the symbolic form
                    // of the path rather that the expanded one.
                    //
                  
                  m_deps.push_back (full_name);
                  
                  break;
                }
            }
        }
    }

  return (m_deps);
}

