//-----------------------------------------------------------
// Copyright Christian Arnault LAL-Orsay CNRS
// arnault@lal.in2p3.fr
// Modified by Grigory Rybkin
// 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_tag.h"
#include "cmt_database.h"
#include "cmt_log.h"

/*----------------------------------------------------------*/
/*                                                          */
/*  Operations on Tags                                      */
/*                                                          */
/*----------------------------------------------------------*/

/*----------------------------------------------------------*/
void Tag::unmark ()
{
  if (!is_primary ()) return;
  if (!m_selected) return;

  Log;

  if ((m_priority == PriorityDefault) ||
      (m_priority == PrioritySite) ||
      (m_priority == PriorityUname)) return;

  log << "Unmarking tag[" << this << "] " << m_name << " p=" << m_priority << log_endl;

  m_selected = false;
  m_def_use = 0;
  //m_act_context = "";
}

/*----------------------------------------------------------*/
void Tag::unmark_all ()
{
  static TagPtrVector& vector = tags ();

  int tag_number;

  Log;
  log << "Unmarking all tags (" << vector.size () << ")" << log_endl;

  for (tag_number = 0; tag_number < vector.size (); tag_number++)
    {
      Tag* tag = vector[tag_number];

      tag->unmark ();
    }
}

/**
 *   Restore the tag tree after cleaning up one tag and
 *   its local tree.
 */
void Tag::restore_tree ()
{
  static TagPtrVector& vector = tags ();

  int tag_number;

  Log;
  log << "Restore tags tree (" << vector.size () << ")" << log_endl;

  for (tag_number = 0; tag_number < vector.size (); tag_number++)
    {
      Tag* tag = vector[tag_number];
      
      if (tag->is_primary () && tag->is_selected ())
	{
	  if (tag->m_tag_refs.size () > 0)
	    {
	      int number;
	      
	      for (number = 0; number < tag->m_tag_refs.size (); number++)
		{
		  Tag* ref = tag->m_tag_refs[number];
		  
		  ref->mark ("");
		}
	    }
	}
    }
}

/*----------------------------------------------------------*/
void Tag::mark (const cmt_string& context)
//void Tag::mark ()
{
  if (!is_primary ()) return;
  if (m_selected) return;

  Log;

  const CmtSystem::cmt_string_vector& tags_remove = Cmt::get_tags_remove ();
  for (int i = tags_remove.size () - 1; i >= 0; i--)
    if (tags_remove[i] == m_name) return;

  if (m_tag_excludes.size () > 0)
    {
      int number;

      for (number = 0; number < m_tag_excludes.size (); number++)
        {
          Tag* ref = m_tag_excludes[number];

	  if (ref->is_selected ()) 
            {
              if (m_priority > ref->m_priority)
                {
		  //
		  // Although this other contradictory tag is already selected,
		  // its priority is lower. Therefore it will lose !! It has to be 
		  // unselected ...
		  //

                  ref->unmark ();
                }
              else
                {
		  /*
		    if (!Cmt::quiet)
                    {
		    cerr << "Cannot mark excluded tag " << m_name << " p=" << m_priority
		    << " because " << ref->m_name << "(" << ref->m_priority << ")" << endl;
		    show (0);
                    }
		  */

                  return;
                }
            }
        }
    }

  m_selected = true;
  if (context.size () != 0)
    m_act_context = context;

  log << "Marking tag[" << this << "] " << m_name << " p=" << m_priority
      << " context=" << context << log_endl;

  if (m_tag_refs.size () > 0)
    {
      int number;

      for (number = 0; number < m_tag_refs.size (); number++)
        {
          Tag* ref = m_tag_refs[number];

          ref->mark (context);
        }
    }
}

/*----------------------------------------------------------*/
void Tag::action (const CmtSystem::cmt_string_vector& words, Use* use)
{
  cmt_string name;
  Tag* tag;
  Tag* ref;

  if (words.size () < 1) return;
  name = words[1];
  if (name == "") return;

  tag = add (name, PriorityUserTag, "use", use);

  int priority = PriorityUserTag;
  if (tag->m_priority > priority)
    {
      priority = tag->m_priority;
    }

  //if (tag->m_tag_refs.size () == 0)
  //{

  tag->add_ref_use (use);
  //  tag->m_set_use = use;

  //}

  for (int i = 2; i < words.size (); i++)
    {
      name = words[i];
      if (name == "") break;
      ref = add (name, priority, "use", use);
      tag->add_tag_ref (ref);
      if (tag->is_selected ())  // it was previously selected
	{
	  ref->mark (use ? use->get_package_name () : "");
	}
    }
}

