#include "Executive.h"
#include "ExecutivePython.h"
#ifndef _PYMOL_NOPY
#include "ObjectAlignment.h"
#include "ObjectCGO.h"
#include "ObjectCallback.h"
#include "ObjectMap.h"
#include "P.h"
#include "Feedback.h"

pymol::Result<> ExecutiveLoadObject(PyMOLGlobals* G,
    const char* oname, PyObject* model, int frame, int type, int finish,
    int discrete, int quiet, int zoom)
{
  ObjectNameType valid_name = "";
  pymol::CObject *origObj = nullptr, *obj;
  OrthoLineType buf;
  buf[0] = 0;
  ExecutiveProcessObjectName(G, oname, valid_name);

  origObj = ExecutiveFindObjectByName(G, valid_name);

  /* TODO check for existing object of wrong type */

  switch (type) {
  case cLoadTypeChemPyModel: {
    if (origObj) {
      if (origObj->type != cObjectMolecule) {
        ExecutiveDelete(G, valid_name);
        origObj = nullptr;
      }
    }
    PBlock(G); /*PBlockAndUnlockAPI(); */
    obj = ObjectMoleculeLoadChemPyModel(
        G, (ObjectMolecule*) origObj, model, frame, discrete);
    PUnblock(G); /*PLockAPIAndUnblock(); */
    if (!origObj) {
      if (obj) {
        ObjectSetName(obj, valid_name);
        ExecutiveManageObject(G, obj, zoom, quiet);
        if (frame < 0)
          frame = ((ObjectMolecule*) obj)->NCSet - 1;
        sprintf(buf,
            " CmdLoad: ChemPy-model loaded into object \"%s\", state %d.\n",
            valid_name, frame + 1);
      }
    } else if (origObj) {
      if (finish)
        ExecutiveUpdateObjectSelection(G, origObj);
      if (frame < 0)
        frame = ((ObjectMolecule*) origObj)->NCSet - 1;
      sprintf(buf,
          " CmdLoad: ChemPy-model appended into object \"%s\", state %d.\n",
          valid_name, frame + 1);
    }
    break;
  }
  case cLoadTypeChemPyBrick:
    if (origObj)
      if (origObj->type != cObjectMap) {
        ExecutiveDelete(G, valid_name);
        origObj = nullptr;
      }
    PBlock(G); /*PBlockAndUnlockAPI(); */
    obj = ObjectMapLoadChemPyBrick(
        G, (ObjectMap*) origObj, model, frame, discrete, quiet);
    PUnblock(G); /*PLockAPIAndUnblock(); */
    if (!origObj) {
      if (obj) {
        ObjectSetName(obj, valid_name);
        ExecutiveManageObject(G, obj, zoom, quiet);
        sprintf(buf, " CmdLoad: chempy.brick loaded into object \"%s\"\n",
            valid_name);
      }
    } else if (origObj) {
      sprintf(buf, " CmdLoad: chempy.brick appended into object \"%s\"\n",
          valid_name);
    }
    break;
  case cLoadTypeChemPyMap:
    if (origObj)
      if (origObj->type != cObjectMap) {
        ExecutiveDelete(G, valid_name);
        origObj = nullptr;
      }
    PBlock(G); /*PBlockAndUnlockAPI(); */
    obj = ObjectMapLoadChemPyMap(
        G, (ObjectMap*) origObj, model, frame, discrete, quiet);
    PUnblock(G); /*PLockAPIAndUnblock(); */
    if (!origObj) {
      if (obj) {
        ObjectSetName(obj, valid_name);
        ExecutiveManageObject(G, obj, zoom, quiet);
        sprintf(buf, " CmdLoad: chempy.map loaded into object \"%s\"\n",
            valid_name);
      }
    } else if (origObj) {
      sprintf(buf, " CmdLoad: chempy.map appended into object \"%s\"\n",
          valid_name);
    }
    break;
  case cLoadTypeCallback:
    if (origObj)
      if (origObj->type != cObjectCallback) {
        ExecutiveDelete(G, valid_name);
        origObj = nullptr;
      }
    PBlock(G); /*PBlockAndUnlockAPI(); */
    obj = ObjectCallbackDefine(
        G, (ObjectCallback*) origObj, model, frame);
    PUnblock(G); /*PLockAPIAndUnblock(); */
    if (!origObj) {
      if (obj) {
        ObjectSetName(obj, valid_name);
        ExecutiveManageObject(G, obj, zoom, quiet);
        sprintf(buf, " CmdLoad: pymol.callback loaded into object \"%s\"\n",
            valid_name);
      }
    } else if (origObj) {
      sprintf(buf, " CmdLoad: pymol.callback appended into object \"%s\"\n",
          valid_name);
    }
    break;
  case cLoadTypeCGO:
    if (origObj)
      if (origObj->type != cObjectCGO) {
        ExecutiveDelete(G, valid_name);
        origObj = nullptr;
      }
    PBlock(G); /*PBlockAndUnlockAPI(); */
    obj = ObjectCGODefine(G, (ObjectCGO*) origObj, model, frame);
    PUnblock(G); /*PLockAPIAndUnblock(); */
    if (!origObj) {
      if (obj) {
        ObjectSetName(obj, valid_name);
        ExecutiveManageObject(G, obj, zoom, quiet);
        sprintf(buf, " CmdLoad: CGO loaded into object \"%s\"\n", valid_name);
      }
    } else if (origObj) {
      sprintf(buf, " CmdLoad: CGO appended into object \"%s\"\n", valid_name);
    }
    break;
  }
  if (origObj && !quiet) {
    PRINTFB(G, FB_Executive, FB_Actions)
    "%s", buf ENDFB(G);
    OrthoRestorePrompt(G);
  }
  return {};
}

