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

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

#include "cmt.h"
#include "cmt_pattern.h"
#include "cmt_use.h"
#include "cmt_database.h"
#include "cmt_error.h"
#include "cmt_syntax.h"
#include "cmt_log.h"


//----------------------------------------------------------

PatternList* PatternList::find (const cmt_string& name)
{
  static PatternListMap& map = pattern_list_map ();

  return (map.find (name));
}


Pattern* PatternList::find_pattern (const cmt_string& name)
{
  PatternList* list = find (name);
  if (list == 0) return (0);

  Pattern::PatternPtrVector& vector = list->get_patterns ();

  if (vector.size () == 0) return (0);

  Pattern* p = vector[vector.size () - 1];

  return (p);
}

Pattern* PatternList::find (const cmt_string& name, Use* use)
{
  PatternList* list = find (name);
  if (list == 0) return (0);

  Pattern::PatternPtrVector& vector = list->get_patterns ();

  for (int i = 0; i < vector.size (); i++)
    {
      Pattern* p = vector[i];

      if (p->use == use) return (p);
    }

  return (0);
}

PatternList* PatternList::add (const cmt_string& name)
{
  PatternList* list = find (name);
  if (list != 0) return (list);

  static PatternListVector& vector = pattern_lists ();

  PatternList& pl = vector.add ();
  pl.m_name = name;

  static PatternListMap& map = pattern_list_map ();
  map.add (name, pl);

  return (&pl);
}

void PatternList::clear_all ()
{
  static PatternListVector& vector = pattern_lists ();
  static PatternListMap& map = pattern_list_map ();

  for (int i = 0; i < vector.size (); i++)
    {
      PatternList& p = vector[i];
      p.clear ();
    }

  vector.clear ();
  map.clear ();
}

PatternList::PatternListMap& PatternList::pattern_list_map ()
{
  static Database& db = Database::instance ();
  static PatternListMap& map = db.pattern_list_map ();

  return (map);
}

PatternList::PatternListVector& PatternList::pattern_lists ()
{
  static Database& db = Database::instance ();
  static PatternListVector& vector = db.pattern_lists ();

  return (vector);
}

PatternList::PatternList ()
{
}

PatternList::PatternList (const cmt_string& name) : m_name (name)
{
}

PatternList::~PatternList ()
{
  clear ();
}

Pattern::PatternPtrVector& PatternList::get_patterns ()
{
  return (m_patterns);
}

void PatternList::add_pattern (Pattern* pattern)
{
  for (int i = 0; i < m_patterns.size (); i++)
    {
      Pattern* p = m_patterns[i];
      if (p->use == pattern->use)
	{
	  m_patterns[i] = pattern;
	  return;
	}
    }

  m_patterns.push_back (pattern);
}

void PatternList::clear ()
{
  m_name = "";
  m_patterns.clear ();
}


//----------------------------------------------------------
//
//  Operations on Patterns
//
//----------------------------------------------------------

//----------------------------------------------------------
void Pattern::action (const CmtSystem::cmt_string_vector& words, Use* use)
{
  bool global = false;
  int start_index;

  if (words.size () < 2) return;

  //
  // expected syntax is:
  //
  //  pattern [-global] pattern-name any-cmt-statement
  //
  // where any-cmt-statement may contain "templates"
  //
  //      <package>
  //      <PACKAGE>
  //      <version>
  //      <path>
  //

  cmt_string& option = words[1];

  if (option == "-global")
    {
      global = true;
      start_index = 2;
    }
  else
    {
      start_index = 1;
    }

  cmt_string& name = words[start_index];

  start_index++;

  add (name, words, start_index, global, use);

  if (CmtSystem::testenv ("CMTTESTPATTERN"))
    {
      cerr << "Pattern::action> add " << name << endl;
    }
}

/**
 *
 *  Patterns are stored with two keys : <name> and <use>
 *
 *  thus search must done against these two keys.
 *
 */
Pattern* Pattern::find (const cmt_string& name)
{
  Pattern* p = PatternList::find_pattern (name);

  return (p);
}

//----------------------------------------------------------
Pattern* Pattern::find (const cmt_string& name, Use* use)
{
  Pattern* p = PatternList::find (name, use);

  return (p);
}

