// Description								/*{{{*/
// $Id: progress.cc,v 1.5 2003/06/03 03:03:23 mvo Exp $
/* ######################################################################

   Progress - Wrapper for the progress related functions

   ##################################################################### */
#include <Python.h>
#include <iostream>
#include <sys/types.h>
#include <sys/wait.h>
#include <map>
#include <utility>
#include <apt-pkg/acquire-item.h>
#include <apt-pkg/acquire-worker.h>
#include <apt-pkg/install-progress.h>
#include "progress.h"
#include "generic.h"
#include "apt_pkgmodule.h"

/**
 * Set an attribute on an object, after creating the value with
 * Py_BuildValue(fmt, arg). Afterwards, decrease its refcount and return
 * whether setting the attribute was successful.
 */
template<class T>
inline bool setattr(PyObject *object, const char *attr, const char *fmt, T arg)
{
    if (!object)
        return false;
    PyObject *value = Py_BuildValue(fmt, arg);
    if (value == NULL)
       return false;

    int result = PyObject_SetAttrString(object, attr, value);
    Py_DECREF(value);
    return result != -1;
}

inline PyObject *TUPLEIZE(PyObject *op) {
    PyObject *ret = Py_BuildValue("(O)", op);
    Py_DECREF(op);
    return ret;
}

// generic
bool PyCallbackObj::RunSimpleCallback(const char* method_name,
				      PyObject *arglist,
				      PyObject **res)
{
   if(callbackInst == 0) {
      Py_XDECREF(arglist);
      return false;
   }

   PyObject *method = PyObject_GetAttrString(callbackInst,(char*) method_name);
   if(method == NULL) {
      Py_XDECREF(arglist);
      if (res) {
	 Py_INCREF(Py_None);
	 *res = Py_None;
      }
      return false;
   }

   PyObject *result = PyObject_CallObject(method, arglist);
   Py_XDECREF(arglist);

   if(result == NULL) {
      // exception happend
      std::cerr << "Error in function " << method_name << std::endl;
      PyErr_Print();
      PyErr_Clear();

      return false;
   }
   if(res != NULL)
      *res = result;
   else
      Py_XDECREF(result);
   Py_XDECREF(method);

   return true;
}


// OpProgress interface
void PyOpProgress::Update()
{
   // Build up the argument list...
   if(!CheckChange(0.7))
      return;

   setattr(callbackInst, "op", "s", Op.c_str());
   setattr(callbackInst, "subop", "s", SubOp.c_str());
   setattr(callbackInst, "major_change", "b", MajorChange);
   setattr(callbackInst, "percent", "N", MkPyNumber(Percent));
   RunSimpleCallback("update");
}

void PyOpProgress::Done()
{
   RunSimpleCallback("done");
}



// fetcher interface



// apt interface

PyObject *PyFetchProgress::GetDesc(pkgAcquire::ItemDesc *item) {
    if (!pyAcquire && item->Owner && item->Owner->GetOwner()) {
        pyAcquire = PyAcquire_FromCpp(item->Owner->GetOwner(), false, NULL);
    }
    PyObject *pyItem = PyAcquireItem_FromCpp(item->Owner, false, pyAcquire);
    PyObject *pyDesc = PyAcquireItemDesc_FromCpp(item, false, pyItem);
    Py_DECREF(pyItem);
    return pyDesc;
}

bool PyFetchProgress::MediaChange(std::string Media, std::string Drive)
{
   PyCbObj_END_ALLOW_THREADS
   PyObject *arglist = Py_BuildValue("(ss)", Media.c_str(), Drive.c_str());
   PyObject *result = NULL;

   if(PyObject_HasAttrString(callbackInst, "mediaChange"))
      RunSimpleCallback("mediaChange", arglist, &result);
   else
      RunSimpleCallback("media_change", arglist, &result);

   bool res = true;
   if(!PyArg_Parse(result, "b", &res)) {
      // no return value or None, assume false
      PyCbObj_BEGIN_ALLOW_THREADS
      return false;
   }

   PyCbObj_BEGIN_ALLOW_THREADS
   return res;
}