pymol::Result<> ExecutiveSetRawAlignment(PyMOLGlobals* G,
    pymol::zstring_view alnname, PyObject* raw, pymol::zstring_view guidename,
    int state, int quiet)
{

  ObjectMolecule* guide = nullptr;
  if (!guidename.empty()) {
    guide = ExecutiveFindObject<ObjectMolecule>(G, guidename.c_str());
  }

  if(!PyList_Check(raw)) {
    return pymol::make_error("alignment must be list");
  }

  auto n_cols = PyList_Size(raw);

  pymol::vla<int> align_vla(n_cols * 3);
  size_t vla_offset = 0;

  for(size_t c = 0; c < n_cols; ++c) {
    PyObject * col = PyList_GetItem(raw, c);

    if(!PyList_Check(col)) {
      return pymol::make_error("columns must be list");
    }

    auto n_idx = PyList_Size(col);

    for(size_t i = 0; i < n_idx; ++i) {
      const char * model;
      int index;

      PyObject * idx = PyList_GetItem(col, i);

      if(!PyArg_ParseTuple(idx, "si", &model, &index)) {
        return pymol::make_error("indices must be (str, int)");
      }

      auto mol = ExecutiveFindObject<ObjectMolecule>(G, model);

      if(!mol) {
        return pymol::make_error("object ", model, " not found");
      }

      if (!guide) {
        guide = mol;
      }

      if (index < 1 || mol->NAtom < index) {
        return pymol::make_error("index ('", model, ", ", index, ") out of range");
      }

      auto uid = AtomInfoCheckUniqueID(G, mol->AtomInfo + index - 1);
      *(align_vla.check(vla_offset++)) = uid;
    }

    *(align_vla.check(vla_offset++)) = 0;
  }

  align_vla.resize(vla_offset);

  // does alignment object already exist?
  auto cobj = ExecutiveFindObjectByName(G, alnname.c_str());
  if (cobj && cobj->type != cObjectAlignment) {
    ExecutiveDelete(G, cobj->Name);
    cobj = nullptr;
  }

  // create alignment object
  cobj = ObjectAlignmentDefine(G, (ObjectAlignment*) cobj,
      align_vla, state, true, guide, nullptr);

  // manage alignment object
  ObjectSetName(cobj, alnname.c_str());
  ExecutiveManageObject(G, cobj, 0, quiet);
  SceneInvalidate(G);

  // make available as selection FIXME find better solution
  cobj->update();
  return {};
}

pymol::Result<float> ExecutiveFitPairs(
    PyMOLGlobals* G, PyObject* list, int quiet)
{
  auto ln = PyObject_Length(list);
  if (!ln) {
    return pymol::make_error("No selections provided");
  }
  if (ln & 0x1) {
    return pymol::make_error(
        G, "FitPairs", "must supply an even number of selections.");
  }

  std::vector<SelectorTmp> word(ln);

  int a = 0;
  while (a < ln) {
    unique_PyObject_ptr item(PySequence_GetItem(list, a));
    auto tmp = SelectorTmp::make(G, PyString_AsString(item.get()));
    p_return_if_error(tmp);
    word[a] = std::move(tmp.result());
    a++;
  }
  return ExecutiveRMSPairs(G, word, 2, quiet);
}

/**
 * Implementation of ExecutiveGetRawAlignment
 * @param alnobj alignment object
 * @param active_only only consider active alignments
 * @param state state of alignment object
 * @return a list of lists of (object, index) tuples containing the
 * raw per-atom alignment relationships
 */
static pymol::Result<PyObject*> ExecutiveGetRawAlignmentImpl(
    PyMOLGlobals* G, const ObjectAlignment* alnobj, bool active_only, int state)
{
  if (state >= alnobj->getNFrame()) {
    return pymol::make_error(
        "Index Error: state ", state, " >= NState ", alnobj->getNFrame());
  }

  const auto& vla = alnobj->State[state].alignVLA;

  if (!vla) {
    return pymol::make_error("state ", state, " not valid");
  }

  auto hide_underscore = SettingGet<bool>(G, cSetting_hide_underscore_names);
  const auto vla_len = VLAGetSize(vla);

  PyObject* raw = PyList_New(0);

  for (size_t i = 0; i < vla_len; ++i) {
    PyObject* col = PyList_New(0);

    for (int id; (id = vla[i]); ++i) {
      auto eoo = ExecutiveUniqueIDAtomDictGet(G, id);
      if (eoo && (!active_only || eoo->obj->Enabled) &&
          (!hide_underscore || eoo->obj->Name[0] != '_')) {
        PyObject* idx = Py_BuildValue("si", eoo->obj->Name, eoo->atm + 1);
        PyList_Append(col, idx);
        Py_DECREF(idx);
      }
    }

    if (PyList_Size(col) > 0) {
      PyList_Append(raw, col);
    }
    Py_DECREF(col);
  }
  return raw;
}

pymol::Result<PyObject*> ExecutiveGetRawAlignment(PyMOLGlobals* G,
    pymol::null_safe_zstring_view name, bool active_only, int state)
{
  if (name.empty()) {
    name = ExecutiveGetActiveAlignment(G);
  }
  if (!name.empty()) {
    if (auto obj = ExecutiveFindObject<ObjectAlignment>(G, name)) {
      return ExecutiveGetRawAlignmentImpl(G, obj, active_only, state);
    }
  }
  return pymol::make_error("No such alignment: ", name);
}

#endif
