// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
// vim: ts=8 sw=2 smarttab
/*
 * Ceph - scalable distributed file system
 *
 * Copyright (C) 2016 John Spray <john.spray@redhat.com>
 *
 * This is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License version 2.1, as published by the Free Software
 * Foundation.  See file COPYING.
 */


#include "BaseMgrStandbyModule.h"

#include "StandbyPyModules.h"
#include "PyFormatter.h"


#define dout_context g_ceph_context
#define dout_subsys ceph_subsys_mgr

using std::string;

typedef struct {
  PyObject_HEAD
  StandbyPyModule *this_module;
} BaseMgrStandbyModule;

static PyObject *
BaseMgrStandbyModule_new(PyTypeObject *type, PyObject *args, PyObject *kwds)
{
    BaseMgrStandbyModule *self;

    self = (BaseMgrStandbyModule *)type->tp_alloc(type, 0);

    return (PyObject *)self;
}

static int
BaseMgrStandbyModule_init(BaseMgrStandbyModule *self, PyObject *args, PyObject *kwds)
{
    PyObject *this_module_capsule = nullptr;
    static const char *kwlist[] = {"this_module", NULL};

    if (! PyArg_ParseTupleAndKeywords(args, kwds, "O",
                                      const_cast<char**>(kwlist),
                                      &this_module_capsule)) {
        return -1;
    }

    self->this_module = static_cast<StandbyPyModule*>(PyCapsule_GetPointer(
        this_module_capsule, nullptr));
    ceph_assert(self->this_module);

    return 0;
}

static PyObject*
ceph_get_mgr_id(BaseMgrStandbyModule *self, PyObject *args)
{
  return PyUnicode_FromString(g_conf()->name.get_id().c_str());
}

static PyObject*
ceph_get_module_option(BaseMgrStandbyModule *self, PyObject *args)
{
  char *what = nullptr;
  char *prefix = nullptr;
  if (!PyArg_ParseTuple(args, "s|s:ceph_get_module_option", &what, &prefix)) {
    derr << "Invalid args!" << dendl;
    return nullptr;
  }
  PyThreadState *tstate = PyEval_SaveThread();
  std::string final_key;
  std::string value;
  bool found = false;
  if (prefix) {
    final_key = std::string(prefix) + "/" + what;
    found = self->this_module->get_config(final_key, &value);
  }
  if (!found) {
    final_key = what;
    found = self->this_module->get_config(final_key, &value);
  }
  PyEval_RestoreThread(tstate);
  if (found) {
    dout(10) << __func__ << " " << final_key << " found: " << value
	     << dendl;
    return self->this_module->py_module->get_typed_option_value(what, value);
  } else {
    if (prefix) {
      dout(4) << __func__ << " [" << prefix << "/]" << what << " not found "
	      << dendl;
    } else {
      dout(4) << __func__ << " " << what << " not found " << dendl;
    }
    Py_RETURN_NONE;
  }
}

static PyObject*
ceph_option_get(BaseMgrStandbyModule *self, PyObject *args)
{
  char *what = nullptr;
  if (!PyArg_ParseTuple(args, "s:ceph_option_get", &what)) {
    derr << "Invalid args!" << dendl;
    return nullptr;
  }

  std::string value;
  int r = g_conf().get_val(string(what), &value);
  if (r >= 0) {
    dout(10) << "ceph_option_get " << what << " found: " << value << dendl;
    return PyUnicode_FromString(value.c_str());
  } else {
    dout(4) << "ceph_option_get " << what << " not found " << dendl;
    Py_RETURN_NONE;
  }
}

static PyObject*
ceph_store_get(BaseMgrStandbyModule *self, PyObject *args)
{
  char *what = nullptr;
  if (!PyArg_ParseTuple(args, "s:ceph_store_get", &what)) {
    derr << "Invalid args!" << dendl;
    return nullptr;
  }

  // Drop GIL for blocking mon command execution
  PyThreadState *tstate = PyEval_SaveThread();

  std::string value;
  bool found = self->this_module->get_store(what, &value);

  PyEval_RestoreThread(tstate);

  if (found) {
    dout(10) << "ceph_store_get " << what << " found: " << value.c_str() << dendl;
    return PyUnicode_FromString(value.c_str());
  } else {
    dout(4) << "ceph_store_get " << what << " not found " << dendl;
    Py_RETURN_NONE;
  }
}