void PyFetchProgress::UpdateStatus(pkgAcquire::ItemDesc &Itm, int status)
{
   // Added object file size and object partial size to
   // parameters that are passed to updateStatus.
   // -- Stephan
   PyObject *arglist = Py_BuildValue("(sssNNN)", Itm.URI.c_str(),
				     Itm.Description.c_str(),
				     Itm.ShortDesc.c_str(),
				     MkPyNumber(status),
				     MkPyNumber(Itm.Owner->FileSize),
				     MkPyNumber(Itm.Owner->PartialSize));

   RunSimpleCallback("update_status_full", arglist);

   // legacy version of the interface

   arglist = Py_BuildValue("(sssN)", Itm.URI.c_str(), Itm.Description.c_str(),
                           Itm.ShortDesc.c_str(), MkPyNumber(status));

   if(PyObject_HasAttrString(callbackInst, "updateStatus"))
      RunSimpleCallback("updateStatus", arglist);
   else
      RunSimpleCallback("update_status", arglist);
}

void PyFetchProgress::IMSHit(pkgAcquire::ItemDesc &Itm)
{
   PyCbObj_END_ALLOW_THREADS
   if (PyObject_HasAttrString(callbackInst, "ims_hit"))
       RunSimpleCallback("ims_hit", TUPLEIZE(GetDesc(&Itm)));
   else
       UpdateStatus(Itm, DLHit);
   PyCbObj_BEGIN_ALLOW_THREADS
}

void PyFetchProgress::Fetch(pkgAcquire::ItemDesc &Itm)
{
   PyCbObj_END_ALLOW_THREADS
   if (PyObject_HasAttrString(callbackInst, "fetch"))
       RunSimpleCallback("fetch", TUPLEIZE(GetDesc(&Itm)));
   else
       UpdateStatus(Itm, DLQueued);
   PyCbObj_BEGIN_ALLOW_THREADS
}

void PyFetchProgress::Done(pkgAcquire::ItemDesc &Itm)
{
   PyCbObj_END_ALLOW_THREADS
   if (PyObject_HasAttrString(callbackInst, "done"))
       RunSimpleCallback("done", TUPLEIZE(GetDesc(&Itm)));
   else
       UpdateStatus(Itm, DLDone);
   PyCbObj_BEGIN_ALLOW_THREADS
}

void PyFetchProgress::Fail(pkgAcquire::ItemDesc &Itm)
{
   PyCbObj_END_ALLOW_THREADS
   if (PyObject_HasAttrString(callbackInst, "fail")) {
       RunSimpleCallback("fail", TUPLEIZE(GetDesc(&Itm)));
       PyCbObj_BEGIN_ALLOW_THREADS
       return;
   }

   // Ignore certain kinds of transient failures (bad code)
   if (Itm.Owner->Status == pkgAcquire::Item::StatIdle) {
      PyCbObj_BEGIN_ALLOW_THREADS
      return;
   }

   if (Itm.Owner->Status == pkgAcquire::Item::StatDone)
   {
      UpdateStatus(Itm, DLIgnored);
   }


   if (PyObject_HasAttrString(callbackInst, "fail"))
       RunSimpleCallback("fail", TUPLEIZE(GetDesc(&Itm)));
   else
       UpdateStatus(Itm, DLFailed);
   PyCbObj_BEGIN_ALLOW_THREADS
}

void PyFetchProgress::Start()
{
   pkgAcquireStatus::Start();


   RunSimpleCallback("start");
   /* After calling the start method we can safely allow
    * other Python threads to do their work for now.
    */
   PyCbObj_BEGIN_ALLOW_THREADS
}


