// -*- mode: cpp; mode: fold -*-
// Description								/*{{{*/
/* ######################################################################
   Set of methods to help writing and reading everything needed for EDSP
   ##################################################################### */
									/*}}}*/
// Include Files							/*{{{*/
#include <apt-pkg/edsp.h>
#include <apt-pkg/error.h>
#include <apt-pkg/configuration.h>
#include <apt-pkg/version.h>
#include <apt-pkg/policy.h>
#include <apt-pkg/tagfile.h>

#include <apti18n.h>
#include <limits>

#include <stdio.h>
									/*}}}*/

// EDSP::WriteScenario - to the given file descriptor			/*{{{*/
bool EDSP::WriteScenario(pkgDepCache &Cache, FILE* output)
{
   // we could use pkgCache::DepType and ::Priority, but these would be lokalized strings…
   const char * const PrioMap[] = {0, "important", "required", "standard",
				   "optional", "extra"};
   const char * const DepMap[] = {"", "Depends", "PreDepends", "Suggests",
				  "Recommends" , "Conflicts", "Replaces",
				  "Obsoletes", "Breaks", "Enhances"};

   for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
   {
      for (pkgCache::VerIterator Ver = Pkg.VersionList(); Ver.end() == false; ++Ver)
      {
	 fprintf(output, "Package: %s\n", Pkg.Name());
	 fprintf(output, "Architecture: %s\n", Ver.Arch());
	 fprintf(output, "Version: %s\n", Ver.VerStr());
	 if (Pkg.CurrentVer() == Ver)
	    fprintf(output, "Installed: yes\n");
	 if (Pkg->SelectedState == pkgCache::State::Hold)
	    fprintf(output, "Hold: yes\n");
	 fprintf(output, "APT-ID: %d\n", Ver->ID);
	 fprintf(output, "Priority: %s\n", PrioMap[Ver->Priority]);
	 if ((Pkg->Flags & pkgCache::Flag::Essential) == pkgCache::Flag::Essential)
	    fprintf(output, "Essential: yes\n");
	 fprintf(output, "Section: %s\n", Ver.Section());
	 if (Ver->MultiArch == pkgCache::Version::Allowed || Ver->MultiArch == pkgCache::Version::AllAllowed)
	    fprintf(output, "Multi-Arch: allowed\n");
	 else if (Ver->MultiArch == pkgCache::Version::Foreign || Ver->MultiArch == pkgCache::Version::AllForeign)
	    fprintf(output, "Multi-Arch: foreign\n");
	 else if (Ver->MultiArch == pkgCache::Version::Same)
	    fprintf(output, "Multi-Arch: same\n");
	 signed short Pin = std::numeric_limits<signed short>::min();
	 for (pkgCache::VerFileIterator File = Ver.FileList(); File.end() == false; ++File) {
	    signed short const p = Cache.GetPolicy().GetPriority(File.File());
	    if (Pin < p)
	       Pin = p;
	 }
	 fprintf(output, "APT-Pin: %d\n", Pin);
	 if (Cache.GetCandidateVer(Pkg) == Ver)
	    fprintf(output, "APT-Candidate: yes\n");
	 if ((Cache[Pkg].Flags & pkgCache::Flag::Auto) == pkgCache::Flag::Auto)
	    fprintf(output, "APT-Automatic: yes\n");
	 std::string dependencies[pkgCache::Dep::Enhances + 1];
	 bool orGroup = false;
	 for (pkgCache::DepIterator Dep = Ver.DependsList(); Dep.end() == false; ++Dep)
	 {
	    // Ignore implicit dependencies for multiarch here
	    if (strcmp(Pkg.Arch(), Dep.TargetPkg().Arch()) != 0)
	       continue;
	    if (orGroup == false)
	       dependencies[Dep->Type].append(", ");
	    dependencies[Dep->Type].append(Dep.TargetPkg().Name());
	    if (Dep->Version != 0)
	       dependencies[Dep->Type].append(" (").append(pkgCache::CompTypeDeb(Dep->CompareOp)).append(" ").append(Dep.TargetVer()).append(")");
	    if ((Dep->CompareOp & pkgCache::Dep::Or) == pkgCache::Dep::Or)
	    {
	       dependencies[Dep->Type].append(" | ");
	       orGroup = true;
	    }
	    else
	       orGroup = false;
	 }
	 for (int i = 1; i < pkgCache::Dep::Enhances + 1; ++i)
	    if (dependencies[i].empty() == false)
	       fprintf(output, "%s: %s\n", DepMap[i], dependencies[i].c_str()+2);
	 string provides;
	 for (pkgCache::PrvIterator Prv = Ver.ProvidesList(); Prv.end() == false; ++Prv)
	 {
	    // Ignore implicit provides for multiarch here
	    if (strcmp(Pkg.Arch(), Prv.ParentPkg().Arch()) != 0 || strcmp(Pkg.Name(),Prv.Name()) == 0)
	       continue;
	    provides.append(", ").append(Prv.Name());
	 }
	 if (provides.empty() == false)
	    fprintf(output, "Provides: %s\n", provides.c_str()+2);


	 fprintf(output, "\n");
      }
   }
   return true;
}
									/*}}}*/
