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

/*

From Frank Behner, John Allison 13th February 1999.
From Christian Arnault               Oct.     2000

*/

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>

#include "cmt_triggers.h"
#include "cmt_std.h"
#include "cmt_string.h"
#include "cmt_system.h"
#include "cmt_awk.h"
#include "cmt_use.h"
#include "cmt_symbol.h"
#include "cmt_constituent.h"
#include "cmt_syntax.h"
#include "cmt_log.h"

//--------------------------------------------------
class Libmap
{
public:
  typedef cmt_vector<Libmap> LibmapVector;

  static Libmap& find (const cmt_string& name, const cmt_string& package);
  static Libmap& add (const cmt_string& name, const cmt_string& package);
  static LibmapVector& libmaps ();
  static Libmap& find_with_trigger (const cmt_string& name);
  static Libmap& null ();

public:
  Libmap ();

  void add_trigger (const cmt_string& trigger_name);
  void add_use (const cmt_string& use_name);
  int operator != (const Libmap& other) const;
  int operator == (const Libmap& other) const;
  void set_used ();

  cmt_string name;
  cmt_string package;
  CmtSystem::cmt_string_vector triggers;
  bool used;
  CmtSystem::cmt_string_vector uses;
};
//--------------------------------------------------

//--------------------------------------------------
Libmap& Libmap::find (const cmt_string& name, const cmt_string& package)
{
  LibmapVector& table = libmaps ();

  for (int i = 0; i < table.size (); i++)
    {
      Libmap& libmap = table[i];

#ifdef USE_PACKAGE_SCOPE
      if ((name == libmap.name) &&
	  (package == libmap.package)) return (libmap);
#else
      if (name == libmap.name) return (libmap);
#endif
    }

  return (null ());
}

Libmap& Libmap::add (const cmt_string& name, const cmt_string& package)
{
  {
    Libmap& libmap = find (name, package);

    if (libmap != null ()) return (libmap);
  }

  LibmapVector& table = libmaps ();

  Libmap& libmap = table.add ();

  libmap.name = name;
  libmap.package = package;

  return (libmap);
}

Libmap::LibmapVector& Libmap::libmaps ()
{
  static cmt_vector<Libmap> table;

  return (table);
}

Libmap::Libmap () : used (false)
{
}

void Libmap::add_trigger (const cmt_string& trigger_name)
{
  cmt_string& trigger = triggers.add ();

  trigger = trigger_name;
}

void Libmap::add_use (const cmt_string& use_name)
{
  cmt_string& use = uses.add ();

  use = use_name;
}

Libmap& Libmap::find_with_trigger (const cmt_string& name)
{
  LibmapVector& table = libmaps ();

  for (int i = 0; i < table.size (); i++)
    {
      Libmap& libmap = table[i];

      for (int j = 0; j < libmap.triggers.size (); j++)
	{
	  const cmt_string& trigger = libmap.triggers[j];

	  if (name == trigger) return (libmap);
	}
    }

  return (null ());
}

Libmap& Libmap::null ()
{
  static Libmap null_libmap;

  return (null_libmap);
}

int Libmap::operator != (const Libmap& other) const
{
  return (this != &other);
}

int Libmap::operator == (const Libmap& other) const
{
  return (this == &other);
}

void Libmap::set_used ()
{
  if (used) return;

  used = true;

  cmt_string package_name;
  cmt_string use_name;

  for (int i = 0; i < uses.size (); i++)
    {
      const cmt_string& use = uses[i];

      int pos = use.find ("::");

      if (pos == cmt_string::npos)
	{
	  package_name = "";
	  use_name = use;
	}
      else
	{
	  use.substr (0, pos, package_name);
	  use.substr (pos + 2, use_name);

	  Libmap& libmap = find (use_name, package_name);
	  if (libmap != null ())
	    {
	      libmap.set_used ();
	    }
	}
    }
}
//--------------------------------------------------

