#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 ";
      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) return;
        }
    }

  selected = true;

/*
  if (!Cmt::quiet)
    {
      cerr << "Marking tag ";
      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);

  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, PriorityUserTag, "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;

      for (int i = 2; i < words.size (); i++)
	{
	  name = words[i];
	  if (name == "") break;
	  ref = add (name, PriorityUserTag, "use", use);
	  tag->add_tag_exclude (ref);
	  ref->add_tag_exclude (tag);
	}
    }
}

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

    /*
  if (Cmt::debug)
    {
      cmt_string t = "Tag::add> ";
      t += name;
      Database::dump (t, Database::key_tag);
    }
    */

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

      if (Cmt::debug)
        {
          cout << "Tag::add3> tag=" << tag << endl;
        }

      return (tag);
    }

  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;

    /*
  if (Cmt::debug)
    {
      cmt_string t = "Tag::add4> ";
      t += name;
      Database::dump (t, Database::key_tag);

      cout << "Tag::add5> tag=" << tag << endl;
    }
    */

  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 ();

    /*
  if (Cmt::debug)
    {
      cmt_string t = "Tag::clear_all> ";
      Database::dump (t, Database::key_tag);
    }
    */

  int tag_index;

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

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

    /*
  if (Cmt::debug)
    {
      cmt_string t = "Tag::clear_all 2> ";
      Database::dump (t, Database::key_tag);
    }
    */
}

/*----------------------------------------------------------*/
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 (bool quiet) const
{
  static const cmt_string priority_text[] = {
    "Lowest",
    "Default",
    "Uname",
    "Config",
    "UserTag",
    "PrimaryUserTag",
    "Tag"
  };

  if (selected)
    {
      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;
    }
}

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



