// dep_item.cc
//
//  Copyright 1999-2005 Daniel Burrows
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; see the file COPYING.  If not, write to
//  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
//  Boston, MA 02111-1307, USA.
//
// Implementation of 'dependency items'.
//
//  For now we use the current version..

#include "aptitude.h"

#include "dep_item.h"
#include "pkg_sortpolicy.h"
#include "pkg_subtree.h"
#include "pkg_ver_item.h"

#include <generic/apt/apt.h>
#include <generic/apt/config_signal.h>

#include <apt-pkg/version.h>

using namespace std;
using namespace __gnu_cxx;

class pkg_depitem:public pkg_subtree
{
  pkgCache::DepIterator firstdep;

  // calculated at initialization: true iff at least one package
  // fulfilling the dependency is available (not necessarily installable)
  bool available;

  bool is_broken();
  // Returns true if the dependency should be displayed as a broken dependency
public:
  pkg_depitem(pkgCache::DepIterator &_first, pkg_signal *_sig);

  void paint(vs_tree *win, int y, bool hierarchical,
	     const style &st);

  style get_normal_style();

  void select(undo_group *undo);
  // A special case -- installs the 'best' target package (using
  // SmartTargetPkg() )
};

pkg_depitem::pkg_depitem(pkgCache::DepIterator &first, pkg_signal *sig):
  pkg_subtree(L""),firstdep(first),available(false)
{
  pkgCache::DepIterator start,end;
  first.GlobOr(start,end);

  string currlabel="";
  bool firstiter=true;
  bool is_or=(start->CompareOp&pkgCache::Dep::Or);

  do
    {
      if(!firstiter)
	currlabel+=" | ";

      firstiter=false;

      currlabel+=start.TargetPkg().Name();
      if(start.TargetVer())
	{
	  currlabel+=" (";
	  currlabel+=start.CompType();
	  currlabel+=" ";
	  currlabel+=start.TargetVer();
	  currlabel+=")";
	}

      for(pkgCache::VerIterator i=start.TargetPkg().VersionList(); !i.end(); i++)
	if(_system->VS->CheckDep(i.VerStr(), start->CompareOp, start.TargetVer()))
	  {
	    available=true;
	    add_child(new pkg_ver_item(i, sig, is_or));
	  }

      for(pkgCache::PrvIterator i=start.TargetPkg().ProvidesList(); !i.end(); i++)
	if(_system->VS->CheckDep(i.ProvideVersion(), start->CompareOp, start.TargetVer()))
	  {
	    available=true;
	    add_child(new pkg_ver_item(i.OwnerVer(), sig, true));
	  }

      if(start==end)
	break;
      // Bleach.  Anyone who can offer a cleaner way to do this gets a virtual cookie :)
      start++;
    } while(1);
  pkg_subtree::set_label(transcode(currlabel, "ASCII"));
}

bool pkg_depitem::is_broken()
{
  pkgCache::DepIterator dep=firstdep;

  // Ok, here's the story: we need to check the DepGInstall dependency flag
  // for the whole `or' group.  HOWEVER, this is only set for the LAST member
  // of an `or' group.  SO, we need to find the last item in the group in order
  // to do this.
  //
  // Alles klar? :)
  while(dep->CompareOp & pkgCache::Dep::Or)
    ++dep;

  // Showing "Replaces" dependencies as broken is weird.
  return ((firstdep.IsCritical() ||
	   firstdep->Type==pkgCache::Dep::Recommends ||
	   firstdep->Type==pkgCache::Dep::Suggests) &&
	  !((*apt_cache_file)[dep]&pkgDepCache::DepGInstall));
}

void pkg_depitem::paint(vs_tree *win, int y, bool hierarchical,
			const style &st)
{
  if(is_broken())
    {
      // If we can quickly prove that this is not satisfiable, tell
      // the user that.  (specifically, if there are no available packages
      // fulfilling the dependency)

      wstring broken_str=transcode(available?_("UNSATISFIED"):_("UNAVAILABLE"));

      vs_subtree<pkg_tree_node>::paint(win, y, hierarchical,
				       get_name()+L" ("+broken_str+L")");
    }
  else
    pkg_subtree::paint(win, y, hierarchical,
		       st);
}

style pkg_depitem::get_normal_style()
{
  if(is_broken())
    return get_style("DepBroken");
  else
    return pkg_subtree::get_normal_style();
}

void pkg_depitem::select(undo_group *undo)
{
  if(firstdep->Type!=pkgCache::Dep::Conflicts &&
     firstdep->Type!=pkgCache::Dep::Replaces)
    {
      bool last_or=true;
      for(pkgCache::DepIterator dep=firstdep;
	  !dep.end() && last_or;
	  dep++)
	{
	  last_or&=dep->CompareOp&pkgCache::Dep::Or;
	  if((*apt_cache_file)[dep]&pkgDepCache::DepInstall)
	    return;
	}
      pkgCache::PkgIterator pkg=firstdep.SmartTargetPkg();
      // (note: no OR handling, since the first one is the preferred choice..)
      if(!pkg.end())
	(*apt_cache_file)->mark_install(pkg, aptcfg->FindB(PACKAGE "::Auto-Install", true), false, undo);
    }
}