//--------------------------------------------------
//
//  This class analyzes a trigger description file (ie a file
// named <constituent>.triggers and located in the cmt directory of
// a package).
//
//  The file is expected to include lines of the form:
//
//    <constituent>_implied_libs=<lib> <lib> ...
//    <constituent>_triggers=<trigger> <trigger> ...
//
//  with <lib> having the form:
//
//    <package>::<constituent>
//     eg: Cm::Cm
//
//    or more simply
//
//    <constituent>
//
//  and <trigger> being an include file path (in the same form as
//  the one used in the dependency files)
//
//     eg: $(CMROOT)/src/Cm.h
//         $(AGDDROOT)/AGDD/AGDD.h
// 
//
//    When reading one trigger description file, one Libmap object is 
// created, and its list of uses and triggers are filled in.
//
//--------------------------------------------------
class TriggerAnalyzer : public FAwk
{
public:
  TriggerAnalyzer (const cmt_string& package_name);
  void begin ();
  void filter (const cmt_string& line);
  void end ();

private:
  cmt_string package;
};
//--------------------------------------------------

//--------------------------------------------------
TriggerAnalyzer::TriggerAnalyzer (const cmt_string& package_name) :
  package (package_name)
{
}

void TriggerAnalyzer::begin ()
{
}

void TriggerAnalyzer::filter (const cmt_string& line)
{
  int pos = line.find ("=");
  if (pos == 0)
    {
      CmtMessage::error ("Syntax in trigger file : empty name");
      /*
      if (!Cmt::get_quiet ())
        {
          cerr << "Syntax error in trigger file : empty name" << endl;
        }
      */
      exit (0);
    }
  if (pos == cmt_string::npos)
    {
      CmtMessage::error ("Syntax in trigger file : no = sign");
      /*
      if (!Cmt::get_quiet ())
        {
          cerr << "Syntax error in trigger file : no = sign" << endl;
        }
      */
      exit (0);
    }

  cmt_string name;
  cmt_string text;
  CmtSystem::cmt_string_vector words;

  line.substr (0, pos, name);
  line.substr (pos + 1, text);
  CmtSystem::split (text, " ", words);

  if (name.find ("_triggers") != cmt_string::npos)
    {
      name.replace ("_triggers", "");

      Libmap& libmap = Libmap::add (name, package);

      for (int i = 0; i < words.size (); i++)
	{
	  const cmt_string& w = words[i];
	  libmap.add_trigger (w);
	}
    }
  else if (name.find ("_implied_libraries") != cmt_string::npos)
    {
      name.replace ("_implied_libraries", "");

      Libmap& libmap = Libmap::add (name, package);

      for (int i = 0; i < words.size (); i++)
	{
	  const cmt_string& w = words[i];
	  libmap.add_use (w);
	}
    }
  else
    {
      CmtMessage::error ("Syntax in trigger file : bad keyword (" + name + ")");
      /*
      if (!Cmt::get_quiet ())
        {
          cerr << "Syntax error in trigger file : bad keyword (" << 
	    name << ")" << endl;
        }
      */
      exit (0);
    }
}

void TriggerAnalyzer::end ()
{
}
//--------------------------------------------------

//--------------------------------------------------
//
//  This filter scans the CMT dependency file of a constituent.
//
//  The file should contains a set of entries, each composed of one line:
//
//      <module-name>_dependencies=<dependency> <dependency> ...
//
//  (Each such line actually defines one make macro used to trigger the
//   rebuild of the corresponding module)
//
//  Dependencies may typically be:
//     o the module source itself
//     o a local dependency (ie. one which is preficed by one of the
//              include dirs)
//     o an external dependency (all other cases)
//
//  Local dependencies will generate triggers for this constituent (filling
//  up the "triggers" unique-vector)
//
//  External dependencies will trigger the use of one Libmap object (thus
//  filling up the "uses" unique-vector)
//
//  At the end of the scan, the results are printed to standard output
//  with a format of a trigger description file.
//
//    For libraries, only the first level list is output (ie. only Libmap
//  object directly triggered by dependencies of that constituent are
//  listed)
//
//    For applications, indirect dependencies are considered and resolved
//  recursively.
//
//--------------------------------------------------
class DependencyAnalyzer : public FAwk
{
public:
  DependencyAnalyzer (const cmt_string& package_name, 
		      Constituent& constituent_ref);
  void begin ();
  void filter (const cmt_string& line);
  virtual void end ();

protected:

