#include <XSUser/Python/xspec/PyModel.h>
#include <XSUser/Python/xspec/PyXSutils.h>

#include <XSContainer.h>
#include <XSstreams.h>
#include <XSFit/Fit/Fit.h>
#include <XSModel/Data/SpectralData.h>
#include <XSModel/GlobalContainer/DataContainer.h>
#include <XSModel/GlobalContainer/ModelContainer.h>
#include <XSModel/Model/Model.h>
#include <XSModel/Model/Component/Component.h>
#include <XSModel/Parameter/ModParam.h>
#include <XSModel/Parameter/Parameter.h>
#include <XSUser/Handler/XSinterface.h>
#include <XSUtil/Error/Error.h>
#include <XSUtil/Parse/XSparse.h>
#include <XSUtil/Utils/XSutility.h>
#include <sstream>

namespace {
   PyObject* createModelTuple(const Model*);
   PyObject* createParTuple(const Parameter*);
   PyObject* getModelComps(const Model*);

   // If handle no longer points to an existing C++ Model,
   // this will set the PyErr string and return NULL.
   Model* verifyModelHandle(PyObject* handle);
}

PyObject*
_pyXspec_createModel(PyObject *self, PyObject *args)
{
   using namespace XSContainer;

   const char *exprStr=0;
   const char *modName=0;
   size_t sourceNum=0;
   if (!PyArg_ParseTuple(args,"ssI", &exprStr, &modName, &sourceNum))
   {
      string msg("Programmer Error: Invalid args in createModel function.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   if (XSparse::isBlank(exprStr))
   {
      string msg("Error: Model definition string is required.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   StringArray rawArgs;
   rawArgs.push_back(string("model"));
   string modNameStr;
   if (XSparse::isBlank(modName))
   {
      if (sourceNum != 1)
      {
         string msg("Error: When assigning to source > 1, model must be given a name.");
         PyErr_SetString(PyExc_Exception, msg.c_str());
         return NULL;
      }
      else
         modNameStr = Model::DEFAULT();
   }
   else
   {
      modNameStr = string(modName);
      std::ostringstream oss;
      oss << sourceNum << ":" << modNameStr;
      rawArgs.push_back(oss.str());
   }
   rawArgs.push_back(string(exprStr));
   rawArgs.push_back("& /*");
   if (XSGlobal::doModel(rawArgs) < 0)
   {
      PyErr_SetString(PyExc_Exception, "Model Command Error");
      return NULL;
   }

   // The lowest dg copy is all we need.
   Model* newModel = models->lookup(modNameStr);
   if (!newModel)
   {
      string msg("Programmer Error: Cannot access new model object");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   return createModelTuple(newModel);
}  // end createModel

PyObject* _pyXspec_fluxCmd(PyObject *self, PyObject *args)
{
   int isFlux=0;
   PyObject* inList=0;
   if (!PyArg_ParseTuple(args, "iO", &isFlux, &inList))
   {
      string msg("Programmer Error: Parsing flux command");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   StringArray rawArgs;
   if (isFlux)
      rawArgs.push_back("flux");
   else
      rawArgs.push_back("lumin");
   PY_SZ_TYPE nArgs = PyList_Size(inList);
   for (PY_SZ_TYPE i=0; i<nArgs; ++i)
      rawArgs.push_back(PyString_AsString(PyList_GetItem(inList, i)));
   if (XSGlobal::doFlux(rawArgs) < 0)
   {
      PyErr_SetString(PyExc_Exception, "Flux Command Error");
      return NULL;
   }
   return Py_BuildValue("i",0);
}
// end fluxCmd

PyObject* _pyXspec_getArray(PyObject *self, PyObject *args)
{
   PyObject* handle=0;
   size_t specNum=0;
   int isFolded=0;
   if (!PyArg_ParseTuple(args, "OIi", &handle, &specNum, &isFolded))
   {
      string msg("Programmer Error: Cannot parse getFlux arguments.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   Model* mod = verifyModelHandle(handle);
   if (!mod)
      return NULL;
   PyObject* list=0;
   try
   {
      RealArray noticedFolded;
      if (isFolded)
      {
         // This throws if no folded model for specNum.
         const RealArray& folded = mod->foldedModel(specNum);
         const SpectralData* spec = XSContainer::datasets->lookup(specNum);
         if (!spec)
         {
            string msg("Programmer Error: Can't find spectrum in _getArray.");
            PyErr_SetString(PyExc_Exception, msg.c_str());
            return NULL;
         }
         // If/when SpectralData's indirectNotice array gets automatically
         // updated when channels change, won't need to do this here.
         std::valarray<size_t> indirectNotice;
         spec->buildIndirectNotice(indirectNotice);
         noticedFolded.resize(indirectNotice.size());
         noticedFolded = folded[indirectNotice];
      }
      const RealArray& array = isFolded ? noticedFolded :
                                          mod->modelFlux(specNum);
      list = PyList_New(array.size());
      for (PY_SZ_TYPE i=0; i<static_cast<PY_SZ_TYPE>(array.size()); ++i)
      {
         PyObject* val = PyFloat_FromDouble(array[i]);
         PyList_SetItem(list, i, val);
      }
   }
   catch (...)
   {
      string msg("Error: Cannot retrieve array for this model and spectrum number.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   return list;
}
// end getArray

PyObject*
_pyXspec_getComponentPars(PyObject *self, PyObject *args)
{
   const char* modName=0;
   int iComp=0;  
   PyObject* paramList=0;

   try
   {
      if (!PyArg_ParseTuple(args, "si", &modName, &iComp))
         throw YellowAlert();
      string modNameStr = XSparse::isBlank(modName) ? 
                   string("_DEFAULT") : string(modName);

      // The lowest dg copy will suffice.
      const Model* mod = XSContainer::models->lookup(modNameStr);
      if (!mod)
         throw YellowAlert();
      std::vector<Component*> modComps;
      mod->bundleComponents(modComps);
      if (iComp < 1 || iComp > (int)modComps.size())
         throw YellowAlert();
      const Component* comp = modComps[iComp-1];
      const std::vector<Parameter*>& params = comp->parameterSet();
      paramList = PyList_New(params.size());
      for (PY_SZ_TYPE i=0; i<static_cast<PY_SZ_TYPE>(params.size()); ++i)
      {
         const Parameter* par = params[i];
         const string& nameStr = par->name();
         PyObject* pyNameStr = PyString_FromString(nameStr.c_str());
         PyList_SetItem(paramList, i, pyNameStr);
      }

   }
   catch (...)
   {
      string msg("Programmer Error: Failed to locate parameters for model in getComponentPars.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }

   return paramList;
} 
// end getComponentPars

PyObject* _pyXspec_getModelFluxLuminCalc(PyObject *self, PyObject *args)
{
   PyObject* handle=0;
   int isFlux=0;
   if (!PyArg_ParseTuple(args, "Oi", &handle, &isFlux))
   {
      string msg("Programmer Error: Cannot parse getModelFluxLuminCalc arguments.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   Model* mod = verifyModelHandle(handle);
   if (!mod)
      return NULL;
   const SpectralData::FluxCalc& fluxCalc = isFlux ?
                mod->lastModelFluxCalc() : mod->lastModelLuminCalc();
   PyObject* fluxTuple = PyTuple_New(6);
   PyTuple_SetItem(fluxTuple, 0, PyFloat_FromDouble(fluxCalc.value));
   PyTuple_SetItem(fluxTuple, 1, PyFloat_FromDouble(fluxCalc.errLow));
   PyTuple_SetItem(fluxTuple, 2, PyFloat_FromDouble(fluxCalc.errHigh));
   PyTuple_SetItem(fluxTuple, 3, PyFloat_FromDouble(fluxCalc.photonValue));
   PyTuple_SetItem(fluxTuple, 4, PyFloat_FromDouble(fluxCalc.photonLow));
   PyTuple_SetItem(fluxTuple, 5, PyFloat_FromDouble(fluxCalc.photonHigh));       
   return fluxTuple;
}
// end getModelFluxLuminCalc

PyObject*
_pyXspec_getModelFromNameAndGroup(PyObject *self, PyObject *args)
{
   const char* modName=0;
   size_t groupNum=0;
   if (!PyArg_ParseTuple(args, "sI", &modName, &groupNum))
   {
      string msg("Programmer Error: Parsing getModel args.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   // We could do lookup with just one function call to ModelContainer,
   // but let's do it in 2 stages to provide better error messages
   // in case model isn't there.
   Model* mod=0;
   string modNameStr(modName);
   std::vector<Model*> modGroups = 
        XSContainer::models->lookupModelGroup(modNameStr);
   if (!modGroups.size())
   {
      string msg("Error: ");
      if (modNameStr.empty())
         msg += "Model is not currently loaded in XSPEC.";
      else
         msg += "Model with name: " + modNameStr
                + " is not currently loaded in XSPEC.";
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;      
   }
   else
   {
      if (modNameStr.empty())
         modNameStr = "_DEFAULT";
      mod = XSContainer::models->lookup(modNameStr, groupNum);
   }   
   if (!mod)
   {
      const string errName = (modNameStr == string("_DEFAULT")) ?
              string("unnamed model") : string("model name: ")+modNameStr;
      std::ostringstream oss;
      oss <<"Error: No model object exists for " << errName 
          << ", data group " << groupNum;
      PyErr_SetString(PyExc_Exception, oss.str().c_str());
      return NULL;
   }
   void* handle = static_cast<void*>(mod);
   return PyCObject_FromVoidPtr(handle,NULL);
} // end getModelFromNameAndGroup

PyObject* _pyXspec_getModelSourceAssignments(PyObject *self, PyObject *args)
{
   using namespace XSContainer;
   
   PyObject* dict=PyDict_New();
   const size_t allSources = datasets->numSourcesForSpectra();
   for (size_t i=0; i<allSources; ++i)
   {
      string modName(models->lookupModelForSource(i+1));
      if (modName.length())
      {
         if (modName == Model::DEFAULT())
            modName = "";
         const long iSource = static_cast<long>(i)+1;
         PyObject* pySourceNum = PyInt_FromLong(iSource);
         PyObject* pyModName = PyString_FromString(modName.c_str());
         PyDict_SetItem(dict, pySourceNum, pyModName);
      }
   }
   
   return dict;
}

PyObject* _pyXspec_getModelTuple(PyObject *self, PyObject *args)
{
   PyObject* handle=0;
   if (!PyArg_ParseTuple(args, "O", &handle))
   {
      string msg("Programmer Error: Cannot convert model handle.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   Model* mod = verifyModelHandle(handle);
   if (!mod)
      return NULL;
   return createModelTuple(mod);
} // end getModelTuple

PyObject*
_pyXspec_getParTuple(PyObject *self, PyObject *args)
{
   PyObject* modHandle=0;
   int iPar=0;
   PyObject* paramTuple=0;
   try
   {
      if (!PyArg_ParseTuple(args, "Oi", &modHandle, &iPar))
         throw YellowAlert();
      const Model* mod = verifyModelHandle(modHandle);
      if (!mod)
         return NULL;
      iPar += static_cast<int>(mod->parameterIndexBase());
      const Parameter* par = XSContainer::models->lookupParameter(iPar, mod->name());
      if (!par)
         throw YellowAlert();
      paramTuple = createParTuple(par);
   }
   catch (...)
   {
      std::ostringstream oss;
      oss << "Programmer Error: Cannot retrieve parameter " << iPar << " from Model object."
          << std::endl; 
      PyErr_SetString(PyExc_Exception, oss.str().c_str());
      return NULL;
   }
   return paramTuple;
} 
// end getParTuple

PyObject* _pyXspec_getSpectraForModel(PyObject *self, PyObject *args)
{
   PyObject* indexTuple=0;
   PyObject* handle=0;
   if (!PyArg_ParseTuple(args, "O", &handle))
   {
      string msg("Programmer Error: Cannot convert model handle.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   const Model* mod = verifyModelHandle(handle);
   if (!mod)
      return NULL;
   const size_t nSpecs = mod->modelFlux().size();
   if (nSpecs)
   {
      size_t i=0;
      indexTuple = PyTuple_New(nSpecs);
      ArrayContainer::const_iterator itFlux = mod->modelFlux().begin();
      ArrayContainer::const_iterator itEnd = mod->modelFlux().end();
      while (itFlux != itEnd)
      {
         const long specIdx = static_cast<long>(itFlux->first);
         PyTuple_SetItem(indexTuple, i, PyInt_FromLong(specIdx));
         ++itFlux, ++i;      
      }
   }
   return indexTuple;
}
// end getSpectraForModel

PyObject* _pyXspec_localModel(PyObject *self, PyObject *args)
{
   const char* packageName=0;
   const char* dirPath=0;
   if (!PyArg_ParseTuple(args, "ss", &packageName, &dirPath))
   {
      string errMsg("Programmer Error: Parsing localModel");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;
   }
   StringArray rawArgs(2);
   rawArgs[0] = string("lmod");
   rawArgs[1] = string(packageName);
   string dirPathStr(dirPath);
   if (dirPathStr.length())
      rawArgs.push_back(dirPathStr);
   if (XSGlobal::doLmod(rawArgs))
   {
      string errMsg("Error attempting to load local model library.");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;
   }
   return Py_BuildValue("i",0);
}
// end localModel

PyObject* _pyXspec_removeModels(PyObject *self, PyObject *args)
{
   const char* modName=0;
   if (!PyArg_ParseTuple(args, "s", &modName))
   {
      string errMsg("Programmer Error: Cannot parse removeModel");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;
   }
   const string modNameStr(modName);
   StringArray rawArgs;
   rawArgs.push_back("model");
   if (modNameStr.empty())
      rawArgs.push_back("clear");
   else
   {
      rawArgs.push_back(modNameStr);
      rawArgs.push_back("none");
   }
   if (XSGlobal::doModel(rawArgs) < 0)
   {
      PyErr_SetString(PyExc_Exception, "Model removal error.");
      return NULL;
   }

   return Py_BuildValue("i",1);
}
// end removeModels

PyObject* _pyXspec_setParBayes(PyObject *self, PyObject *args)
{
   PyObject* modHandle=0;
   int iPar=0;
   const char* type=0;
   PyObject* hyperList=0;
   if (!PyArg_ParseTuple(args, "OisO", &modHandle, &iPar, &type, &hyperList))
   {
      string msg("Programmer Error: Cannot parse setParBayes input.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   const Model* mod = verifyModelHandle(modHandle);
   if (!mod)
   {
      string msg("Error: Parameter belongs to a model no longer found in Xspec.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   iPar += static_cast<int>(mod->parameterIndexBase());
   std::ostringstream oss;
   oss << iPar;
   const string modNameStr = (mod->name() == Model::DEFAULT()) ?
                                string("") : mod->name() + string(":");
   const string parID(modNameStr + oss.str());
   const string priorType(type);
   StringArray rawArgs(3);
   rawArgs[0] = "bayes";
   rawArgs[1] = parID;
   rawArgs[2] = priorType;
   
   PY_SZ_TYPE nHyper = PyList_Size(hyperList);
   for (PY_SZ_TYPE i=0; i<nHyper; ++i)
   {
      // Borrowed reference
      PyObject* hypObj = PyList_GetItem(hyperList, i);
      rawArgs.push_back(string(PyString_AsString(hypObj)));
   }
   if (XSGlobal::doBayes(rawArgs))
   {
      string msg("Error executing bayes command");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }   
   return Py_BuildValue("i",1);
}

PyObject* _pyXspec_setParFreeze(PyObject *self, PyObject *args)
{
   PyObject* modHandle=0;
   int iPar=0;
   int input=0; 
   if (!PyArg_ParseTuple(args, "Oii", &modHandle, &iPar, &input))
   {
      string errMsg("Programmer Error: Cannot parse setParFreeze input");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;
   }
   const Model* mod = verifyModelHandle(modHandle);
   if (!mod)
      return NULL;
   iPar += static_cast<int>(mod->parameterIndexBase());
   Parameter* par = XSContainer::models->lookupParameter(iPar, mod->name());
   if (!par)
   {
      string errMsg("Programmer Error: Cannot find parameter in setParFreeze function");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;        
   }
   int changeMade=0;
   try
   {
      if (input == 1)
      {
         if (!par->isFrozen())
         {
            par->freeze();
            changeMade=1;
            XSContainer::fit->Update();
         }
      }
      else
      {
         if (par->isFrozen())
         {
            // This does nothing if not a ModParam.
            par->thaw();
            changeMade=1;
            XSContainer::fit->Update();
         }
      }
   }
   catch (...)
   {
      string errMsg("Error while attempting to change frozen/thaw status of parameter");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;   
   }    
   return Py_BuildValue("i",changeMade);
}
// end setParFreeze

PyObject* _pyXspec_setParLink(PyObject *self, PyObject *args)
{
   PyObject* modHandle=0;
   const char* input=0;
   int iPar=0;

   if (!PyArg_ParseTuple(args, "Ois", &modHandle, &iPar, &input))
   {
      string errMsg("Programmer Error: Cannot parse setParLink input");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;
   }
   const Model* mod = verifyModelHandle(modHandle);
   if (!mod)
      return NULL;
   iPar += static_cast<int>(mod->parameterIndexBase());
   // Piece together the par ID and input string into a form
   // recognized by the newpar command.
   std::ostringstream oss;
   std::ostringstream parID;
   const string inputStr(input);
   if (mod->name() != Model::DEFAULT())
      parID << mod->name() <<":";
   parID << iPar;
   if (!inputStr.length())
   {
      // Remove link (if any)
      Parameter* par = XSContainer::models->lookupParameter(iPar, mod->name());
      if (!par)
      {
         string errMsg("Programmer Error: Cannot find parameter in setParLink function");
         PyErr_SetString(PyExc_Exception, errMsg.c_str());
         return NULL;        
      }
      try
      {
         // Arg = true means "preserve current value".
         par->untie(true);
         XSContainer::fit->Update();
      }
      catch (...)
      {
         string msg("Error while attempting to unfreeze parameter ");
         msg += parID.str();
         PyErr_SetString(PyExc_Exception, msg.c_str());
         return NULL;
      }
   }
   else
   {
      oss << parID.str() << " " << inputStr;
      string newparStr(oss.str());
      const bool isRespPar = false;
      // These variables are only needed in the context of the xsNewpar handler.
      string dummy1;
      IntegerArray dummy2;
      try
      {
         XSGlobal::doNewpar(newparStr, isRespPar, dummy1, dummy2);
      }
      catch (...)
      {
         string msg("Error: Cannot set parameter ");
         msg += parID.str();
         msg += " with string: ";
         msg += input;
         PyErr_SetString(PyExc_Exception, msg.c_str());
         return NULL;
      }
   }
   return Py_BuildValue("i",1);
}
// end setParLink

PyObject* _pyXspec_setPars(PyObject *self, PyObject *args)
{
   PyObject* modHandle=0;
   PyObject* parIdxTuple=0;
   PyObject* valsTuple=0;
   
   if (!PyArg_ParseTuple(args, "OOO", &modHandle, &parIdxTuple,
                &valsTuple))
   {
      string errMsg("Programmer Error: Cannot parse setPars input");
      PyErr_SetString(PyExc_Exception, errMsg.c_str());
      return NULL;
   }
   Model* mod = verifyModelHandle(modHandle);
   if (!mod)
      return NULL;
   std::vector<Parameter*> pars;
   mod->bundleParameters(pars);
   
   const PY_SZ_TYPE nPars = PySequence_Size(parIdxTuple);
   // Save par value strings.  If ANY of the user's input causes an
   // exception, ALL pars will be reset to their original values.
   std::vector<int> parIndices;
   std::vector<string> origValues;
   for (PY_SZ_TYPE i=0; i<nPars; ++i)
   {
      const int iPar = static_cast<int>(PyLong_AsLong(PyTuple_GetItem(parIdxTuple, i)));
      // Convert from 1-based to 0-based for pars vector positioning.
      parIndices.push_back(iPar-1);
      const Parameter* par = pars[iPar-1];
      if (par->isLinked())
      {
         origValues.push_back(par->parameterSetting());
      }
      else
      {
         // Go through "tclout param" to get the values string in precisely
         // the format that can be reused as input.
         int actualIdx = iPar + mod->parameterIndexBase();
         std::ostringstream parID;
         if (mod->name() != Model::DEFAULT())
            parID << mod->name() <<":";
         parID << actualIdx;
         StringArray rawArgs(3);
         rawArgs[0] = "tclout";
         rawArgs[1] = "param";
         rawArgs[2] = parID.str();
         bool dummy=false;
         string results;
         if (XSGlobal::doTclout(rawArgs, dummy, results))
         {
            string msg("Error: Cannot retrieve original parameter value string.");
            PyErr_SetString(PyExc_Exception, msg.c_str());
            return NULL;
         }
         origValues.push_back(results);
      }
   }
   
   bool allOK=true;
   for (PY_SZ_TYPE i=0; i<nPars && allOK; ++i)
   {
      const string valString(PyString_AsString(PyTuple_GetItem(valsTuple, i)));
      Parameter* par = pars[parIndices[i]];
      try
      {
         par->modify(valString);
      }
      catch(YellowAlert&)
      {
         allOK = false;
      }
   }
   if (!allOK)
   {
      for (PY_SZ_TYPE i=0; i<nPars; ++i)
      {
         Parameter* par = pars[parIndices[i]];
         // Assume it can't throw when restoring original values.
         par->modify(origValues[i]);
      }
   }
   // Do recalculation even if !allOK and original pars were restored.
   // Their 'calculate' flags would have been set to true, so might as
   // well do a recalc to keep things consistent.  This should only be
   // a corner case anyhow.
   
   XSContainer::models->setCompute(mod->name());
   if (!mod->isActive())
   {
      // If active, this gets done in Fit Update.
      mod->calculate();
   }
   XSContainer::fit->Update();
   if (!allOK)
   {
      string msg("Error: Unable to apply new parameter settings.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   return Py_BuildValue("i",1);
}
// end setPars


PyObject* _pyXspec_showModel(PyObject *self, PyObject *args)
{
   PyObject* handle=0;
   if (!PyArg_ParseTuple(args, "O", &handle))
   {
      string msg("Programmer Error: Cannot get model object handle.");
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   const Model* mod = verifyModelHandle(handle);
   if (!mod)
      return NULL;
   mod->printHeading();
   tcout << *mod;
   mod->printMixComp();
   tcout << string(72,'_') << '\n' << std::endl;

   return Py_BuildValue("i",1);
}

PyObject* _pyXspec_showPar(PyObject *self, PyObject *args)
{
   StringArray rawArgs(2);
   rawArgs[0] = string("show");
   rawArgs[1] = string("par");
   // args(0) list can contain pieces of paramIDs including range
   // specifiers (with the commas and hyphens that implies).
   if (!PyXSutils::PyListToXSArgs(args, rawArgs))
      return NULL;
   string dummy;
   XSGlobal::doShow(rawArgs, dummy);
   return Py_BuildValue("i",1);
}


namespace {

PyObject* createModelTuple(const Model* mod)
{
   PyObject* pyVals = PyTuple_New(7);
   Model* ncMod = const_cast<Model*>(mod);
   PyObject* handle = PyCObject_FromVoidPtr(static_cast<void*>(ncMod),NULL);
   PyTuple_SetItem(pyVals, 0, handle);
   PyTuple_SetItem(pyVals, 1, getModelComps(mod));
   long dgNumber = static_cast<long>(mod->dataGroupNumber());
   long sourceNumber = static_cast<long>(mod->sourceNumber());
   long startParIdx = 1 + static_cast<long>(mod->parameterIndexBase());
   long numberOfPars = static_cast<long>(mod->numberOfParameters());
   PyTuple_SetItem(pyVals, 2, PyInt_FromLong(dgNumber));
   PyTuple_SetItem(pyVals, 3, PyInt_FromLong(sourceNumber));
   PyTuple_SetItem(pyVals, 4, PyInt_FromLong(startParIdx));
   PyTuple_SetItem(pyVals, 5, PyInt_FromLong(numberOfPars));
   PyTuple_SetItem(pyVals, 6, PyString_FromString(mod->fullExpression().c_str()));
   return pyVals;
}


PyObject* createParTuple(const Parameter* par)
{
   PyObject* pyVal = PyTuple_New(11);
   PyObject* valList = PyList_New(0);
   PyObject* modVal = PyFloat_FromDouble(par->value());
   PyList_Append(valList,modVal);
   Py_DECREF(modVal);
   double sigma=-1.0;
   string units;
   double errLow=0.0, errHigh=0.0;
   string errCmdCode;
   string priorTypeStr;
   RealArray hyperPars;
   PyObject* hyperList = PyList_New(0);
   if (const ModParam* modPar = dynamic_cast<const ModParam*>(par))
   {
      const char valTypes[]={'d','l','b','t','h'};
      const size_t nTypes=5;
      for (size_t iType=0; iType<nTypes; ++iType)
      {
         double dval = modPar->value(valTypes[iType]);
         if (valTypes[iType] == 'd')
         {
            // Xspec stores a positive delta even when par is frozen,
            // but the user should see it as negative.
            if (modPar->isFrozen() && dval > 0.0)
               dval *= -1.0;
         }
         PyObject* subVal = PyFloat_FromDouble(dval);
         PyList_Append(valList, subVal);
         Py_DECREF(subVal);
      }
      sigma = modPar->sigma();
      // Only modPars have a units string
      units = modPar->unit();

      // Only modPars can have error command output or
      // Bayesian priors.
      errLow = modPar->emn();
      errHigh = modPar->epe();
      errCmdCode = modPar->lastErrorStatus();
      switch (modPar->priorType())
      {
         case ModParam::CONS:
            priorTypeStr = "CONS";
            break;
         case ModParam::EXP:
            priorTypeStr = "EXP";
            break;
         case ModParam::JEFFREYS:
            priorTypeStr = "JEFFREYS";
            break;
         case ModParam::GAUSS:
         default:
            priorTypeStr = "GAUSS";
            break;
      }
      hyperPars.resize(modPar->hyperParam().size());
      hyperPars = modPar->hyperParam();
      for (size_t i=0; i<hyperPars.size(); ++i)
      {
         PyObject* hyperPar = PyFloat_FromDouble(hyperPars[i]);
         PyList_Append(hyperList, hyperPar);
         Py_DECREF(hyperPar);
      }
   }

   // This function "steals" the reference to valList.
   PyTuple_SetItem(pyVal, 0, valList);
   PyObject* sigmaVal = PyFloat_FromDouble(sigma);
   // Now steal a reference to sigmaVal
   PyTuple_SetItem(pyVal, 1, sigmaVal);
   PyObject* frozenVal = 0;
   if (par->isFrozen())
   {
      Py_INCREF(Py_True);
      frozenVal = Py_True;
   }
   else
   {
      Py_INCREF(Py_False);
      frozenVal = Py_False;
   }
   PyTuple_SetItem(pyVal, 2, frozenVal);

   string linkStr;
   if (par->isLinked())
      linkStr = par->parameterSetting();
   PyTuple_SetItem(pyVal, 3, PyString_FromString(linkStr.c_str()));   
   PyTuple_SetItem(pyVal, 4, PyString_FromString(units.c_str()));
   PyTuple_SetItem(pyVal, 5, PyString_FromString(par->name().c_str()));
   PyTuple_SetItem(pyVal, 6, PyFloat_FromDouble(errLow));
   PyTuple_SetItem(pyVal, 7, PyFloat_FromDouble(errHigh));
   PyTuple_SetItem(pyVal, 8, PyString_FromString(errCmdCode.c_str()));
   PyTuple_SetItem(pyVal, 9, PyString_FromString(priorTypeStr.c_str()));
   PyTuple_SetItem(pyVal, 10, hyperList);

   return pyVal;
} // end createParTuple

PyObject* getModelComps(const Model* mod)
{
   std::vector<Component*> comps;
   mod->bundleComponents(comps);
   PY_SZ_TYPE nComps = static_cast<PY_SZ_TYPE>(comps.size());
   PyObject* compList = PyList_New(nComps);
   for (PY_SZ_TYPE i=0; i<nComps; ++i)
   {
      PyObject* pyStr = PyString_FromString(comps[i]->name().c_str());
      // This function transfers ownership to the list container.
      PyList_SetItem(compList, i, pyStr);
   }
   return compList;
} // end getModelComps

Model* verifyModelHandle(PyObject* handle)
{
   using namespace XSContainer;

   Model* mod = static_cast<Model*>(PyCObject_AsVoidPtr(handle));
   bool isFound = false;
   // Unfortunately this is O(N) lookup:
   ModelMap::const_iterator itMap = models->modelSet().begin();
   ModelMap::const_iterator itMapEnd = models->modelSet().end();
   while (!isFound && itMap != itMapEnd)
   {
      if (mod == itMap->second)
         isFound = true;
      ++itMap;
   }
   if (!isFound)
   {
      string msg("Error: Python Model object reference no longer corresponds to\n");
      msg +=     "          an actual XSPEC model.";
      PyErr_SetString(PyExc_Exception, msg.c_str());
      return NULL;
   }
   return mod;
}

} // end unnamed namespace