//----------------------------------------------------------
void Pattern::add (const cmt_string& name,
                   const CmtSystem::cmt_string_vector& words,
                   int start_index,
                   bool global,
                   Use* use)
{
  static PatternVector& Patterns = patterns ();

  Pattern* pattern;

  pattern = find (name, use);

  if (pattern == 0)
    {
      // No pattern for the pair <name, use> exist yet.
      // create one.
      Pattern& p = Patterns.add ();

      p.clear ();

      p.name = name;
      p.use  = use;

      PatternList* pl = PatternList::add (name);
      pl->add_pattern (&p);

      pattern = &p;
    }
  else
    {
      Pattern& p = *pattern;

      p.clear ();

      p.name = name;
      p.use  = use;
    }

  pattern->line = "";

  int first_word = start_index;

  //
  // Install the cmt-statement both as a vector of words and as a single line
  //
  for (int i = start_index; i < words.size (); i++)
    {
      bool need_quotes = (i > (first_word + 1));
      //bool need_quotes = true;

      cmt_string& s = words[i];

      if (i > start_index) pattern->line += " ";

      if (s == ";") first_word = i+1;

      if ((s == "\n") | (s == ";"))
        {
          pattern->line += "\n  ";
        }
      else
        {
	  cmt_string sep = "\"";

	  if (s.find (sep) != cmt_string::npos)
	    {
	      sep = "\'";
	    }

	  if (!need_quotes) sep = "";

          pattern->line += sep;
          pattern->line += s;
          pattern->line += sep;
        }
    }

  pattern->global = global;
}

/**
 * Get the number of registered patterns
 */
int Pattern::pattern_number ()
{
  static PatternVector& Patterns = patterns ();

  return (Patterns.size ());
}

/**
 * Get the index'th pattern in the database
 */
Pattern& Pattern::pattern (int index)
{
  static PatternVector& Patterns = patterns ();

  return (Patterns[index]);
}

//----------------------------------------------------------
void Pattern::clear_all ()
{
  static PatternVector& Patterns = patterns ();

  for (int i = 0; i < Patterns.size (); i++)
    {
      Pattern& p = Patterns[i];
      p.clear ();
    }

  Patterns.clear ();
}

//----------------------------------------------------------
Pattern::PatternVector& Pattern::patterns ()
{
  static Database& db = Database::instance ();
  static PatternVector& Patterns = db.patterns ();

  return (Patterns);
}

/**
 * Applies all global patterns to all uses
 */
void PatternList::apply_all_globals ()
{
  static PatternListVector& PatternLists = pattern_lists ();

  int i;

  for (i = 0; i < PatternLists.size (); i++)
    {
      PatternList& pl = PatternLists[i];

      int n = pl.m_patterns.size ();

      if (n > 0)
	{
	  Pattern* p = pl.m_patterns[n-1];

	  if ((p != 0) && (p->global)) p->apply ();
	}
    }
}

/**
 * Applies all global patterns to a given Use
 */
void PatternList::apply_all_globals (Use* use)
{
  if (use->get_package_name () == "CMT") return;

  static PatternListVector& PatternLists = pattern_lists ();

  int i;

  for (i = 0; i < PatternLists.size (); i++)
    {
      PatternList& pl = PatternLists[i];

      int n = pl.m_patterns.size ();

      if (n > 0)
	{
	  Pattern* p = pl.m_patterns[n-1];

	  if ((p != 0) && (p->global)) 
	    {
	      if (p->global)
		{
		  if (IgnorePattern::find (p->name, use) == 0) p->apply (use);
		}
	    }
	}
    }
}

/**
 * Show all patterns
 */
void PatternList::show_all_patterns ()
{
  static PatternListVector& PatternLists = pattern_lists ();

  int i;

  for (i = 0; i < PatternLists.size (); i++)
    {
      PatternList& pl = PatternLists[i];

      int n = pl.m_patterns.size ();

      if (n > 0)
	{
	  Pattern* p = pl.m_patterns[n-1];

	  if (p != 0) p->show ();
	}
    }
}
/**
 * Show all pattern names
 */