  void add_trigger (const cmt_string& name);
  void add_use (Libmap& libmap);

  CmtSystem::cmt_string_vector include_dirs;
  cmt_vector<Libmap*> uses;
  CmtSystem::cmt_string_vector triggers;
  Constituent& constituent;
  cmt_string package;
  cmt_string package_upper;
};

class LibraryAnalyzer : public DependencyAnalyzer
{
public:
  LibraryAnalyzer (const cmt_string& package_name, 
                   Constituent& constituent_ref);
  void end ();
};

class ApplicationAnalyzer : public DependencyAnalyzer
{
public:
  ApplicationAnalyzer (const cmt_string& package_name, 
                       Constituent& constituent_ref);
  void end ();
};
//--------------------------------------------------

//--------------------------------------------------
DependencyAnalyzer::DependencyAnalyzer (const cmt_string& package_name, 
					Constituent& constituent_ref) :
  package (package_name),
  constituent (constituent_ref)
{
  cmt_string dirs;

  int pos;
  char c;

  package_upper = package;

  for (pos = 0; pos < package_upper.size (); pos++)
    {
      c = package_upper[pos];
      package_upper[pos] = toupper (c);
    }

  CmtSystem::execute ("cmt show include_dirs", dirs);
  dirs.replace_all ("\n", "");
  CmtSystem::split (dirs, " ", include_dirs);
}

void DependencyAnalyzer::begin ()
{
}

void DependencyAnalyzer::filter (const cmt_string& line)
{
  /* Clip target out of dependency file... */
  int pos = line.find ("=");
  if ((pos == 0) || (pos == cmt_string::npos))
    {
      CmtMessage::error ("Syntax in dependency file: " + line
			 + ": Missing = or target name.");
      /*
      if (!Cmt::get_quiet ())
        {
          cerr << "  ERROR: Syntax in dependency file: " << line << endl;
          cerr << "  Missing = or target name." << endl;
        }
      */
      exit (1);
    }

  cmt_string module;

  line.substr (0, pos, module);
  module.trim ();
  module.replace ("_dependencies", "");

  if (module == "cmt_path_make") return;

  int underscore = module.find_last_of ("_");

  if (underscore != cmt_string::npos)
    {
      module[underscore] = '.';
    }

  static cmt_string dependencies;

  line.substr (pos + 1, dependencies);

  if (dependencies == "") 
    {
      CmtMessage::warning ("It seems there is nothing after \'=\' "
			   "in dependency file " + m_file_name);
      //      cerr << "#CMT> Warning: It seems there is nothing after \'=\' "
      //	"in dependency file " << m_file_name << endl;
      return;
    }

  CmtSystem::cmt_string_vector deps;

  CmtSystem::split (dependencies, " ", deps);

  for (int i = 0; i < deps.size (); i++)
    {
      const cmt_string& dep = deps[i];

      //
      // dep may either be:
      //  o the module itself
      //  o a file in one of include_dirs
      //  o something else
      //

      if (dep.find (module) != cmt_string::npos)
        {
	  // This is the module itself.
        }
      else
        {
          bool found = false;

          for (int j = 0; j < include_dirs.size (); j++)
            {
              const cmt_string& dir = include_dirs[j];

              if (dep.find (dir) == 0)
                {
		  // This is a local dependency.

                  cmt_string name = dep;

		  if (dir == "$(src)")
		    {
		      cmt_string new_dir;

		      new_dir = "$(";
		      new_dir += package_upper;
		      new_dir += "ROOT)/src/";

		      name.replace (dir, new_dir);
		    }

		  if (CmtSystem::file_separator () == '\\')
		    {
		      name.replace_all (CmtSystem::file_separator (), "/");
		    }

		  Libmap& libmap = Libmap::find_with_trigger (name);

		  if (libmap != Libmap::null ())
		    {
		      add_use (libmap);
		    }
		  else
		    {
		      add_trigger (name);
		    }

                  found = true;
                  break;
                }
            }

          if (!found)
            {
	      cmt_string name = dep;

	      if (CmtSystem::file_separator () == '\\')
		{
		  name.replace_all (CmtSystem::file_separator (), "/");
		}

	      // This is an external dependency.

	      Libmap& libmap = Libmap::find_with_trigger (name);

	      if (libmap != Libmap::null ())
		{
		  add_use (libmap);
		}
            }
        }
    }
}