void PyFetchProgress::Stop()
{
   /* After the stop operation occurred no other threads
    * are allowed. This is done so we have a matching
    * PyCbObj_END_ALLOW_THREADS to our previous
    * PyCbObj_BEGIN_ALLOW_THREADS (Python requires this!).
    */

   PyCbObj_END_ALLOW_THREADS
   pkgAcquireStatus::Stop();
   RunSimpleCallback("stop");
}

bool PyFetchProgress::Pulse(pkgAcquire * Owner)
{
   PyCbObj_END_ALLOW_THREADS
   pkgAcquireStatus::Pulse(Owner);

   if(callbackInst == 0) {
      PyCbObj_BEGIN_ALLOW_THREADS
      return false;
   }

   setattr(callbackInst, "last_bytes", "N", MkPyNumber(LastBytes));
   setattr(callbackInst, "current_cps", "N", MkPyNumber(CurrentCPS));
   setattr(callbackInst, "current_bytes", "N", MkPyNumber(CurrentBytes));
   setattr(callbackInst, "total_bytes", "N", MkPyNumber(TotalBytes));
   setattr(callbackInst, "fetched_bytes", "N", MkPyNumber(FetchedBytes));
   setattr(callbackInst, "elapsed_time", "N", MkPyNumber(ElapsedTime));
   setattr(callbackInst, "current_items", "N", MkPyNumber(CurrentItems));
   setattr(callbackInst, "total_items", "N", MkPyNumber(TotalItems));

   // New style
   if (!PyObject_HasAttrString(callbackInst, "updateStatus")) {
      PyObject *result1;
      bool res1 = true;

      if (pyAcquire == NULL) {
         pyAcquire = PyAcquire_FromCpp(Owner, false, NULL);
      }
      Py_INCREF(pyAcquire);

      if (RunSimpleCallback("pulse", TUPLEIZE(pyAcquire) , &result1)) {
      if (result1 != NULL &&
          result1 != Py_None &&
          PyArg_Parse(result1, "b", &res1) && 
          res1 == false) {
         // the user returned a explicit false here, stop
         PyCbObj_BEGIN_ALLOW_THREADS
         return false;
      }
     }
     PyCbObj_BEGIN_ALLOW_THREADS
     return true;
   }
   return false;
}



// install progress

void PyInstallProgress::StartUpdate()
{
   RunSimpleCallback("start_update");
   PyCbObj_BEGIN_ALLOW_THREADS
}

void PyInstallProgress::UpdateInterface()
{
   PyCbObj_END_ALLOW_THREADS
   RunSimpleCallback("update_interface");
   PyCbObj_BEGIN_ALLOW_THREADS
}

void PyInstallProgress::FinishUpdate()
{
   PyCbObj_END_ALLOW_THREADS
   RunSimpleCallback("finish_update");
}