void PatternList::show_all_pattern_names ()
{
  bool empty = true;

  static PatternListVector& PatternLists = pattern_lists ();

  int i;

  for (i = 0; i < PatternLists.size (); i++)
    {
      PatternList& pl = PatternLists[i];

      int n = pl.m_patterns.size ();

      if (n > 0)
	{
	  Pattern* p = pl.m_patterns[n-1];

	  if (p != 0)
	    {
	      cout << p->name << " ";
	      empty = false;
	    }
	}
    }
  if (!empty) cout << endl;
}

/**
 * Applies all global patterns to all uses
 */
void Pattern::apply_all_globals ()
{
  PatternList::apply_all_globals ();
}

/**
 * Applies all global patterns to a given Use
 */
void Pattern::apply_all_globals (Use* use)
{
  PatternList::apply_all_globals ();
}

/**
 * this is the cmt show patterns command
 * It just shows the pattern declarations.
 */
void Pattern::show_all ()
{
  PatternList::show_all_patterns ();

  show_all_applied_patterns ();
}

/**
 * this is the cmt show applied_patterns command
 * It just shows the pattern applications.
 */
void Pattern::show_all_applied_patterns ()
{
  Use* use = &(Use::current ());
  for (int i = 0; i < use->apply_patterns.size (); i++)
    {
      const ApplyPattern& apply_pattern = use->apply_patterns[i];

      cout << "# " << use->get_package_name ()
           << " applies pattern " << apply_pattern.name << " => " << endl;

      apply_pattern.show ();

      cout << endl;
    }
}

//----------------------------------------------------------
void Pattern::show_all_names ()
{
  PatternList::show_all_pattern_names ();
}

/**
 *  This is the cmt show pattern <name> command
 * It shows both the pattern definition(s) and the places
 * where it is explicitly applied.
 */
void Pattern::show (const cmt_string& name)
{
  static PatternVector& Patterns = patterns ();

  int i;
  int j;

  bool found = false;

  // First show the definitions.

  Pattern* p = Pattern::find (name);

  if (p == 0)
    {
      CmtError::set (CmtError::pattern_not_found, name);
      return;
    }

  p->show ();

  //
  // Then show the packages which explicitly apply the pattern.
  //
  Use* use;
  ApplyPattern* apply_pattern = 0;

  Use::UsePtrVector& uses = Use::get_ordered_uses ();

  for (i = 0; i < uses.size (); i++)
    {
      use = uses[i];
      for (j = 0; j < use->apply_patterns.size (); j++)
        {
          apply_pattern = &(use->apply_patterns[j]);
            
          if (apply_pattern->name == name)
            {
              cout << "# applied by " << use->get_package_name () << " => " << endl;
              apply_pattern->show ();
              cout << endl;
            }
        }
    }

  use = &(Use::current ());
  for (j = 0; j < use->apply_patterns.size (); j++)
    {
      apply_pattern = &(use->apply_patterns[j]);
	
      if (apply_pattern->name == name)
	{
          cout << "# " << use->get_package_name () << " " << use->version << " applies pattern " << name;
	  cout << " => " << endl;
	  apply_pattern->show ();
          cout << endl;
	}
    }
}

//----------------------------------------------------------
Pattern::Pattern ()
{
}

//----------------------------------------------------------
Pattern::~Pattern ()

{
}

//----------------------------------------------------------
void Pattern::clear ()
{
  global = false;
  name = "";
  use = 0;
  line = "";
}

//----------------------------------------------------------
void Pattern::show () const
{
  if (use != 0) cout << "# " << use->get_package_name () << " " << use->version;
  else cout << "# ?? ";

  cout  << " defines ";

  if (global) cout << "global ";
  
  cout << "pattern " << name << " as" << endl;
  cout << "  " << line << endl;
}

//----------------------------------------------------------
class PatternCache
{
public:
  static PatternCache& instance ()
  {
    static PatternCache me;

    return (me);
  }

  static bool update (Use* c, Use* t)
  {
    static PatternCache& me = instance ();

    return (me.do_update (c, t));
  }

  static Use::UsePtrVector& get_list ()
  {
    static PatternCache& me = instance ();

    return (me.list);
  }

private:

  PatternCache () : current(0), target(0)
  {
    current_name = "";
    target_name = "";
  }