void DependencyAnalyzer::end ()
{
}

void DependencyAnalyzer::add_trigger (const cmt_string& name)
{
  for (int i = 0; i < triggers.size (); i++)
    {
      const cmt_string& trigger = triggers[i];

      if (trigger == name) return;
    }

  cmt_string& new_trigger = triggers.add ();

  new_trigger = name;
}

void DependencyAnalyzer::add_use (Libmap& libmap)
{
  for (int i = 0; i < uses.size (); i++)
    {
      const Libmap& ref = *(uses[i]);

      if (ref == libmap) return;
    }

  uses.push_back (&libmap);
}

LibraryAnalyzer::LibraryAnalyzer (const cmt_string& package_name, 
                                  Constituent& constituent_ref) :
  DependencyAnalyzer (package_name, constituent_ref)
{
}

void LibraryAnalyzer::end ()
{
  cmt_string macro_name;
  cmt_string output;

  int i;

  if (uses.size () > 0)
    {
      for (i = 0; i < uses.size (); i++)
	{
	  Libmap& libmap = *(uses[i]);

	  libmap.set_used ();
	}

      Libmap::LibmapVector& table = Libmap::libmaps ();

      macro_name = constituent.name;
      macro_name += "_implied_libraries";

      output  = "macro_prepend ";
      output += macro_name;
      output += " \"";
      for (i = 0; i < table.size (); i++)
	{
	  Libmap& libmap = table[i];
	  
	  if (libmap.used)
	    {
#ifdef USE_PACKAGE_SCOPE
	      output += libmap.package;
              output += "::";
#endif
              output += libmap.name;
              output += " ";
	    }
	}
      output += "\"";

      SyntaxParser::parse_requirements_text (output, "", 0);

      Symbol* macro = Symbol::find (macro_name);
      output = macro_name;
      output += "=";
      output += macro->build_macro_value ();

      cout << output << endl;
    }

  if (triggers.size () > 0)
    {
      macro_name = constituent.name;
      macro_name += "_triggers";

      output  = "macro_prepend ";
      output += macro_name;
      output += " \"";
      for (i = 0; i < triggers.size (); i++)
	{
	  const cmt_string& trigger = triggers[i];
	  
	  output += trigger;
          output += " ";
	}
      output += "\"";

      SyntaxParser::parse_requirements_text (output, "", 0);

      Symbol* macro = Symbol::find (macro_name);
      output = macro_name;
      output += "=";
      output += macro->build_macro_value ();

      cout << output << endl;
    }
}

ApplicationAnalyzer::ApplicationAnalyzer (const cmt_string& package_name, 
                                          Constituent& constituent_ref) :
  DependencyAnalyzer (package_name, constituent_ref)
{
}

