/** -*- Mode: C++; tab-width: 4 -*-
 * vim: sw=4 ts=4:
 *
 * Gnome Apt package cache handling class
 *
 * 	(C) 1998 Havoc Pennington <hp@pobox.com>
 * 	    2002-2004 Filip Van Raemdonck <mechanix@debian.org>
 *
 * This file is based on apt-get, copyright someone else.
 *
 * 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; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 *
 * 	$Id$
 *
 **/

using namespace std;

#include <errno.h>
#include <signal.h>
#include <apt-pkg/acquire-item.h>
#include <apt-pkg/algorithms.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/error.h>
#include <apt-pkg/pkgcachegen.h>
#include <apt-pkg/pkgsystem.h>
#include <apt-pkg/policy.h>
#include <apt-pkg/sourcelist.h>
#include <glib.h>

#include "cache.h"
#include "gadefs.h"

#ifndef G_STRLOC
#warning G_STRLOC
#define G_STRLOC ""
#else
#ifndef __PRETTY_FUNCTION__
#undef G_STRLOC
#define G_STRLOC      __FILE__ ":" G_STRINGIFY (__LINE__)
#endif
#endif

static bool uselock = true;

GAptCacheFile*
gnome_apt_cache_file_init (OpProgress* prog) {
	GAptCacheFile* cf = new GAptCacheFile;

	/* For now, never use locking when running as non-root. */
	if (geteuid() > 0) {
		uselock = false;
		_config->Set ("Debug::NoLocking", true);
	} else if (_config->FindB("Debug::NoLocking", false)) {
		uselock = false;
	}
	if (!cf->Open (prog) || !cf->cache()) {
		if (_error->PendingError()) {
			_error->DumpErrors();
		}
		_error->Error (_("Fatal error opening the package cache file which "
		      "describes the available package lists."));
		delete cf; cf = 0;
	}

	return cf;
}

GAptCache::GAptCache (pkgCache* pCache, pkgPolicy* pPolicy) :
      pkgDepCache (pCache, pPolicy), package_states (NULL) {
	records = new pkgRecords (*this);
	if (_error->PendingError()) {
		ga_debug ("%s pkgRecords", G_STRLOC);
		delete records; records = 0;
		return;
	}
}

GAptCache::~GAptCache (void) {
	delete []package_states;
}

void
GAptCache::set_states (set<string>& pkg_names) {
	delete []package_states;
	package_states = new aptitude_state[Head().PackageCount];

	if (pkg_names.begin() != pkg_names.end()) {
		for (pkgCache::PkgIterator i = PkgBegin(); !i.end(); i++) {
			if (pkg_names.find (i.Name()) == pkg_names.end()) {
				ga_debug ("%s is new", i.Name());
				pkg_names.insert (i.Name());
				mark_new (i, true);
			} else {
				mark_new (i, false);
			}
		}
	} else {
		for (pkgCache::PkgIterator i = PkgBegin(); !i.end(); i++) {
			pkg_names.insert (i.Name());
			mark_new (i, false);
		}
	}
}

GAptCache::PkgStatusType
GAptCache::pkgStatus (pkgCache::PkgIterator& i) {
	pkgDepCache::StateCache& state = (*this)[i];

	if (state.NowBroken() || state.InstBroken()) {
		return	StatusNowBroken;
	} else if (state.Status == 2) {
		return StatusNotInstalled;
	} else if (state.Upgradable()) {
		pkgCache::VerIterator cand = state.CandidateVerIter (*this);
		if (!cand.end()) {
			return StatusOutdated;
		}
	}

	pkgCache::VerIterator ver = (&i)->CurrentVer();
	if (!ver.end()) {
		return StatusUptodate;
	}

	return  StatusTypeEnd;
}

static const gchar* status_strings[] = {
	N_("Broken"),
	N_("To be installed"), N_("To be upgraded"), N_("To be downgraded"),
	N_("To be deleted"), N_("Installed"),
	N_("Not installed"),
	N_("Kept"), N_("Held"),
	N_("Outdated"), N_("Up to date")
};

const gchar*
GAptCache::statusText (GAptCache::PkgStatusType st) {
	if (st > StatusTypeEnd) {
		g_warning ("How odd, package status goes beyond the end.");
		return "";
	}

	if (st == StatusTypeEnd) {
		return "";
	}

	if (st > (GAptCache::PkgStatusType) sizeof (status_strings)) {
		g_warning ("Status types and strings sizes differ!");
		return "";
	}

	return _(status_strings[st]);
}

#define MAGIC_NOVERSION_PRIORITY (pkgCache::State::VerPriority) 100

const gchar*
GAptCache::priorityString (pkgCache::PkgIterator& i, pkgCache::State::VerPriority* p) {
	pkgCache::State::VerPriority priority = (pkgCache::State::VerPriority) 0;
	const gchar* pri_string = NULL;

	if (!i.VersionList()) {
		/* Virtual package - where are we getting this from? */
		if (p) *p = priority;
		return NULL;
	}

	pkgCache::VerIterator vi = i.CurrentVer();

	if (!vi.end()) {
		priority = (pkgCache::State::VerPriority) vi->Priority;
		if (!priority) {
			pri_string = _("No priority available");
		} else {
			pri_string = vi.PriorityType();
		}
	} else {
		/* See if we have the info for the candidate version. */
		pkgDepCache::StateCache& state = (*this)[i];
		vi = (state.CandidateVerIter) (*this);
		/* vi = i.TargetVer(); */
		if (!vi.end()) {
			priority = (pkgCache::State::VerPriority) vi->Priority;
			if (!priority) {
				pri_string = _("No priority available");
			} else {
				pri_string = vi.PriorityType();
			}
		} else {
			priority = MAGIC_NOVERSION_PRIORITY;
			pri_string = _("No version available");
		}
	}

	if (p) *p = priority;
	return pri_string;
}