  bool do_update (Use* c, Use* t)
  {
    cmt_string c_name = c->get_package_name ();
    cmt_string t_name = t->get_package_name ();

    if ((current_name != c_name) || (target_name != t_name))
      {
	if (CmtSystem::getenv ("TESTCACHE") != "")
	  {
	    cerr << "update cache " << c->get_package_name () << "(" << c << ") " << t->get_package_name () << "(" << t << ")" << endl;
	  }
	
	current = c;
	current_name = c_name;

	target = t;
	target_name = t_name;

	list.clear ();
	  
	if (current != target)
	  {
	    if (!current->get_paths (target, list)) return (false);
	  }
	else
	  {
	    list.push_back (current);
	  }
      }
    else
      {
	if (CmtSystem::getenv ("TESTCACHE") != "")
	  {
	    cerr << "keep cache" << endl;
	  }
      }
    return (true);
  }
  
  Use* current;
  cmt_string current_name;
  Use* target;
  cmt_string target_name;
  Use::UsePtrVector list;
};

/**
 *   Applies a pattern to all uses (except CMT itself) between
 *   current and the use that declared the pattern.
 */
void Pattern::apply () const
{
  Use::UsePtrVector& uses = Use::get_ordered_uses ();

  Use* current = &(Use::current());

  if (Cmt::get_debug ())
    {
      cout << "Pattern(" << name << "::apply> " << " defined in " << use->get_package_name () << endl;
    }

  if (!PatternCache::update (current, use)) return;

  Use::UsePtrVector& list = PatternCache::get_list ();

  for (int i = 0; i < list.size (); i++)
    {
      Use* u = list[i];

      if (Cmt::get_debug ())
	{
	  cout << "Pattern(" << name << "::apply> " << " to package " << u->get_package_name () << endl;
	}

      if ((u->get_package_name () != "CMT") && 
          (IgnorePattern::find (name, u) == 0)) apply (u);
    }
}

//----------------------------------------------------------
void Pattern::apply (Use* context_use) const
{
  static Template::TemplateVector dummy_templates;

  apply (context_use, dummy_templates);
}

/**
 *  Applies a pattern to one particular use.
 */
void Pattern::apply (Use* context_use, 
                     const Template::TemplateVector& templates) const
{
  cmt_string replacement;

  expand (context_use, templates, replacement);

  if (CmtSystem::testenv ("CMTTESTPATTERN"))
    {
      cerr << "Pattern::apply> replacement=[" << replacement << "]" << endl;
    }

  if (replacement != "")
    {
      SyntaxParser::parse_requirements_text (replacement, "", context_use);
    }
}

//----------------------------------------------------------
void Pattern::expand (Use* context_use, 
                      const Template::TemplateVector& templates, 
                      cmt_string& replacement) const
{
  if (context_use == 0) context_use = &(Use::current ());

  if (CmtSystem::testenv ("CMTTESTPATTERN"))
    {
      cerr << "Pattern::expand1> line=[" << line << "]" << endl;
    }

  replacement = line;

  if (replacement != "")
    {
      // Substitute templates from the cmt statement
      replacement.replace_all ("<package>", context_use->get_package_name ().c_str ());
      replacement.replace_all ("<PACKAGE>", context_use->prefix.c_str ());
      replacement.replace_all ("<version>", context_use->version.c_str ());
      replacement.replace_all ("<path>",    context_use->real_path.c_str ());

      cmt_string cmtpath = "";
      cmt_string offset = "";

      context_use->get_cmtpath_and_offset (cmtpath, offset);

      Project* p = Project::find_by_cmtpath (cmtpath);
      if (p != 0)
        {
          const cmt_string& n = p->get_name ();
          replacement.replace_all ("<project>", n.c_str ());
        }

      for (int j = 0; j < templates.size (); j++)
        {
          Template& t = templates[j];
          cmt_string s;
          s = "<";
          s += t.name;
          s += ">";
          replacement.replace_all (s, t.value);
        }

      for (;;)
        {
          int begin = replacement.find ("<");
          if (begin == cmt_string::npos) break;
          int end = replacement.find (begin, ">");
          if (end == cmt_string::npos) break;
          // Do not erase XML constructs
          if (replacement[end-1] == '/') break;
          replacement.erase (begin, end - begin + 1);
        }

      if (CmtSystem::testenv ("CMTTESTPATTERN"))
	{
	  cerr << "Pattern::expand2> repl=[" << replacement << "]" << endl;
	}

    }
}