static PyObject*
ceph_get_active_uri(BaseMgrStandbyModule *self, PyObject *args)
{
  return PyUnicode_FromString(self->this_module->get_active_uri().c_str());
}

static PyObject*
ceph_log(BaseMgrStandbyModule *self, PyObject *args)
{
  char *record = nullptr;
  if (!PyArg_ParseTuple(args, "s:log", &record)) {
    return nullptr;
  }

  ceph_assert(self->this_module);

  self->this_module->log(record);

  Py_RETURN_NONE;
}

static PyObject*
ceph_standby_state_get(BaseMgrStandbyModule *self, PyObject *args)
{
  char *whatc = NULL;
  if (!PyArg_ParseTuple(args, "s:ceph_state_get", &whatc)) {
    return NULL;
  }
  std::string what(whatc);

  PyFormatter f;

  // Drop the GIL, as most of the following blocks will block on
  // a mutex -- they are all responsible for re-taking the GIL before
  // touching the PyFormatter instance or returning from the function.
  without_gil_t no_gil;

  if (what == "mgr_ips") {
    entity_addrvec_t myaddrs = self->this_module->get_myaddrs();
    with_gil_t with_gil{no_gil};
    f.open_array_section("ips");
    std::set<std::string> did;
    for (auto& i : myaddrs.v) {
      std::string ip = i.ip_only_to_str();
      if (auto [where, inserted] = did.insert(ip); inserted) {
	f.dump_string("ip", ip);
      }
    }
    f.close_section();
    return f.get();
  } else {
    derr << "Python module requested unknown data '" << what << "'" << dendl;
    with_gil_t with_gil{no_gil};
    Py_RETURN_NONE;
  }
}


PyMethodDef BaseMgrStandbyModule_methods[] = {
  {"_ceph_get", (PyCFunction)ceph_standby_state_get, METH_VARARGS,
   "Get a cluster object (standby)"},

  {"_ceph_get_mgr_id", (PyCFunction)ceph_get_mgr_id, METH_NOARGS,
   "Get the name of the Mgr daemon where we are running"},

  {"_ceph_get_module_option", (PyCFunction)ceph_get_module_option, METH_VARARGS,
   "Get a module configuration option value"},

  {"_ceph_get_option", (PyCFunction)ceph_option_get, METH_VARARGS,
   "Get a native configuration option value"},

  {"_ceph_get_store", (PyCFunction)ceph_store_get, METH_VARARGS,
   "Get a KV store value"},

  {"_ceph_get_active_uri", (PyCFunction)ceph_get_active_uri, METH_NOARGS,
   "Get the URI of the active instance of this module, if any"},

  {"_ceph_log", (PyCFunction)ceph_log, METH_VARARGS,
   "Emit a log message"},

  {NULL, NULL, 0, NULL}
};

PyTypeObject BaseMgrStandbyModuleType = {
  PyVarObject_HEAD_INIT(NULL, 0)
  "ceph_module.BaseMgrStandbyModule", /* tp_name */
  sizeof(BaseMgrStandbyModule),     /* tp_basicsize */
  0,                         /* tp_itemsize */
  0,                         /* tp_dealloc */
  0,                         /* tp_print */
  0,                         /* tp_getattr */
  0,                         /* tp_setattr */
  0,                         /* tp_compare */
  0,                         /* tp_repr */
  0,                         /* tp_as_number */
  0,                         /* tp_as_sequence */
  0,                         /* tp_as_mapping */
  0,                         /* tp_hash */
  0,                         /* tp_call */
  0,                         /* tp_str */
  0,                         /* tp_getattro */
  0,                         /* tp_setattro */
  0,                         /* tp_as_buffer */
  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,        /* tp_flags */
  "ceph-mgr Standby Python Plugin", /* tp_doc */
  0,                         /* tp_traverse */
  0,                         /* tp_clear */
  0,                         /* tp_richcompare */
  0,                         /* tp_weaklistoffset */
  0,                         /* tp_iter */
  0,                         /* tp_iternext */
  BaseMgrStandbyModule_methods,     /* tp_methods */
  0,                         /* tp_members */
  0,                         /* tp_getset */
  0,                         /* tp_base */
  0,                         /* tp_dict */
  0,                         /* tp_descr_get */
  0,                         /* tp_descr_set */
  0,                         /* tp_dictoffset */
  (initproc)BaseMgrStandbyModule_init,                         /* tp_init */
  0,                         /* tp_alloc */
  BaseMgrStandbyModule_new,     /* tp_new */
};