/*----------------------------------------------------------*/
void Tag::action_apply (const CmtSystem::cmt_string_vector& words, Use* use)
{
  cmt_string name;
  Tag* tag;
  
  if (words.size () < 1) return;
  name = words[1];
  if (name == "") return;
  
  if (use->get_current_scope () == ScopePrivate) return;

  Symbol::expand (name);
  
  
  if (name == "")
    {
      CmtMessage::warning ("apply_tag with empty name [" + words[1] + "]");
      /*
      if (!Cmt::get_quiet ())
	{
	  cerr << "#CMT> Warning: apply_tag with empty name [" << words[1] << "]" << endl;
	}
      */
    }
  else
    {
      tag = find (name);
      if (tag == 0)
	{
	  tag = add (name, PriorityUserTag, "use", use);
	}

      tag->mark (use ? use->get_package_name () : "");

      Use::UsePtrVector * puses;
      if (!(puses = apply_tag ().find (tag)))
	{
	  Use::UsePtrVector uses;
	  uses.push_back (use);
	  apply_tag ().add (tag, uses);
	}
      else
	{
	  bool have (false);
	  for (int i = 0; i < puses->size (); i++)
	    {
	      if ((*puses)[i] == use)
		{
		  have = true;
		  break;
		}
	    }
	  if (!have)
	    puses->push_back (use);
	}

    }
}

/*----------------------------------------------------------*/
void Tag::action_exclude (const CmtSystem::cmt_string_vector& words, Use* use)
{
  cmt_string name;
  Tag* tag;
  Tag* ref;

  if (words.size () < 1) return;
  name = words[1];
  if (name == "") return;
  if (use->get_current_scope () == ScopePrivate) return;

  tag = add (name, PriorityUserTag, "use", use);

  if (tag->m_tag_excludes.size () == 0)
    {
      tag->m_set_use = use;

      int i;

      for (i = 2; i < words.size (); i++)
	{
          cmt_string n;
	  n = words[i];
	  if (n == "") break;
	  ref = add (n, PriorityUserTag, "use", use);

	  /*
	    if (!Cmt::quiet)
            {
	    cerr << "Excluding tag " << n << "(" << ref->m_priority << ") from tag " 
	    << name << "(" << tag->m_priority << ")" << endl;
            }
	  */

	  tag->add_tag_exclude (ref);
	  ref->add_tag_exclude (tag);
	}

      //
      // We have to check that some of the excluded tags may be already selected.
      // Then we have to figure out which one has to win:
      //
      //  the one with the highest priority
      //  or the first that had been declared.
      //
      
      int count = 0;
      int winner_count = 0;
      
      Tag* winner = 0;

      if (tag->is_selected ()) 
        {
          count++;
          winner = tag;
          winner_count = 1;
        }

      for (i = 0; i < tag->m_tag_excludes.size (); i++)
        {
          Tag* ref = tag->m_tag_excludes[i];
          
          if (ref == 0) continue;
              
          if (ref->is_selected ()) 
            {
              count++;

              if ((winner == 0) ||
                  (ref->m_priority > winner->m_priority))
                {
                  winner = ref;
                  winner_count = 1;
                }
              else if (ref->m_priority == winner->m_priority)
                {
                  winner_count++;
                }
            }
        }
      
      if (count > 1)
        {
          if (winner_count > 1)
            {
	      //
	      // Several contradictory tags are selected and have the same priority!!
	      //
            }

	  //
	  // We have at least one selected, and one winner. 
	  // All others will be unselected.
	  //
          
          if (tag != winner)
            {
              tag->unmark ();
            }

          for (i = 0; i < tag->m_tag_excludes.size (); i++)
            {
              Tag* ref = tag->m_tag_excludes[i];
              
              if (ref == 0) continue;
              if (ref == winner) continue;
              
              ref->unmark ();
            }
        }
    }
}

/*----------------------------------------------------------*/
Tag* Tag::find (const cmt_string& name)
{
  TagMap& map = tag_map ();

  Tag* result = map.find (name);

  return (result);

  /*
    TagPtrVector& vector = tags ();

    int tag_index;
    Tag* tag;

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

    for (tag_index = 0; tag_index < vector.size (); tag_index++)
    {
    tag = vector[tag_index];

    if ((tag != 0) && (tag->m_name == name))
    {
    return (tag);
    }
    }

    return (0);
  */
}