//----------------------------------------------------------
//
//  Operations on ApplyPatterns
//
//----------------------------------------------------------

//----------------------------------------------------------
void ApplyPattern::action (const CmtSystem::cmt_string_vector& words, Use* use)
{
  //
  // Expected syntax is
  //
  // apply_pattern <pattern-name> [ <template>=<value> ... ]
  //   or just
  // <pattern-name> [ <template>=<value> ... ]
  //

  int first_word = 0;

  if (words[0] == "apply_pattern") first_word = 1;
  else first_word = 0;

  if (words.size () < (first_word + 1)) return;

  if (use == 0) use = &(Use::current());

  cmt_string name = words[first_word];
  Symbol::expand (name);

  if (name == "") return;

  Pattern* p = Pattern::find (name);
  if (p == 0) 
    {
      CmtError::set (CmtError::pattern_not_found, name);
      return;
    }

  ApplyPattern* apply_pattern = add (name, use);

  /*
    We then look for all <name>=<value> pairs
  */
  enum
    {
      need_template,
      need_equal,
      need_value,
      can_add,
      in_error
    } state = need_template;

  cmt_string tname;
  cmt_string tvalue;

  for (int i = (first_word + 1); i < words.size (); i++)
    {
      cmt_string s = words[i];

      if (CmtSystem::testenv ("CMTTESTPATTERN"))
	{
	  cerr << "ApplyPattern::action> " << name << " s=[" << s << "] state=" << state << endl;
	}

      int pos = cmt_string::npos;

      switch (state)
	{
	case need_template:
	  pos = s.find ("=");

	  tname = s;
	  tvalue = "";

	  if (pos == cmt_string::npos)
	    {
	      state = need_equal;
	    }
	  else
	    {
	      s.substr (0, pos, tname);
	      s.substr (pos + 1, tvalue);

	      if (CmtSystem::testenv ("CMTTESTPATTERN"))
		{
		  cerr << "ApplyPattern::action-1> n=[" << tname << "] v=[" << tvalue << "]" << endl;
		}
	      
	      if (tvalue == "")
		{
		  state = need_value;
		}
	      else
		{		  
		  state = can_add;
		}
	    }
	  break;
	case need_equal:
	  pos = s.find ("=");

	  tvalue = "";

	  if (pos != 0)
	    {
	      state = in_error;
	      CmtMessage::warning ("bad syntax in apply_pattern " + name
				   + " (missing '=' separator)"
				   + ( (use != 0) ?
				       " (from " + use->get_package_name () + ")":
				       "" )
				   );
	      /*
	      if (!Cmt::get_quiet ())
		{
		  cerr << "#CMT> Warning: bad syntax in apply_pattern " << name
		       << " (missing '=' separator)";

		  if (use != 0) cerr << " (from " << use->get_package_name () << ")";

		  cerr << endl;
		}
	      */
	      break;
	    }
	  else
	    {
	      s.substr (pos + 1, tvalue);

	      if (tvalue == "")
		{
		  state = need_value;
		}
	      else
		{		  
		  state = can_add;
		}
	    }
	  break;
	case need_value:

	  pos = s.find ("=");

	  if (pos == cmt_string::npos)
	    {
	      tvalue = s;
	      state = can_add;
	    }
	  else
	    {
	      tname = s;
	      tvalue = "";

	      s.substr (0, pos, tname);
	      s.substr (pos + 1, tvalue);

	      if (CmtSystem::testenv ("CMTTESTPATTERN"))
		{
		  cerr << "ApplyPattern::action-2> n=[" << tname << "] v=[" << tvalue << "]" << endl;
		}
	      
	      if (tvalue == "")
		{
		  state = need_value;
		}
	      else
		{		  
		  state = can_add;
		}
	    }

	  break;
	}

      if (state == can_add)
	{
	  state = need_template;

	  if (CmtSystem::testenv ("CMTTESTPATTERN"))
	    {
	      cerr << "ApplyPattern::action-3> n=[" << tname << "] v=[" << tvalue << "]" << endl;
	    }

	  cmt_string tsearch = "<";
	  tsearch += tname;
	  tsearch += ">";

	  if (p->line.find (tsearch) == cmt_string::npos)
	    {
	      CmtMessage::warning ("template <" + tname
				   + "> not expected in pattern " + name
				   + ( (use != 0) ?
				       " (from " + use->get_package_name () + ")":
				       "" )
				   );
	      /*
	      if (!Cmt::get_quiet ())
		{
		  cerr << "#CMT> Warning: template <" << tname << "> not expected in pattern " << name;
		  if (use != 0) cerr << " (from " << use->get_package_name () << ")";
		  cerr << endl;
		}
	      */
	    }

	  Template& t = apply_pattern->replacements.add ();

	  t.name = tname;
	  t.value = tvalue;

	  int size = t.value.size ();

	  if (size >= 2)
	    {
	      if (((t.value[0] == '"') && (t.value[size - 1] == '"')) ||
		  ((t.value[0] == '\'') && (t.value[size - 1] == '\'')))
		{
		  t.value.erase (size - 1);
		  t.value.erase (0, 1);
		}
	    }
	}
    }

  apply_pattern->apply ();
}