class pkg_grouppolicy_dep:public pkg_grouppolicy
{
public:
  pkg_grouppolicy_dep(pkg_signal *_sig, desc_signal *_desc_sig)
    :pkg_grouppolicy(_sig, _desc_sig) {}

  void add_package(const pkgCache::PkgIterator &pkg, pkg_subtree *root)
  {
    pkg_item_with_generic_subtree *newtree=new pkg_item_with_generic_subtree(pkg,
									     get_sig());

    root->add_child(newtree);

    setup_package_deps<pkg_item_with_generic_subtree>(pkg,
						      pkg.CurrentVer(),
						      newtree,
						      get_sig());
  }

  virtual ~pkg_grouppolicy_dep() {}
};

pkg_grouppolicy *pkg_grouppolicy_dep_factory::instantiate(pkg_signal *_sig,
							  desc_signal *_desc_sig)
{
  return new pkg_grouppolicy_dep(_sig, _desc_sig);
}

vs_treeitem *pkg_dep_screen::setup_new_root(const pkgCache::PkgIterator &pkg,
					    const pkgCache::VerIterator &ver)
{
  pkg_item_with_generic_subtree *newtree=new pkg_item_with_generic_subtree(pkg, get_sig(), true);

  if(!reverse)
    setup_package_deps<pkg_item_with_generic_subtree>(pkg, ver, newtree, get_sig(), false);
  else
    {
      setup_package_deps<pkg_item_with_generic_subtree>(pkg, ver, newtree, get_sig(), true);
    }

  return newtree;
}

pkg_dep_screen::pkg_dep_screen(const pkgCache::PkgIterator &pkg,
			       const pkgCache::VerIterator &ver,
			       bool _reverse)
  :apt_info_tree(pkg.Name(), ver.end()?"":ver.VerStr()), reverse(_reverse)
{
  set_root(setup_new_root(pkg, ver), true);
}

typedef hash_map<string, pkg_subtree *> tree_map;

template<class tree_type>
void setup_package_deps(const pkgCache::PkgIterator &pkg,
			const pkgCache::VerIterator &ver,
			tree_type *tree,
			pkg_signal *sig,
			bool reverse)
{
  tree_map subtrees;

  if(ver.end() && !reverse)
    return;

  pkgCache::DepIterator D=reverse?pkg.RevDependsList():ver.DependsList();
  while(!D.end())
    {
      if(reverse)
	{
	  if((ver.end() && (D->CompareOp&0x0F)!=pkgCache::Dep::NoOp) ||
	     (!ver.end() &&
	      !_system->VS->CheckDep(ver.VerStr(), D->CompareOp, D.TargetVer())))

	    {
	      D++;
	      continue;
	    }
	}

      pkg_subtree *subtree;

      tree_map::iterator found=subtrees.find(D.DepType());
      if(found==subtrees.end())
	{
	  subtree=new pkg_subtree(transcode(D.DepType()),
				  true);
	  subtrees[D.DepType()]=subtree;
	  tree->add_child(subtree);
	}
      else
	subtree=found->second;

      if(!reverse)
	subtree->add_child(new pkg_depitem(D, sig));
      else
	{
	  subtree->add_child(new pkg_ver_item(D.ParentVer(), sig, true));
	  D++;
	}
    }

  if(reverse && !ver.end())
    // I'm starting to think the reverse-depends stuff should be split into
    // another function..this is getting too nasty
    for(pkgCache::PrvIterator i=ver.ProvidesList(); !i.end(); i++)
      {
	for(pkgCache::DepIterator D=i.ParentPkg().RevDependsList(); !D.end(); D++)
	  {
	    if(_system->VS->CheckDep(i.ProvideVersion(), D->CompareOp, D.TargetVer()))
	      {
		pkg_subtree *subtree;
		tree_map::iterator found=subtrees.find(D.DepType());
		if(found==subtrees.end())
		  {
		    subtree=new pkg_subtree(transcode(D.DepType()),
					    true);
		    subtrees[D.DepType()]=subtree;
		    tree->add_child(subtree);
		  }
		else
		  subtree=found->second;

		subtree->add_child(new pkg_ver_item(D.ParentVer(), sig, true));
	      }
	  }
      }

  pkg_sortpolicy *policy = pkg_sortpolicy_name(pkg_sortpolicy_ver(NULL, false), false);
  pkg_sortpolicy_wrapper sorter(policy);
  for(tree_map::const_iterator i = subtrees.begin();
      i != subtrees.end(); ++i)
    i->second->sort(sorter);
  delete policy;
}

template void setup_package_deps<pkg_subtree>(const pkgCache::PkgIterator &pkg,
					      const pkgCache::VerIterator &ver,
					      pkg_subtree *tree,
					      pkg_signal *sig,
					      bool reverse);