/*----------------------------------------------------------*/
Tag* Tag::find (const cmt_string& name, TagMap& instances)
{
  Tag* result = instances.find (name);

  return (result);

  /*
    int tag_index;

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

    for (tag_index = 0; tag_index < instances.size (); tag_index++)
    {
    Tag& tag = instances[tag_index];

    if (tag.m_name == name)
    {
    return (&tag);
    }
    }

    return (0);
  */
}

/*----------------------------------------------------------*/
Tag* Tag::add (const cmt_string& name, 
	       int priority, 
               const cmt_string& context, 
	       Use* use)
{
  Log;

  TagMap& map = tag_map ();
  TagPtrVector& vector = tags ();
  TagVector& instances = all_tags ();

  Tag* tag;

  if (name == "") return (0);

  tag = find (name);
  if (tag != 0)
    {
      if (priority > tag->m_priority) 
        {
          tag->m_priority = priority;

	  /*
	    if (!Cmt::quiet)
            {
	    cerr << "increasing priority of " << name << " p=" << priority << endl;
            }
	  */
        }
      else
        {
	  /*
	    if (!Cmt::quiet)
            {
	    cerr << "keeping priority of " << name << " p=" << tag->m_priority << endl;
            }
	  */
        }

      log << "re-adding tag[" << tag << "] " << name << " p=" << priority
	  << " context=" << context << (use != 0 ? " " + use->get_info () : "") << log_endl;
      
      return (tag);
    }

  Tag& tag_object = instances.add ();
  tag = &tag_object;
  vector.push_back (tag);
  map.add (name, tag_object);

  log << "adding tag[" << tag << "] " << name << " p=" << priority
      << " context=" << context << (use != 0 ? " " + use->get_info () : "") << log_endl;
  
  tag->clear ();

  tag->m_name = name;
  tag->m_priority = priority;
  tag->m_def_use = use;
  tag->m_context = context;

  int pos = 0;
  int length = name.size ();
  while (pos < length)
    {
      int and_pos = name.find (pos, "&");
      if ((and_pos == cmt_string::npos) && (pos == 0)) break;

      cmt_string op_name;

      if (and_pos == cmt_string::npos)
        {
          name.substr (pos, op_name);
        }
      else
        {
          name.substr (pos, and_pos - pos, op_name);
        }

      if (op_name != "")
        {
          Tag* t = find (op_name);
          if (t == 0)
            {
              t = add (op_name, priority, context, use);
            }

          if (t != 0)
            {
              tag->m_and_operands.push_back (t);
              if (t->get_priority () > priority)
                {
                  tag->m_priority = t->get_priority ();
                }
            }
          else
            {
              CmtMessage::warning ("Tag::add> unknown tag " + op_name + " in tag expression");
	      //              cerr << "#Tag::add> unknown tag " << op_name << " in tag expression" << endl;
            }
        }

      if (and_pos == cmt_string::npos) break;

      pos = and_pos + 1;
    }

  return (tag);
}

/*----------------------------------------------------------*/
int Tag::tag_number ()
{
  static TagPtrVector& vector = tags ();

  return (vector.size ());
}

/*----------------------------------------------------------*/
Tag* Tag::tag (int index)
{
  static TagPtrVector& vector = tags ();

  return (vector[index]);
}