// EDSP::WriteRequest - to the given file descriptor			/*{{{*/
bool EDSP::WriteRequest(pkgDepCache &Cache, FILE* output, bool const Upgrade,
			bool const DistUpgrade, bool const AutoRemove)
{
   string del, inst;
   for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
   {
      string* req;
      if (Cache[Pkg].Delete() == true)
	 req = &del;
      else if (Cache[Pkg].NewInstall() == true || Cache[Pkg].Upgrade() == true)
	 req = &inst;
      else
	 continue;
      req->append(" ").append(Pkg.FullName());
   }
   fprintf(output, "Request: EDSP 0.2\n");
   if (del.empty() == false)
      fprintf(output, "Remove: %s\n", del.c_str()+1);
   if (inst.empty() == false)
      fprintf(output, "Install: %s\n", inst.c_str()+1);
   if (Upgrade == true)
      fprintf(output, "Upgrade: yes\n");
   if (DistUpgrade == true)
      fprintf(output, "Dist-Upgrade: yes\n");
   if (AutoRemove == true)
      fprintf(output, "Autoremove: yes\n");
   if (_config->FindB("APT::Solver::Strict-Pinning", true) == false)
      fprintf(output, "Strict-Pinning: no\n");
   string solverpref("APT::Solver::");
   solverpref.append(_config->Find("APT::Solver::Name", "internal")).append("::Preferences");
   if (_config->Exists(solverpref) == true)
      fprintf(output, "Preferences: %s\n", _config->Find(solverpref,"").c_str());
   fprintf(output, "\n");

   return true;
}
									/*}}}*/
// EDSP::ReadResponse - from the given file descriptor			/*{{{*/
bool EDSP::ReadResponse(int const input, pkgDepCache &Cache) {
	FileFd in;
	in.OpenDescriptor(input, FileFd::ReadOnly);
	pkgTagFile response(&in);
	pkgTagSection section;

	/* We build an map id to mmap offset here
	   In theory we could use the offset as ID, but then VersionCount
	   couldn't be used to create other versionmappings anymore and it
	   would be too easy for a (buggy) solver to segfault APT… */
	unsigned long long const VersionCount = Cache.Head().VersionCount;
	unsigned long VerIdx[VersionCount];
	for (pkgCache::PkgIterator P = Cache.PkgBegin(); P.end() == false; ++P)
		for (pkgCache::VerIterator V = P.VersionList(); V.end() == false; ++V)
			VerIdx[V->ID] = V.Index();

	while (response.Step(section) == true) {
		std::string type;
		if (section.Exists("Install") == true)
			type = "Install";
		else if (section.Exists("Remove") == true)
			type = "Remove";
		//FIXME: handle progress
		else
			continue;

		size_t const id = section.FindULL(type.c_str(), VersionCount);
		if (id == VersionCount) {
			_error->Warning("Unable to parse %s request with id value '%s'!", type.c_str(), section.FindS(type.c_str()).c_str());
			continue;
		} else if (id > Cache.Head().VersionCount) {
			_error->Warning("ID value '%s' in %s request stanza is to high to refer to a known version!", section.FindS(type.c_str()).c_str(), type.c_str());
			continue;
		}

		pkgCache::VerIterator Ver(Cache.GetCache(), Cache.GetCache().VerP + VerIdx[id]);
		Cache.SetCandidateVersion(Ver);
		if (type == "Install")
			Cache.MarkInstall(Ver.ParentPkg(), false, false);
		else if (type == "Remove")
			Cache.MarkDelete(Ver.ParentPkg(), false);
	}
	return true;
}
									/*}}}*/
// EDSP::ReadLine - first line from the given file descriptor		/*{{{*/
// ---------------------------------------------------------------------
/* Little helper method to read a complete line into a string. Similar to
   fgets but we need to use the low-level read() here as otherwise the
   listparser will be confused later on as mixing of fgets and read isn't
   a supported action according to the manpages and results are undefined */
bool EDSP::ReadLine(int const input, std::string &line) {
	char one;
	ssize_t data = 0;
	line.erase();
	line.reserve(100);
	while ((data = read(input, &one, sizeof(one))) != -1) {
		if (data != 1)
			continue;
		if (one == '\n')
			return true;
		if (one == '\r')
			continue;
		if (line.empty() == true && isblank(one) != 0)
			continue;
		line += one;
	}
	return false;
}
									/*}}}*/