pkgPackageManager::OrderResult PyInstallProgress::Run(pkgPackageManager *pm)
{
   pkgPackageManager::OrderResult res;
   int ret;
   pid_t child_id;

#if 0 // FIXME: this needs to be merged into apt to support medium swaping
   res = pm->DoInstallPreFork();
   if (res == pkgPackageManager::Failed)
       return res;
#endif

   // support custom fork methods
   if(PyObject_HasAttrString(callbackInst, "fork")) {
      PyObject *method = PyObject_GetAttrString(callbackInst, "fork");
      PyObject *arglist = Py_BuildValue("()");
      PyObject *result = PyObject_CallObject(method, arglist);
      Py_DECREF(arglist);
      if (result == NULL) {
	 std::cerr << "fork method invalid" << std::endl;
	 PyErr_Print();
	 return pkgPackageManager::Failed;
      }
      if(!PyArg_Parse(result, "i", &child_id) ) {
	 std::cerr << "custom fork() result could not be parsed?"<< std::endl;
	 return pkgPackageManager::Failed;
      }
   } else {
      child_id = fork();
   }

   PyObject *child_o = MkPyNumber(child_id);
   PyObject_SetAttrString(callbackInst, "child_pid", child_o);
   Py_DECREF(child_o);

#if 0 // FIXME: this needs to be merged into apt to support medium swaping
   if (child_id == 0) {
      res = pm->DoInstallPostFork();
      _exit(res);
   }
#endif
   if (child_id == 0) {
      PyObject *v = PyObject_GetAttrString(callbackInst, "writefd");
      if(v) {
	 int fd = PyObject_AsFileDescriptor(v);

	 APT::Progress::PackageManagerProgressFd progress(fd);
	 res = pm->DoInstall(&progress);
      } else {
	 APT::Progress::PackageManagerProgressFd progress(-1);
	 res = pm->DoInstall(&progress);
      }
      _exit(res);
   }

   StartUpdate();


   PyCbObj_END_ALLOW_THREADS
   if(PyObject_HasAttrString(callbackInst, "waitChild") ||
      PyObject_HasAttrString(callbackInst, "wait_child")) {
      PyObject *method;
      if (PyObject_HasAttrString(callbackInst, "waitChild"))
          method = PyObject_GetAttrString(callbackInst, "waitChild");
      else
          method = PyObject_GetAttrString(callbackInst, "wait_child");
      PyObject *result = PyObject_CallObject(method, NULL);
      if (result == NULL) {
	 std::cerr << "waitChild method invalid" << std::endl;
	 PyErr_Print();
	 PyCbObj_BEGIN_ALLOW_THREADS
	 return pkgPackageManager::Failed;
      }
      if(!PyArg_Parse(result, "i", &res) ) {
	 std::cerr << "custom waitChild() result could not be parsed?"<< std::endl;
	 PyCbObj_BEGIN_ALLOW_THREADS
	 return pkgPackageManager::Failed;
      }
      PyCbObj_BEGIN_ALLOW_THREADS
   } else {
      PyCbObj_BEGIN_ALLOW_THREADS
      while (waitpid(child_id, &ret, WNOHANG) == 0) {
	 PyCbObj_END_ALLOW_THREADS
	 UpdateInterface();
         PyCbObj_BEGIN_ALLOW_THREADS
      }

      res = (pkgPackageManager::OrderResult) WEXITSTATUS(ret);
   }

   FinishUpdate();

   return res;
}


//-----------------------------------------------------------------------------
// apt-cdrom interface

void PyCdromProgress::Update(std::string text, int current)
{
   PyObject *arglist = Py_BuildValue("(si)", text.c_str(), current);
   setattr(callbackInst, "total_steps", "i", totalSteps);
   RunSimpleCallback("update", arglist);
}

bool PyCdromProgress::ChangeCdrom()
{
   PyObject *arglist = Py_BuildValue("()");
   PyObject *result = NULL;
   if (PyObject_HasAttrString(callbackInst, "changeCdrom"))
      RunSimpleCallback("changeCdrom", arglist, &result);
   else
      RunSimpleCallback("change_cdrom", arglist, &result);

   bool res = true;
   if(!PyArg_Parse(result, "b", &res))
      std::cerr << "ChangeCdrom: result could not be parsed" << std::endl;

   return res;
}


bool PyCdromProgress::AskCdromName(std::string &Name)
{
   PyObject *arglist = Py_BuildValue("()");
   const char *new_name;
   bool res;
   PyObject *result = NULL;

   // Old style: (True, name) on success, (False, name) on failure.
   if (PyObject_HasAttrString(callbackInst, "askAdromName")) {
      RunSimpleCallback("askAdromName", arglist, &result);
      if(!PyArg_Parse(result, "(bs)", &res, &new_name))
         std::cerr << "AskCdromName: result could not be parsed" << std::endl;
      // set the new name
      Name =std:: string(new_name);
      return res;
   }
   // New style: String on success, None on failure.
   else {
        RunSimpleCallback("ask_cdrom_name", arglist, &result);
        if(result == Py_None)
            return false;
        if(!PyArg_Parse(result, "s", &new_name))
            std::cerr << "ask_cdrom_name: result could not be parsed" << std::endl;
        else
           Name = std::string(new_name);
        return true;
  }
}