/*----------------------------------------------------------*/
void Tag::clear_all ()
{
  TagMap& map = tag_map ();
  TagPtrVector& vector = tags ();
  TagVector& instances = all_tags ();

  int tag_index;

  for (tag_index = 0; tag_index < instances.size (); tag_index++)
    {
      Tag& tag = instances[tag_index];
      tag.clear ();
    }

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

/*----------------------------------------------------------*/
Tag::TagMap& Tag::tag_map ()
{
  static Database& db = Database::instance ();
  TagMap& map = db.tag_map ();

  return (map);
}

/*----------------------------------------------------------*/
Tag::TagVector& Tag::all_tags ()
{
  static Database& db = Database::instance ();
  TagVector& vector = db.all_tags ();

  return (vector);
}

/*----------------------------------------------------------*/
Tag::TagPtrVector& Tag::tags ()
{
  static Database& db = Database::instance ();
  TagPtrVector& vector = db.tags ();

  return (vector);
}

/*----------------------------------------------------------*/
Tag* Tag::get_default ()
{
  static Tag default_tag;
  static bool initialized = false;
  if (!initialized)
    {
      initialized = true;

      default_tag.m_name = "Default";
      default_tag.m_selected = true;
      default_tag.m_context = "Default";
      default_tag.m_priority = PriorityDefault;
    }

  return (&(default_tag));
}

/*----------------------------------------------------------*/
bool Tag::check_tag_used (const Tag* tag)
{
  if (tag == 0) return (false);

  if (tag->m_tag_refs.size () > 0) return (true);
  if (tag->m_tag_excludes.size () > 0) return (true);
  if (apply_tag ().has (tag)) return (true);

  return (false);
}

/*----------------------------------------------------------*/
Tag::TagUses& Tag::apply_tag ()
{
  static TagUses tag_uses;
  return tag_uses;
}

/*----------------------------------------------------------*/
Tag::Tag () :
  m_name (""),
  m_selected (false),
  m_context (""),
  m_def_use (0),
  m_set_use (0),
  m_act_context (""),
  m_priority (0)
{
  clear ();
}

/*----------------------------------------------------------*/
Tag::Tag (const Tag& other)
{
  clear ();

  m_name = other.m_name;
  m_selected = other.m_selected;
  m_and_operands = other.m_and_operands;
  m_tag_refs = other.m_tag_refs;
  m_tag_excludes = other.m_tag_excludes;
  m_context = other.m_context;
  m_def_use = other.m_def_use;
  m_set_use = other.m_set_use;
  m_act_context = other.m_act_context;
  m_ref_uses = other.m_ref_uses;
  m_priority = other.m_priority;
}

/*----------------------------------------------------------*/
Tag& Tag::operator = (const Tag& other)
{
  clear ();

  m_name = other.m_name;
  m_selected = other.m_selected;
  m_and_operands = other.m_and_operands;
  m_tag_refs = other.m_tag_refs;
  m_tag_excludes = other.m_tag_excludes;
  m_context = other.m_context;
  m_def_use = other.m_def_use;
  m_set_use = other.m_set_use;
  m_act_context = other.m_act_context;
  m_ref_uses = other.m_ref_uses;
  m_priority = other.m_priority;

  return (*this);
}

/*----------------------------------------------------------*/
Tag::~Tag ()
{
}

/*----------------------------------------------------------*/
void Tag::clear ()
{
  m_name = "";
  m_tag_refs.clear ();
  m_tag_excludes.clear ();
  m_priority = PriorityUserTag;
  m_def_use = 0;
  m_set_use = 0;
  m_act_context = "";
  m_ref_uses.clear ();
  m_context = "";

  m_selected = false;
  m_and_operands.clear ();
}

/*----------------------------------------------------------*/
void Tag::add_tag_ref (Tag* ref)
{
  if (ref == 0) return;

  if (m_tag_refs.size () > 0)
    {
      int number;

      for (number = 0; number < m_tag_refs.size (); number++)
        {
          Tag* t = m_tag_refs[number];
	  if (t == ref) return;
        }
    }

  m_tag_refs.push_back (ref);
}

/*----------------------------------------------------------*/
void Tag::add_tag_exclude (Tag* ref)
{
  if (ref == 0) return;

  if (m_tag_excludes.size () > 0)
    {
      int number;

      for (number = 0; number < m_tag_excludes.size (); number++)
        {
          Tag* t = m_tag_excludes[number];
	  if (t == ref) return;
        }
    }

  m_tag_excludes.push_back (ref);
}

/*----------------------------------------------------------*/
void Tag::add_ref_use (Use* use)
{
  if (use == 0) return;

  for (int number = 0; number < m_ref_uses.size (); number++)
    {
      Use* u = m_ref_uses[number];
      if (u == use) return;
    }

  m_ref_uses.push_back (use);
}

/*----------------------------------------------------------*/
void Tag::show_definition (bool quiet, ostream& out,
			   const cmt_string& prefix) const
{
  static const cmt_string priority_text[] = {
    "Lowest",
    "Default",
    "Uname",
    "Config",
    "UserTag",
    "PrimaryUserTag",
    "Tag"
  };

  if (m_name == "Default") return;

  out << prefix << m_name;

  if (!quiet)
    {
      //cout << "context=" << m_context << " use=" << m_def_use << endl;
      
      if ((m_context == "use") || (m_def_use != 0))
	{
	  if (m_def_use != 0)
	    {
	      out << " (from ";
	      if (m_context != "use") out << m_context + " in ";
	      out << "package " << m_def_use->get_package_name () << ")";
	    }
	}
      else
	{
	  out << " (from " << m_context << ")";
	}
      //out << " (" << priority_text[m_priority] << ")";
      
      if (m_tag_refs.size () > 0)
	{
	  int number;
	  
	  if (m_ref_uses.size () != 0)
	    {
	      out << " package [";
	      for (number = 0; number < m_ref_uses.size (); number++)
		{
		  Use* use = m_ref_uses[number];
		  if (number > 0) out << " ";
		  out << use->get_package_name ();
		}
	      out << "]";
	    }
	  /*
	  if (m_set_use != 0)
	    {
	      out << " package " << m_set_use->get_package_name ();
	    }
	  */
	  
	  out << " implies [";
	  
	  for (number = 0; number < m_tag_refs.size (); number++)
	    {
	      Tag* ref = m_tag_refs[number];
	      if (number > 0) out << " ";
	      out << ref->m_name;
	    }
	  
	  out << "]";
	}
      
      if (m_tag_excludes.size () > 0)
	{
	  int number;
	  
	  if (m_set_use != 0)
	    {
	      out << " package " << m_set_use->get_package_name ();
	    }
	  
	  out << " excludes [";
	  
	  for (number = 0; number < m_tag_excludes.size (); number++)
	    {
	      Tag* ref = m_tag_excludes[number];
	      if (number > 0) out << " ";
	      out << ref->m_name;
	    }
	  
	  out << "]";
	}

      if (Use::UsePtrVector * puses = apply_tag ().find (this))
	{
	  out << " applied [";
	  for (int i = 0; i < puses->size (); i++)
	    {
	      if (i > 0) out << " ";
	      Use * use = (*puses)[i];
	      if (use)
		{
		  out << use->get_package_name ();
		}
	      else
		{
		  out << "current";
		}
	    }
	  out << "]";
	}

      if (m_act_context.size () != 0 &&
	  (m_def_use != 0 &&
	   m_act_context != m_context &&
	   m_act_context != m_def_use->get_package_name () ||
	   m_def_use == 0 &&
	   m_act_context != m_context))
	out << " activated " << m_act_context;
    }
  out << endl;
}

/*----------------------------------------------------------*/
void Tag::show (bool quiet, ostream& out, const cmt_string& prefix) const
{
  if (is_primary () && is_selected ()) show_definition (quiet, out, prefix);
}

/*----------------------------------------------------------*/
bool Tag::is_selected () const
{
  if (is_primary ())
    {
      return (m_selected);
    }
  else
    {
      for (int i = 0; i < m_and_operands.size(); i++)
	{
	  Tag* t = m_and_operands[i];

	  if (!t->is_selected ()) return (false);
	}

      return (true);
    }
}

/*----------------------------------------------------------*/
bool Tag::is_primary () const
{
  return (m_and_operands.size() == 0);
}

/*----------------------------------------------------------*/
const cmt_string& Tag::get_name () const
{
  return (m_name);
}

/*----------------------------------------------------------*/
int Tag::get_priority () const
{
  return (m_priority);
}

/**
 *  Recomputes all references to other tags using the new set of 
 *  instances.  This concerns
 *
 *    TagPtrVector m_and_operands;
 *    TagPtrVector m_tag_refs;
 *    TagPtrVector m_tag_excludes;
 *
 *   The pointers stored in those reference vectors are pointing to Tag objects
 *  in one collection
 *    We want to convert these pointers to pointers to the same Tag objects but 
 *  stored in the other Tag collection provided in the argument.
 *
 */
void Tag::install (TagMap& instances)
{
  int i;

  for (i = 0; i <  m_and_operands.size (); i++)
    {
      Tag* t = m_and_operands[i];
      if (t != 0)
        {
          t = find (t->m_name, instances);
          m_and_operands[i] = t;
        }
    }

  for (i = 0; i <  m_tag_refs.size (); i++)
    {
      Tag* t = m_tag_refs[i];
      if (t != 0)
        {
          t = find (t->m_name, instances);
          m_tag_refs[i] = t;
        }
    }

  for (i = 0; i <  m_tag_excludes.size (); i++)
    {
      Tag* t = m_tag_excludes[i];
      if (t != 0)
        {
          t = find (t->m_name, instances);
          m_tag_excludes[i] = t;
        }
    }
}

/*
  Check if a tag is part of the operands of a tag
*/
bool Tag::use_operand (const Tag* other) const
{
  if (other == this) return (true);
  if (m_and_operands.size () == 0) return (false);

  for (int i = 0; i < m_and_operands.size (); i++)
    {
      Tag* t = m_and_operands[i];

      if (t != 0)
        {
	  if (t->use_operand (other)) return (true);
        }
    }

  return (false);
}

/*
  Check if a tag is part of the refs of a tag
*/
bool Tag::use_ref (const Tag* other) const
{
  if (other == this) return (false);
  if (m_tag_refs.size () == 0) return (false);

  for (int i = 0; i < m_tag_refs.size (); i++)
    {
      Tag* t = m_tag_refs[i];

      if (t == other) return (true);
    }

  return (false);
}