pkgRecords::Parser*
GAptCache::pkgParser (pkgCache::PkgIterator& i) {
	if (!records) return 0;

	pkgCache::VerIterator vi = i.CurrentVer();
	if (vi.end()) {
		pkgDepCache::StateCache& state = (*this)[i];
		vi = state.CandidateVerIter (*this);
	}
	if (vi.end()) return 0;

	pkgRecords::Parser& parser = records->Lookup (vi.FileList());
	return &parser;
}

void
GAptCacheFile::clear (bool nocache) {
  // It is vital to set all the view to 0 
  //  before we actually destroy the cache. 
  // This allows them to extract any info from the 
  //  previous cache that needs to be saved.
  set<CacheView*>::iterator i = views_.begin();
  while (i != views_.end())
    {
      (*i)->set_cache(0);
      ++i;
	}

	if (nocache) {
		delete cache_;
		cache_ = 0;
	}
	delete map_;
	map_ = 0;
	delete file_;
	file_ = 0;
}

// GAptCacheFile::Open - Open the cache file				/*{{{*/
// ---------------------------------------------------------------------
/* This routine generates the caches and then opens the dependency cache
   and verifies that the system is OK. */
// can call this multiple times.
bool
GAptCacheFile::Open (OpProgress* prog) {
	if (cache_ != 0) {
		clear();
	}
	if (_error->PendingError()) {
		ga_debug ("%s cache clear()", G_STRLOC);
		return false;
	}

	if (!cache_ && uselock) {
		if (!_system->Lock()) return false;
	}
	if (_error->PendingError()) return false;

  // Read the source list
  pkgSourceList List;
	if (!List.ReadMainList()) {
		ga_debug ("%s ReadMainList", G_STRLOC);
    return _error->Error(_("The list of sources could not be read."));
	}
   
   // Build all of the caches
	pkgMakeStatusCache (List, *prog, NULL, true);
	if (_error->PendingError()) {
		ga_debug ("%s pkgMakeStatusCache", G_STRLOC);
    return _error->Error(_("The package lists or status file could not be parsed or opened."));
	}
	prog->Done();

  // Open the cache file
  file_ = new FileFd(_config->FindFile("Dir::Cache::pkgcache"),FileFd::ReadOnly);
	if (_error->PendingError()) {
		ga_debug ("%s FileFd", G_STRLOC);
    delete file_;
    file_ = 0;
    return false;
  }
   
  map_ = new MMap(*file_,MMap::Public | MMap::ReadOnly);
	if (_error->PendingError()) {
		ga_debug ("%s MMap", G_STRLOC);
    delete map_;
    map_ = 0;
    return false;
  }

  pkgCache *pkgcache = new pkgCache(map_);
	if (_error->PendingError()) {
		ga_debug ("%s pkgCache", G_STRLOC);
    delete pkgcache;
    return false;
  }

	pkgPolicy* pkgpolicy = new pkgPolicy (pkgcache);
	if (_error->PendingError()) {
		ga_debug ("%s pkgPolicy", G_STRLOC);
		delete pkgpolicy;
		return false;
	}
	if (!ReadPinFile (*pkgpolicy)) {
		ga_debug ("%s pkgPolicy", G_STRLOC);
		delete pkgpolicy;
		return false;
	}

	cache_ = new GAptCache (pkgcache, pkgpolicy);
	cache_->Init (prog);
	if (_error->PendingError()) {
		ga_debug ("%s GAptCache", G_STRLOC);
		delete cache_;
		cache_ = 0;
		return false;
	}
	prog->Done();

	cache_->set_states (pkg_names);

	/* Check that the system is OK */
	if (cache_->DelCount() != 0 || cache_->InstCount() != 0) {
		ga_debug ("%s DelCount || InstCount", G_STRLOC);
		return _error->Error ("Internal Error, non-zero counts"
		      " (del count %ld, inst count %ld)",
		      cache_->DelCount(), cache_->InstCount());
	}

   // Apply corrections for half-installed packages
	if (!pkgApplyStatus (*cache_)) {
		ga_debug ("%s pkgApplyStatus", G_STRLOC);
    return false;
	}

  // All done! Cache is open...

  set<CacheView*>::iterator i = views_.begin();
  while (i != views_.end())
    {
      (*i)->set_cache(cache_);
      ++i;
    }   

	ga_debug ("%s Open() done!", G_STRLOC);
  return true;
}
									/*}}}*/

void
GAptCacheFile::MarkUpgrades()
{
  if (pkgAllUpgrade(*cache_) == false)
    {
      g_warning("Internal error, AllUpgrade broke stuff");
    }
}   

void
GAptCacheFile::Fix (void) {
	if (_error->PendingError()) {
		_error->DumpErrors();
	}

	pkgProblemResolver fixer (cache_);

  fixer.InstallProtect();
	if (!fixer.Resolve (true)) {
		_error->DumpErrors();
		_error->Error (_("Error - some problems were unresolvable.\nIf you are "
		      "using an unstable version of Debian, it is possible that one or "
		      "more needed packages are not on the server; or perhaps one or more "
		      "packages are simply broken and uninstallable"));
	}
}