//----------------------------------------------------------
ApplyPattern* ApplyPattern::add (const cmt_string& name, Use* use)
{
  ApplyPattern& a = use->apply_patterns.add ();

  a.name = name;
  a.use  = use;

  return (&a);
}

//----------------------------------------------------------
ApplyPattern::ApplyPattern ()
{
}

//----------------------------------------------------------
ApplyPattern::~ApplyPattern ()
{
}

//----------------------------------------------------------
void ApplyPattern::show () const
{
  cmt_string replacement = "";

  Pattern* p = Pattern::find (name);
  if (p == 0) return;

  Use* u = use;
  if (u == 0) u = &(Use::current ());

  p->expand (u, replacements, replacement);

  if (replacement != "")
    {
      replacement.replace_all ("\"", "");
      cout << replacement;
    }
}

//----------------------------------------------------------
void ApplyPattern::apply () const
{
  if (CmtSystem::testenv ("CMTTESTPATTERN"))
    {
      cerr << "ApplyPattern::apply> " << name << endl;
    }

  Pattern* p = Pattern::find (name);
  if (p == 0) 
    {
      if (CmtSystem::testenv ("CMTTESTPATTERN"))
        {
          cerr << "ApplyPattern::apply> " << name << " not found" << endl;
        }

      return;
    }

  if (p->global) return;
  
  Use* u = use;
  if (u == 0) u = &(Use::current ());

  p->apply (u, replacements);
}



//----------------------------------------------------------
/*                                                          */
/*  Operations on IgnorePatterns                            */
/*                                                          */
//----------------------------------------------------------

//----------------------------------------------------------
void IgnorePattern::action (const CmtSystem::cmt_string_vector& words, Use* use)
{
  //
  // Expected syntax is
  //
  // ignore_pattern <pattern-name>
  //

  if (words.size () < 2) return;

  if (use == 0) use = &(Use::current());

  cmt_string& name = words[1];

  add (name, use);
}

//----------------------------------------------------------
IgnorePattern* IgnorePattern::find (const cmt_string& name, Use* use)
{
  int ignore_pattern_index;

  if (use == 0) use = &(Use::current());

  if (use->ignore_patterns.size () == 0) return (0);

  for (ignore_pattern_index = 0;
       ignore_pattern_index < use->ignore_patterns.size ();
       ignore_pattern_index++)
    {
      IgnorePattern& ignore_pattern = use->ignore_patterns[ignore_pattern_index];

      if (ignore_pattern.name == name)
        {
          return (&ignore_pattern);
        }
    }

  return (0);
}

//----------------------------------------------------------
void IgnorePattern::add (const cmt_string& name, Use* use)
{
  IgnorePattern* ignore_pattern;

  ignore_pattern = find (name, use);

  if (ignore_pattern == 0)
    {
      IgnorePattern& a = use->ignore_patterns.add ();

      a.name = name;
      a.use  = use;
    }
}

//----------------------------------------------------------
IgnorePattern::IgnorePattern ()
{
}

//----------------------------------------------------------
IgnorePattern::~IgnorePattern ()
{
}

//----------------------------------------------------------
void IgnorePattern::show () const
{
}