void ApplicationAnalyzer::end ()
{
  cmt_string macro_name;
  cmt_string output;

  int i;

  if (uses.size () > 0)
    {
      for (i = 0; i < uses.size (); i++)
	{
	  Libmap& libmap = *(uses[i]);

	  libmap.set_used ();
	}

      Libmap::LibmapVector& table = Libmap::libmaps ();

      macro_name = constituent.name;
      macro_name += "linkopts";

      output  = "macro_prepend ";
      output += macro_name;
      output += " \"";
      for (i = 0; i < table.size (); i++)
	{
	  Libmap& libmap = table[i];
	  
	  if (libmap.used)
	    {
              output += "$(implied_library_prefix)";
              output += libmap.name;
              output += "$(implied_library_suffix) ";
	    }
	}
      output += "\"";

      SyntaxParser::parse_requirements_text (output, "", 0);

      Symbol* macro = Symbol::find (macro_name);
      output = macro_name;
      output += "=";
      output += macro->build_macro_value ();

      cout << output << endl;
    }
}
//--------------------------------------------------

//--------------------------------------------------
//
//  The UseAnalyzer is called first to reach all used packages.
// For each package found, it retreives the *.triggers files
// which contain the trigger descriptions for every constituent
// of the package.
//
//  For each trigger description file found, a TriggerAnalyzer is run
// which in turn fills in the database of Libmap objects.
//
//--------------------------------------------------
class UseAnalyzer
{
public:
  void run (const cmt_string& constituent);
  void run (const cmt_string& location, 
            const cmt_string& package,
            const cmt_string& filter_out = "");
};
//--------------------------------------------------

//--------------------------------------------------
void UseAnalyzer::run (const cmt_string& constituent)
{
  Use* use = &(Use::current ());

  run ("./", use->get_package_name (), constituent);

  Use::UsePtrVector& uses = Use::get_ordered_uses ();
  for (int i = 0; i < uses.size (); i++)
    {
      use = uses[i];

      cmt_string s;
                                  
      s = use->real_path;
      s += CmtSystem::file_separator ();
      s += use->get_package_name ();
      s += CmtSystem::file_separator ();
      s += use->version;
      s += CmtSystem::file_separator ();
                                  
      if (use->style == mgr_style) s += "mgr";
      else s += "cmt";

      s += CmtSystem::file_separator ();

      run (s, use->get_package_name ());
    }
}

void UseAnalyzer::run (const cmt_string& location, 
                       const cmt_string& package,
                       const cmt_string& filter_out)
{
  static cmt_regexp expression ("[.]triggers$");

  TriggerAnalyzer analyzer (package);

  CmtSystem::cmt_string_vector files;

  CmtSystem::scan_dir (location, expression, files);

  cmt_string name;

  for (int i = 0; i < files.size (); i++)
    {
      const cmt_string& file = files[i];

      if (filter_out != "")
        {
          CmtSystem::basename (file, ".triggers", name);
          if (name == filter_out) continue;
        }

      analyzer.run (file);
    }
}
//--------------------------------------------------


//--------------------------------------------------
void TriggerGenerator::run (const cmt_string& constituent_name)
{
  Constituent* constituent = Constituent::find (constituent_name);

  Use* use = &(Use::current ());
  cmt_string package = use->get_package_name ();

  // UseAnalyzer use_analyzer (package);
  // use_analyzer.run ("cmt show uses -quiet");

  UseAnalyzer use_analyzer;
  use_analyzer.run (constituent_name);

  cmt_string file_name;

  file_name = "./";
  file_name += constituent_name;
  file_name += "_dependencies.";
#ifdef WIN32
  file_name += "nmake";
#else
  file_name += "make";
#endif

  DependencyAnalyzer* analyzer = 0;

  if (constituent->type == Library)
    {
      analyzer = new LibraryAnalyzer (package, *constituent);
    }
  else if (constituent->type == Application)
    {
      analyzer = new ApplicationAnalyzer (package, *constituent);
    }
  else
    {
      return;
    }

  if (analyzer->run (file_name) == Awk::failed)
    {
      CmtMessage::warning (" File " + file_name + " not found");
      //      cerr << "  File " << file_name << " not found" << endl;
    }

  delete analyzer;
}
//--------------------------------------------------