// EDSP::StringToBool - convert yes/no to bool				/*{{{*/
// ---------------------------------------------------------------------
/* we are not as lazy as we are in the global StringToBool as we really
   only accept yes/no here - but we will ignore leading spaces */
bool EDSP::StringToBool(char const *answer, bool const defValue) {
   for (; isspace(*answer) != 0; ++answer);
   if (strncasecmp(answer, "yes", 3) == 0)
      return true;
   else if (strncasecmp(answer, "no", 2) == 0)
      return false;
   else
      _error->Warning("Value '%s' is not a boolean 'yes' or 'no'!", answer);
   return defValue;
}
									/*}}}*/
// EDSP::ReadRequest - first stanza from the given file descriptor	/*{{{*/
bool EDSP::ReadRequest(int const input, std::list<std::string> &install,
			std::list<std::string> &remove, bool &upgrade,
			bool &distUpgrade, bool &autoRemove)
{
   install.clear();
   remove.clear();
   upgrade = false;
   distUpgrade = false;
   autoRemove = false;
   std::string line;
   while (ReadLine(input, line) == true)
   {
      // Skip empty lines before request
      if (line.empty() == true)
	 continue;
      // The first Tag must be a request, so search for it
      if (line.compare(0, 8, "Request:") != 0)
	 continue;

      while (ReadLine(input, line) == true)
      {
	 // empty lines are the end of the request
	 if (line.empty() == true)
	    return true;

	 std::list<std::string> *request = NULL;
	 if (line.compare(0, 8, "Install:") == 0)
	 {
	    line.erase(0, 8);
	    request = &install;
	 }
	 else if (line.compare(0, 7, "Remove:") == 0)
	 {
	    line.erase(0, 7);
	    request = &remove;
	 }
	 else if (line.compare(0, 8, "Upgrade:") == 0)
	    upgrade = EDSP::StringToBool(line.c_str() + 9, false);
	 else if (line.compare(0, 13, "Dist-Upgrade:") == 0)
	    distUpgrade = EDSP::StringToBool(line.c_str() + 14, false);
	 else if (line.compare(0, 11, "Autoremove:") == 0)
	    autoRemove = EDSP::StringToBool(line.c_str() + 12, false);
	 else
	    _error->Warning("Unknown line in EDSP Request stanza: %s", line.c_str());

	 if (request == NULL)
	    continue;
	 size_t end = line.length();
	 do {
	    size_t begin = line.rfind(' ');
	    if (begin == std::string::npos)
	    {
	       request->push_back(line.substr(0, end));
	       break;
	    }
	    else if (begin < end)
	       request->push_back(line.substr(begin + 1, end));
	    line.erase(begin);
	    end = line.find_last_not_of(' ');
	 } while (end != std::string::npos);
      }
   }
   return false;
}
									/*}}}*/
// EDSP::ApplyRequest - first stanza from the given file descriptor	/*{{{*/
bool EDSP::ApplyRequest(std::list<std::string> const &install,
			 std::list<std::string> const &remove,
			 pkgDepCache &Cache)
{
	for (std::list<std::string>::const_iterator i = install.begin();
	     i != install.end(); ++i)
		Cache.MarkInstall(Cache.FindPkg(*i), false);

	for (std::list<std::string>::const_iterator i = remove.begin();
	     i != remove.end(); ++i)
		Cache.MarkDelete(Cache.FindPkg(*i));
	return true;
}
									/*}}}*/
// EDSP::WriteSolution - to the given file descriptor			/*{{{*/
bool EDSP::WriteSolution(pkgDepCache &Cache, FILE* output)
{
   bool const Debug = _config->FindB("Debug::EDSP::WriteSolution", false);
   for (pkgCache::PkgIterator Pkg = Cache.PkgBegin(); Pkg.end() == false; ++Pkg)
   {
      if (Cache[Pkg].Delete() == true)
	 fprintf(output, "Remove: %d\n", Cache.GetCandidateVer(Pkg)->ID);
      else if (Cache[Pkg].NewInstall() == true || Cache[Pkg].Upgrade() == true)
	 fprintf(output, "Install: %d\n", Cache.GetCandidateVer(Pkg)->ID);
      else
	 continue;
      if (Debug == true)
	 fprintf(output, "Package: %s\nVersion: %s\n", Pkg.FullName().c_str(), Cache.GetCandidateVer(Pkg).VerStr());
      fprintf(output, "\n");
   }

   return true;
}
									/*}}}*/
bool EDSP::WriteError(std::string const &message, FILE* output) { return false; }