//-----------------------------------------------------------
// 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_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;
}

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

  int tag_number;

  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;

  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 ()
{
  if (!is_primary ()) return;
  if (m_selected) return;

  Log;

  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;

  log << "Marking tag[" << this << "] " << m_name << " p=" << m_priority << 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 ();
        }
    }
}

/*----------------------------------------------------------*/
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->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 ();
	}
    }
}

/*----------------------------------------------------------*/
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 == "")
    {
      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 ();
    }
}

/*----------------------------------------------------------*/
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 << 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 << 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
            {
              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);

  return (false);
}

/*----------------------------------------------------------*/
Tag::Tag () :
  m_name (""),
  m_selected (false),
  m_context (""),
  m_def_use (0),
  m_set_use (0),
  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_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_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_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::show_definition (bool quiet) const
{
  static const cmt_string priority_text[] = {
    "Lowest",
    "Default",
    "Uname",
    "Config",
    "UserTag",
    "PrimaryUserTag",
    "Tag"
  };

  if (m_name == "Default") return;

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

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

/*----------------------------------------------------------*/
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);
}


