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

#include "cmt_tag.h"
#include "cmt_database.h"

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

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

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

    /*
  if (!Cmt::quiet)
    {
      cerr << "Unmarking tag " << name << " p=" << priority << endl;
      show (0);
    }
    */

  selected = false;
  def_use = 0;
}

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

  int tag_number;

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

      if (tag->selected) tag->unmark ();
    }
}

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

  int tag_number;

  for (tag_number = 0; tag_number < Tags.size (); tag_number++)
    {
      Tag* tag = Tags[tag_number];
      
      if (tag->selected)
	{
	  if (tag->tag_refs.size () > 0)
	    {
	      int number;
	      
	      for (number = 0; number < tag->tag_refs.size (); number++)
		{
		  Tag* ref = tag->tag_refs[number];
		  
		  ref->mark ();
		}
	    }
	}
    }
}

/*----------------------------------------------------------*/
void Tag::mark ()
{
  if (selected) return;

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

      for (number = 0; number < tag_excludes.size (); number++)
        {
          Tag* ref = tag_excludes[number];
	  if (ref->selected) 
            {
              if (priority > ref->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 " << name << " p=" << priority
                           << " because " << ref->name << "(" << ref->priority << ")" << endl;
                      show (0);
                    }
                    */

                  return;
                }
            }
        }
    }

  selected = true;

    /*
  if (!Cmt::quiet)
    {
      cerr << "Marking tag " << name << " p=" << priority << endl;
      show (0);
    }
    */

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

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

          ref->mark ();
        }
    }
}

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

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

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

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

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

  tag->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->selected)  // it was previously selected
	{
	  ref->mark ();
	}
    }
}

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

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

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

  if (tag->tag_excludes.size () == 0)
    {
      tag->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->priority << ") from tag " 
                   << name << "(" << tag->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->selected) 
        {
          count++;
          winner = tag;
          winner_count = 1;
        }

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

              if ((winner == 0) ||
                  (ref->priority > winner->priority))
                {
                  winner = ref;
                  winner_count = 1;
                }
              else if (ref->priority == winner->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->tag_excludes.size (); i++)
            {
              Tag* ref = tag->tag_excludes[i];
              
              if (ref == 0) continue;
              if (ref == winner) continue;
              
              ref->unmark ();
            }
        }
    }
}

/*----------------------------------------------------------*/
Tag* Tag::find (const cmt_string& name)
{
  static TagPtrVector& Tags = tags ();

  int tag_index;
  Tag* tag;

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

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

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

  return (0);
}

/*----------------------------------------------------------*/
Tag* Tag::add (const cmt_string& name, 
	       int priority, 
               const cmt_string& context, 
	       Use* use)
{
  static TagPtrVector& Tags = tags ();
  static TagVector& AllTags = all_tags ();

  Tag* tag;

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

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

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

      return (tag);
    }

    /*
  if (!Cmt::quiet)
    {
      cerr << "adding tag " << name << " p=" << priority << endl;
    }
    */

  Tag& tag_object = AllTags.add ();
  tag = &tag_object;
  Tags.push_back (tag);

  tag->clear ();
  tag->name = name;

  tag->selected = false;
  tag->priority = priority;
  tag->def_use = use;
  tag->context = context;

  return (tag);
}

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

  return (Tags.size ());
}

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

  return (Tags[index]);
}

/*----------------------------------------------------------*/
void Tag::clear_all ()
{
  static TagPtrVector& Tags = tags ();
  static TagVector& AllTags = all_tags ();

  int tag_index;

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

  Tags.clear ();
  AllTags.clear ();
}

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

  return (AllTags);
}

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

  return (Tags);
}

/*----------------------------------------------------------*/
Tag* Tag::get_default ()
{
  static Tag* default_tag = 0;

  if (default_tag == 0)
    {
      default_tag = add ("Default", PriorityDefault, "Default", 0);
      default_tag->selected = true;
    }

  return (default_tag);
}

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

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

/*----------------------------------------------------------*/
void Tag::clear ()
{
  name = "";
  tag_refs.clear ();
  tag_excludes.clear ();
  priority = PriorityUserTag;
  selected = false;
  def_use = 0;
  set_use = 0;
  context = "";
}

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

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

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

  tag_refs.push_back (ref);
}

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

/*
  if (selected && ref->selected)
    {
      static TagPtrVector& Tags = tags ();

      int tag_index;

      for (tag_index = 0; tag_index < Tags.size (); tag_index++)
        {
          Tag* t = Tags[tag_index];

          if (t == this)
            {
              ref->unmark ();
              break;
            }

          if (t == ref)
            {
              unmark ();
              break;
            }
        }
    }
*/

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

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

  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"
  };

  cout << name;

  if (!quiet)
    {
      //cout << "context=" << context << " use=" << def_use << endl;
      
      if ((context == "use") || (def_use != 0))
	{
	  if (def_use != 0)
	    {
	      cout << " (from ";
	      if (context != "use") cout << context;
	      cout << "package " << def_use->package << ")";
	    }
	}
      else
	{
	  cout << " (from " << context << ")";
	}
      //cout << " (" << priority_text[priority] << ")";
      
      if (tag_refs.size () > 0)
	{
	  int number;
	  
	  if (set_use != 0)
	    {
	      cout << " package " << set_use->package;
	    }
	  
	  cout << " implies [";
	  
	  for (number = 0; number < tag_refs.size (); number++)
	    {
	      Tag* ref = tag_refs[number];
	      if (number > 0) cout << " ";
	      cout << ref->name;
	    }
	  
	  cout << "]";
	}
      
      if (tag_excludes.size () > 0)
	{
	  int number;
	  
	  if (set_use != 0)
	    {
	      cout << " package " << set_use->package;
	    }
	  
	  cout << " excludes [";
	  
	  for (number = 0; number < tag_excludes.size (); number++)
	    {
	      Tag* ref = tag_excludes[number];
	      if (number > 0) cout << " ";
	      cout << ref->name;
	    }
	  
	  cout << "]";
	}
    }
  cout << endl;
}

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

/*----------------------------------------------------------*/
bool Tag::is_selected () const
{
  return (selected);
}



