/*
Copyright (C) 2008-2012 Association of Universities for Research in Astronomy (AURA)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

    1. Redistributions of source code must retain the above copyright
      notice, this list of conditions and the following disclaimer.

    2. Redistributions in binary form must reproduce the above
      copyright notice, this list of conditions and the following
      disclaimer in the documentation and/or other materials provided
      with the distribution.

    3. The name of AURA and its representatives may not be used to
      endorse or promote products derived from this software without
      specific prior written permission.

THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
DAMAGE.
*/

/*
 Author: Michael Droettboom
         mdroe@stsci.edu
*/

#define NO_IMPORT_ARRAY

#include "wcslib_wrap.h"
#include "wcslib_tabprm_wrap.h"
#include "wcslib_wtbarr_wrap.h"
#include "wcslib_units_wrap.h"
#include <structmember.h> /* from Python */

#include <wcs.h>
#include <wcsfix.h>
#include <wcshdr.h>
#include <wcsmath.h>
#include <wcsprintf.h>
#include <wcsunits.h>

#include "isnan.h"
#include "distortion.h"

/*
 It gets to be really tedious to type long docstrings in ANSI C syntax
 (since multi-line strings literals are not valid).  Therefore, the
 docstrings are written in doc/docstrings.py, which are then converted
 by setup.py into docstrings.h, which we include here.
*/
#include "docstrings.h"

/***************************************************************************
 * Helper functions                                                        *
 ***************************************************************************/

enum e_altlin {
  has_pc = 1,
  has_cd = 2,
  has_crota = 4
};

static int
is_valid_alt_key(
    const char* key) {

  if (key[1] != '\0' ||
      !(key[0] == ' ' ||
        (key[0] >= 'A' && key[0] <= 'Z'))) {
    PyErr_SetString(PyExc_ValueError, "key must be ' ' or 'A'-'Z'");
    return 0;
  }

  return 1;
}

/***************************************************************************
 * PyWcsprm methods
 */

static int
PyWcsprm_cset(PyWcsprm* self, const int convert);

static pywcs_inline void
note_change(PyWcsprm* self) {
  self->x.flag = 0;
}

static void
PyWcsprm_dealloc(
    PyWcsprm* self) {

  int ignored;
  ignored = wcsfree(&self->x);
  Py_TYPE(self)->tp_free((PyObject*)self);
}

static PyWcsprm*
PyWcsprm_cnew(void) {
  PyWcsprm* self;
  self = (PyWcsprm*)(&PyWcsprmType)->tp_alloc(&PyWcsprmType, 0);
  return self;
}

static PyObject *
PyWcsprm_new(
    PyTypeObject* type,
    /*@unused@*/ PyObject* args,
    /*@unused@*/ PyObject* kwds) {

  PyWcsprm* self;
  self = (PyWcsprm*)type->tp_alloc(type, 0);
  return (PyObject*)self;
}

static int
PyWcsprm_init(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  int            status;
  PyObject*      header_obj    = NULL;
  char *         header        = NULL;
  Py_ssize_t     header_length = 0;
  Py_ssize_t     nkeyrec       = 0;
  char *         key           = " ";
  PyObject*      relax_obj     = NULL;
  int            relax         = 0;
  int            naxis         = -1;
  int            keysel        = -1;
  PyObject*      colsel        = Py_None;
  PyArrayObject* colsel_array  = NULL;
  int*           colsel_ints   = NULL;
  int            ctrl          = 0;
  int            nreject       = 0;
  int            nwcs          = 0;
  struct wcsprm* wcs           = NULL;
  int            i             = 0;
  const char*    keywords[]    = {"header", "key", "relax", "naxis", "keysel",
                                  "colsel", NULL};
  PyObject*      ignored       = NULL;
  int            ignored_int;

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "|OsOiiO:WCSBase.__init__",
          (char **)keywords, &header_obj, &key, &relax_obj, &naxis, &keysel,
          &colsel)) {
    return -1;
  }

  if (header_obj == NULL || header_obj == Py_None) {
    if (relax_obj != NULL && relax_obj != Py_False) {
      PyErr_SetString(
          PyExc_ValueError,
          "If no header is provided, relax may not be provided either.");
      return -1;
    }

    if (keysel > 0) {
      PyErr_SetString(
          PyExc_ValueError,
          "If no header is provided, keysel may not be provided either.");
      return -1;
    }

    if (colsel != Py_None) {
      PyErr_SetString(
          PyExc_ValueError,
          "If no header is provided, colsel may not be provided either.");
      return -1;
    }

    /* Default number of axes is 2 */
    if (naxis < 0) {
        naxis = 2;
    }

    if (naxis < 1 || naxis > 15) {
      PyErr_SetString(
          PyExc_ValueError,
          "naxis must be in range 1-15");
      return -1;
    }

    note_change(self);
    self->x.flag = -1;
    status = wcsini(1, naxis, &self->x);

    if (status != 0) {
      PyErr_SetString(
          PyExc_MemoryError,
          self->x.err->msg);
      return -1;
    }

    self->x.alt[0] = key[0];

    if (PyWcsprm_cset(self, 0)) {
      return -1;
    }
    wcsprm_c2python(&self->x);

    return 0;
  } else { /* header != NULL */
    #if PY3K
    if (PyBytes_AsStringAndSize(header_obj, &header, &header_length)) {
    #else
    if (PyString_AsStringAndSize(header_obj, &header, &header_length)) {
    #endif
      return -1;
    }

    if (relax_obj == Py_True) {
      relax = WCSHDR_all;
    } else if (relax_obj == NULL || relax_obj == Py_False) {
      relax = WCSHDR_none;
    } else {
      #if PY3K
      relax = PyLong_AsLong(relax_obj);
      #else
      relax = PyInt_AsLong(relax_obj);
      #endif
      if (relax == -1) {
        PyErr_SetString(
            PyExc_ValueError,
            "relax must be True, False or an integer.");
        return -1;
      }
    }

    if (!is_valid_alt_key(key)) {
      return -1;
    }

    if (naxis >= 0) {
      PyErr_SetString(
          PyExc_ValueError,
          "naxis may not be provided if a header is provided.");
      return -1;
    }

    nkeyrec = header_length / 80;
    if (nkeyrec > 0x7fffffff) {
      PyErr_SetString(
          PyExc_MemoryError,
          "header is too long");
      return -1;
    }

    if (colsel != Py_None) {
      colsel_array = (PyArrayObject*) PyArray_ContiguousFromAny(
        colsel, 1, 1, PyArray_INT);
      if (colsel_array == NULL) {
        return -1;
      }

      colsel_ints = malloc(sizeof(int) * (PyArray_DIM(colsel_array, 0) + 1));
      if (colsel_ints == NULL) {
        Py_DECREF(colsel_array);
        PyErr_SetString(
            PyExc_MemoryError,
            "Memory allocation error.");
        return -1;
      }

      colsel_ints[0] = PyArray_DIM(colsel_array, 0);
      for (i = 0; i < colsel_ints[0]; ++i) {
        colsel_ints[i+1] = colsel_array->data[i];
      }

      Py_DECREF(colsel_array);
    }

    if (keysel < 0) {
      status = wcspih(
          header,
          (int)nkeyrec,
          relax,
          ctrl,
          &nreject,
          &nwcs,
          &wcs);
    } else {
      status = wcsbth(
          header,
          (int)nkeyrec,
          relax,
          ctrl,
          keysel,
          colsel_ints,
          &nreject,
          &nwcs,
          &wcs);
    }

    free(colsel_ints);

    if (status != 0) {
      PyErr_SetString(
          PyExc_MemoryError,
          "Memory allocation error.");
      return -1;
    }

    if (nwcs == 0) {
      PyErr_SetString(
          WcsExc_NoWcsKeywordsFound,
          "No WCS keywords found in the given header");
      return -1;
    }

    /* Find the desired WCS */
    for (i = 0; i < nwcs; ++i) {
      if (wcs[i].alt[0] == key[0]) {
        break;
      }
    }

    if (i >= nwcs) {
      ignored_int = wcsvfree(&nwcs, &wcs);
      ignored = PyErr_Format(
          PyExc_KeyError,
          "No WCS with key '%s' was found in the given header",
          key);
      return -1;
    }

    if (wcscopy(1, wcs + i, &self->x) != 0) {
      ignored_int = wcsvfree(&nwcs, &wcs);
      PyErr_SetString(
          PyExc_MemoryError,
          self->x.err->msg);
      return -1;
    }

    note_change(self);
    wcsprm_c2python(&self->x);
    ignored_int = wcsvfree(&nwcs, &wcs);
    return 0;
  }
}

/*@null@*/ static PyObject*
PyWcsprm_copy(
    PyWcsprm* self) {

  PyWcsprm*      copy      = NULL;
  int            status;

  copy = PyWcsprm_cnew();
  if (copy == NULL) {
    return NULL;
  }

  wcsprm_python2c(&self->x);
  status = wcscopy(1, &self->x, &copy->x);
  wcsprm_c2python(&self->x);

  if (status == 0) {
    if (PyWcsprm_cset(copy, 0)) {
      Py_XDECREF(copy);
      return NULL;
    }
    wcsprm_c2python(&copy->x);
    return (PyObject*)copy;
  } else {
    Py_XDECREF(copy);
    wcs_to_python_exc(&(self->x));
    return NULL;
  }
}

PyObject*
PyWcsprm_find_all_wcs(
    PyObject* __,
    PyObject* args,
    PyObject* kwds) {

  PyObject*      header_obj    = NULL;
  char *         header        = NULL;
  Py_ssize_t     header_length = 0;
  Py_ssize_t     nkeyrec       = 0;
  PyObject*      relax_obj     = NULL;
  int            relax         = 0;
  int            keysel        = 0;
  int            ctrl          = 0;
  int            nreject       = 0;
  int            nwcs          = 0;
  struct wcsprm* wcs           = NULL;
  PyObject*      result        = NULL;
  PyWcsprm*      subresult     = NULL;
  int            i             = 0;
  int            ignored       = 0;
  const char*    keywords[]    = {"header", "relax", "keysel", NULL};
  int            status        = -1;

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "O|Oi:find_all_wcs",
          (char **)keywords, &header_obj, &relax_obj, &keysel)) {
    return NULL;
  }

  #if PY3K
  if (PyBytes_AsStringAndSize(header_obj, &header, &header_length)) {
  #else
  if (PyString_AsStringAndSize(header_obj, &header, &header_length)) {
  #endif
    return NULL;
  }

  nkeyrec = header_length / 80;
  if (nkeyrec > 0x7fffffff) {
    PyErr_SetString(
        PyExc_MemoryError,
        "header is too long");
    return NULL;
  }

  if (relax_obj == Py_True) {
    relax = WCSHDR_all;
  } else if (relax_obj == NULL || relax_obj == Py_False) {
    relax = WCSHDR_none;
  } else {
    #if PY3K
    relax = PyLong_AsLong(relax_obj);
    #else
    relax = PyInt_AsLong(relax_obj);
    #endif
    if (relax == -1) {
      PyErr_SetString(
          PyExc_ValueError,
          "relax must be True, False or an integer.");
      return NULL;
    }
  }

  Py_BEGIN_ALLOW_THREADS
  if (keysel < 0) {
    status = wcspih(
        header,
        (int)nkeyrec,
        relax,
        ctrl,
        &nreject,
        &nwcs,
        &wcs);
  } else {
    status = wcsbth(
        header,
        (int)nkeyrec,
        relax,
        ctrl,
        keysel,
        NULL,
        &nreject,
        &nwcs,
        &wcs);
  }
  Py_END_ALLOW_THREADS

  if (status != 0) {
    PyErr_SetString(
        PyExc_MemoryError,
        "Memory allocation error.");
    return NULL;
  }

  result = PyList_New(nwcs);
  if (result == NULL) {
    ignored = wcsvfree(&nwcs, &wcs);
    return NULL;
  }

  for (i = 0; i < nwcs; ++i) {
    subresult = PyWcsprm_cnew();
    if (wcscopy(1, wcs + i, &subresult->x) != 0) {
      Py_DECREF(result);
      ignored = wcsvfree(&nwcs, &wcs);
      PyErr_SetString(
          PyExc_MemoryError,
          "Could not initialize wcsprm object");
      return NULL;
    }

    if (PyList_SetItem(result, i, (PyObject *)subresult) == -1) {
      Py_DECREF(subresult);
      Py_DECREF(result);
      ignored = wcsvfree(&nwcs, &wcs);
      return NULL;
    }

    subresult->x.flag = 0;
    if (PyWcsprm_cset(subresult, 0)) {
      Py_DECREF(subresult);
      Py_DECREF(result);
      ignored = wcsvfree(&nwcs, &wcs);
      return NULL;
    }
    wcsprm_c2python(&subresult->x);
  }

  ignored = wcsvfree(&nwcs, &wcs);
  return result;
}

static PyObject*
PyWcsprm_celfix(
    PyWcsprm* self) {

  int status = 0;

  wcsprm_python2c(&self->x);
  status = celfix(&self->x);
  wcsprm_c2python(&self->x);

  if (status <= 0) {
    #if PY3K
    return PyLong_FromLong((long)status);
    #else
    return PyInt_FromLong((long)status);
    #endif
  } else {
    wcserr_fix_to_python_exc(self->x.err);
    return NULL;
  }
}

/*@null@*/ static PyObject*
PyWcsprm_cylfix(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  PyObject*      naxis_obj   = NULL;
  PyArrayObject* naxis_array = NULL;
  int*           naxis       = NULL;
  int            status      = 0;
  const char*    keywords[]  = {"naxis", NULL};
  PyObject*      ignored     = NULL;

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "|O:cylfix", (char **)keywords,
          &naxis_obj)) {
    return NULL;
  }

  if (naxis_obj != NULL) {
    naxis_array = (PyArrayObject*)PyArray_ContiguousFromAny(
        naxis_obj, 1, 1, PyArray_INT);
    if (naxis_array == NULL) {
      return NULL;
    }
    if (PyArray_DIM(naxis_array, 0) != self->x.naxis) {
      ignored = PyErr_Format(
          PyExc_ValueError,
          "naxis must be same length as the number of axes of "
          "the Wcsprm object (%d).",
          self->x.naxis);
      Py_DECREF(naxis_array);
      return NULL;
    }
    naxis = (int*)PyArray_DATA(naxis_array);
  }

  wcsprm_python2c(&self->x);
  status = cylfix(naxis, &self->x);
  wcsprm_c2python(&self->x);

  Py_XDECREF(naxis_array);

  if (status <= 0) {
    #if PY3K
    return PyLong_FromLong((long)status);
    #else
    return PyInt_FromLong((long)status);
    #endif
  } else {
    wcserr_fix_to_python_exc(self->x.err);
    return NULL;
  }
}

static PyObject*
PyWcsprm_datfix(
    PyWcsprm* self) {

  int status = 0;

  wcsprm_python2c(&self->x);
  status = datfix(&self->x);
  wcsprm_c2python(&self->x);

  if (status <= 0) {
    #if PY3K
    return PyLong_FromLong((long)status);
    #else
    return PyInt_FromLong((long)status);
    #endif
  } else {
    wcserr_fix_to_python_exc(self->x.err);
    return NULL;
  }
}

/*@null@*/ static PyObject*
PyWcsprm_fix(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  char*          translate_units = NULL;
  int            ctrl            = 0;
  PyObject*      naxis_obj       = NULL;
  PyArrayObject* naxis_array     = NULL;
  int*           naxis           = NULL;
  int            stat[NWCSFIX];
  struct wcserr  err[NWCSFIX];
  int            status          = 0;
  PyObject*      subresult;
  PyObject*      result;
  int            i               = 0;
  int            msg_index       = 0;
  const char*    message;
  PyObject*      ignored         = NULL;

  struct message_map_entry {
    const char* name;
    const int index;
  };
  const struct message_map_entry message_map[5] = {
    {"datfix", DATFIX},
    {"unitfix", UNITFIX},
    {"celfix", CELFIX},
    {"spcfix", SPCFIX},
    {"cylfix", CYLFIX},
  };
  const char* keywords[] = {"translate_units", "naxis", NULL};

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "|sO:fix", (char **)keywords,
          &translate_units, &naxis_obj)) {
    return NULL;
  }

  if (translate_units != NULL) {
    if (parse_unsafe_unit_conversion_spec(translate_units, &ctrl)) {
      return NULL;
    }
  }

  if (naxis_obj != NULL) {
    naxis_array = (PyArrayObject*)PyArray_ContiguousFromAny(
        naxis_obj, 1, 1, PyArray_INT);
    if (naxis_array == NULL) {
      return NULL;
    }
    if (PyArray_DIM(naxis_array, 0) != self->x.naxis) {
      ignored = PyErr_Format(
          PyExc_ValueError,
          "naxis must be same length as the number of axes of "
          "the Wcprm object (%d).",
          self->x.naxis);
      Py_DECREF(naxis_array);
      return NULL;
    }
    naxis = (int*)PyArray_DATA(naxis_array);
  }

  memset(err, 0, sizeof(struct wcserr) * NWCSFIX);
  wcsprm_python2c(&self->x);
  status = wcsfixi(ctrl, naxis, &self->x, stat, err);
  wcsprm_c2python(&self->x);

  /* We're done with this already, so deref now so we don't have to remember
     later */
  Py_XDECREF(naxis_array);

  result = PyDict_New();
  if (result == NULL) {
    return NULL;
  }

  for (i = 0; i < 5; ++i) {
    msg_index = stat[message_map[i].index];
    message = err[message_map[i].index].msg;
    if (message == NULL || message[0] == 0) {
      if (msg_index == FIXERR_SUCCESS) {
        message = "Success";
      } else if (msg_index == FIXERR_NO_CHANGE) {
        message = "No change";
      } else if (msg_index == FIXERR_UNITS_ALIAS &&
                 message_map[i].index == CYLFIX){
        message = "No change";
      }
    }
    #if PY3K
    subresult = PyUnicode_FromString(message);
    #else
    subresult = PyString_FromString(message);
    #endif
    if (subresult == NULL ||
        PyDict_SetItemString(result, message_map[i].name, subresult)) {
      Py_XDECREF(subresult);
      Py_XDECREF(result);
      return NULL;
    }
    Py_XDECREF(subresult);
  }

  return result;
}

/*@null@*/ static PyObject*
PyWcsprm_get_cdelt_func(
    PyWcsprm* self,
    /*@unused@*/ PyObject* args,
    /*@unused@*/ PyObject* kwds) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.cdelt)) {
    return NULL;
  }

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  naxis = self->x.naxis;

  return get_double_array_readonly("cdelt", self->x.cdelt, 1, &naxis, (PyObject*)self);
}

/*@null@*/ static PyObject*
PyWcsprm_get_pc_func(
    PyWcsprm* self,
    /*@unused@*/ PyObject* args,
    /*@unused@*/ PyObject* kwds) {

  npy_intp dims[2];

  if (is_null(self->x.pc)) {
    return NULL;
  }

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  dims[0] = self->x.naxis;
  dims[1] = self->x.naxis;

  return get_double_array_readonly("pc", self->x.pc, 2, dims, (PyObject*)self);
}

/*@null@*/ static PyObject*
PyWcsprm_get_ps(
    PyWcsprm* self,
    /*@unused@*/ PyObject* args,
    /*@unused@*/ PyObject* kwds) {

  if (self->x.ps == NULL) {
    PyErr_SetString(
        PyExc_AssertionError,
        "No PSi_ma records present.");
    return NULL;
  }

  return get_pscards("ps", self->x.ps, self->x.nps);
}

/*@null@*/ static PyObject*
PyWcsprm_get_pv(
    PyWcsprm* self,
    /*@unused@*/ PyObject* args,
    /*@unused@*/ PyObject* kwds) {

  if (self->x.pv == NULL) {
    PyErr_SetString(
        PyExc_AssertionError,
        "No PVi_ma records present.");
    return NULL;
  }

  return get_pvcards("pv", self->x.pv, self->x.npv);
}

static PyObject*
PyWcsprm_has_cdi_ja(
    PyWcsprm* self) {

  int result = 0;

  result = self->x.altlin & has_cd;

  return PyBool_FromLong(result);
}

static PyObject*
PyWcsprm_has_crotaia(
    PyWcsprm* self) {

  int result = 0;

  result = self->x.altlin & has_crota;

  return PyBool_FromLong(result);
}

static PyObject*
PyWcsprm_has_pci_ja(
    PyWcsprm* self) {

  int result = 0;

  result = (self->x.altlin == 0 || self->x.altlin & has_pc);

  return PyBool_FromLong(result);
}

static PyObject*
PyWcsprm_is_unity(
    PyWcsprm* self) {

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  return PyBool_FromLong(self->x.lin.unity);
}

/*@null@*/ static PyObject*
PyWcsprm_mix(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  int            mixpix     = 0;
  int            mixcel     = 0;
  double         vspan[2]   = {0, 0};
  double         vstep      = 0;
  int            viter      = 0;
  Py_ssize_t     naxis      = 0;
  PyObject*      world_obj  = NULL;
  PyObject*      pixcrd_obj = NULL;
  int            origin     = 1;
  PyArrayObject* world      = NULL;
  PyArrayObject* phi        = NULL;
  PyArrayObject* theta      = NULL;
  PyArrayObject* imgcrd     = NULL;
  PyArrayObject* pixcrd     = NULL;
  int            status     = -1;
  PyObject*      result     = NULL;
  PyObject*      ignored    = NULL;
  const char*    keywords[] = {
    "mixpix", "mixcel", "vspan", "vstep", "viter", "world", "pixcrd", "origin", NULL };

  if (!PyArg_ParseTupleAndKeywords(
        args, kwds, "ii(dd)diOOi:mix", (char **)keywords,
        &mixpix, &mixcel, &vspan[0], &vspan[1], &vstep, &viter, &world_obj,
        &pixcrd_obj, &origin)) {
    return NULL;
  }

  if (viter < 5 || viter > 10) {
    PyErr_SetString(
        PyExc_ValueError,
        "viter must be in the range 5 - 10");
    goto exit;
  }

  world = (PyArrayObject*)PyArray_ContiguousFromAny
    (world_obj, PyArray_DOUBLE, 1, 1);
  if (world == NULL) {
    PyErr_SetString(
        PyExc_TypeError,
        "Argument 6 (world) must be a 1-dimensional numpy array");
    goto exit;
  }
  if ((int)PyArray_DIM(world, 0) != self->x.naxis) {
    ignored = PyErr_Format(
        PyExc_TypeError,
        "Argument 6 (world) must be the same length as the number "
        "of axes (%d)",
        self->x.naxis);
    goto exit;
  }

  pixcrd = (PyArrayObject*)PyArray_ContiguousFromAny
    (pixcrd_obj, PyArray_DOUBLE, 1, 1);
  if (pixcrd == NULL) {
    PyErr_SetString(
        PyExc_TypeError,
        "Argument 7 (pixcrd) must be a 1-dimensional numpy array");
    goto exit;
  }
  if ((int)PyArray_DIM(pixcrd, 0) != self->x.naxis) {
    ignored = PyErr_Format(
        PyExc_TypeError,
        "Argument 7 (pixcrd) must be the same length as the "
        "number of axes (%d)",
        self->x.naxis);
    goto exit;
  }

  if (mixpix < 1 || mixpix > self->x.naxis) {
    PyErr_SetString(
        PyExc_ValueError,
        "Argument 1 (mixpix) must specify a pixel coordinate "
        "axis number");
    goto exit;
  }

  if (mixcel < 1 || mixcel > 2) {
    PyErr_SetString(
        PyExc_ValueError,
        "Argument 2 (mixcel) must specify a celestial coordinate "
        "axis number (1 for latitude, 2 for longitude)");
    goto exit;
  }

  /* Now we allocate a bunch of numpy arrays to store the
   * results in.
   */
  naxis = (Py_ssize_t)self->x.naxis;
  phi = (PyArrayObject*)PyArray_SimpleNew
    (1, &naxis, PyArray_DOUBLE);
  if (phi == NULL) {
    goto exit;
  }

  theta = (PyArrayObject*)PyArray_SimpleNew
    (1, &naxis, PyArray_DOUBLE);
  if (theta == NULL) {
    goto exit;
  }

  imgcrd = (PyArrayObject*)PyArray_SimpleNew
    (1, &naxis, PyArray_DOUBLE);
  if (imgcrd == NULL) {
    goto exit;
  }

  /* Convert pixel coordinates to 1-based */
  Py_BEGIN_ALLOW_THREADS
  preoffset_array(pixcrd, origin);
  wcsprm_python2c(&self->x);
  status = wcsmix(
      &self->x,
      mixpix,
      mixcel,
      vspan,
      vstep,
      viter,
      (double*)PyArray_DATA(world),
      (double*)PyArray_DATA(phi),
      (double*)PyArray_DATA(theta),
      (double*)PyArray_DATA(imgcrd),
      (double*)PyArray_DATA(pixcrd));
  wcsprm_c2python(&self->x);
  unoffset_array(pixcrd, origin);
  unoffset_array(imgcrd, origin);
  Py_END_ALLOW_THREADS

  if (status == 0) {
    result = PyDict_New();
    if (result == NULL ||
        PyDict_SetItemString(result, "imgcrd", (PyObject*)imgcrd) ||
        PyDict_SetItemString(result, "phi", (PyObject*)phi) ||
        PyDict_SetItemString(result, "theta", (PyObject*)theta) ||
        PyDict_SetItemString(result, "world", (PyObject*)world)) {
      goto exit;
    }
  }

 exit:
  Py_XDECREF(world);
  Py_XDECREF(phi);
  Py_XDECREF(theta);
  Py_XDECREF(imgcrd);
  Py_XDECREF(pixcrd);

  if (status == 0) {
    return result;
  } else {
    Py_XDECREF(result);
    if (status == -1) {
      /* The error message has already been set */
      return NULL;
    } else {
      wcs_to_python_exc(&(self->x));
      return NULL;
    }
  }
}

/*@null@*/ static PyObject*
PyWcsprm_p2s(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  int            naxis      = 2;
  int            ncoord     = 0;
  int            nelem      = 0;
  PyObject*      pixcrd_obj = NULL;
  int            origin     = 1;
  PyArrayObject* pixcrd     = NULL;
  PyArrayObject* imgcrd     = NULL;
  PyArrayObject* phi        = NULL;
  PyArrayObject* theta      = NULL;
  PyArrayObject* world      = NULL;
  PyArrayObject* stat       = NULL;
  PyObject*      result     = NULL;
  int            status     = 0;
  const char*    keywords[] = {
    "pixcrd", "origin", NULL };

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "Oi:p2s", (char **)keywords,
          &pixcrd_obj, &origin)) {
    return NULL;
  }

  naxis = self->x.naxis;

  pixcrd = (PyArrayObject*)PyArray_ContiguousFromAny
    (pixcrd_obj, PyArray_DOUBLE, 2, 2);
  if (pixcrd == NULL) {
    return NULL;
  }

  if (PyArray_DIM(pixcrd, 1) < naxis) {
    PyErr_Format(
      PyExc_RuntimeError,
      "Input array must be 2-dimensional, where the second dimension >= %d",
      naxis);
    goto exit;
  }

  /* Now we allocate a bunch of numpy arrays to store the results in.
   */
  imgcrd = (PyArrayObject*)PyArray_SimpleNew(
      2, PyArray_DIMS(pixcrd), PyArray_DOUBLE);
  if (imgcrd == NULL) {
    goto exit;
  }

  phi = (PyArrayObject*)PyArray_SimpleNew(
      1, PyArray_DIMS(pixcrd), PyArray_DOUBLE);
  if (phi == NULL) {
    goto exit;
  }

  theta = (PyArrayObject*)PyArray_SimpleNew(
      1, PyArray_DIMS(pixcrd), PyArray_DOUBLE);
  if (theta == NULL) {
    goto exit;
  }

  world = (PyArrayObject*)PyArray_SimpleNew(
      2, PyArray_DIMS(pixcrd), PyArray_DOUBLE);
  if (world == NULL) {
    goto exit;
  }

  stat = (PyArrayObject*)PyArray_SimpleNew(
      1, PyArray_DIMS(pixcrd), PyArray_INT);
  if (stat == NULL) {
    goto exit;
  }

  /* Make the call */
  Py_BEGIN_ALLOW_THREADS
  ncoord = PyArray_DIM(pixcrd, 0);
  nelem = PyArray_DIM(pixcrd, 1);
  preoffset_array(pixcrd, origin);
  wcsprm_python2c(&self->x);
  status = wcsp2s(
      &self->x,
      ncoord,
      nelem,
      (double*)PyArray_DATA(pixcrd),
      (double*)PyArray_DATA(imgcrd),
      (double*)PyArray_DATA(phi),
      (double*)PyArray_DATA(theta),
      (double*)PyArray_DATA(world),
      (int*)PyArray_DATA(stat));
  wcsprm_c2python(&self->x);
  unoffset_array(pixcrd, origin);
  /* unoffset_array(world, origin); */
  unoffset_array(imgcrd, origin);
  if (status == 8) {
    set_invalid_to_nan(
        ncoord, nelem, (double*)PyArray_DATA(imgcrd), (int*)PyArray_DATA(stat));
    set_invalid_to_nan(
        ncoord, 1, (double*)PyArray_DATA(phi), (int*)PyArray_DATA(stat));
    set_invalid_to_nan(
        ncoord, 1, (double*)PyArray_DATA(theta), (int*)PyArray_DATA(stat));
    set_invalid_to_nan(
        ncoord, nelem, (double*)PyArray_DATA(world), (int*)PyArray_DATA(stat));
  }
  Py_END_ALLOW_THREADS

  if (status == 0 || status == 8) {
    result = PyDict_New();
    if (result == NULL ||
        PyDict_SetItemString(result, "imgcrd", (PyObject*)imgcrd) ||
        PyDict_SetItemString(result, "phi", (PyObject*)phi) ||
        PyDict_SetItemString(result, "theta", (PyObject*)theta) ||
        PyDict_SetItemString(result, "world", (PyObject*)world) ||
        PyDict_SetItemString(result, "stat", (PyObject*)stat)) {
      goto exit;
    }
  }

 exit:
  Py_XDECREF(pixcrd);
  Py_XDECREF(imgcrd);
  Py_XDECREF(phi);
  Py_XDECREF(theta);
  Py_XDECREF(world);
  Py_XDECREF(stat);

  if (status == 0 || status == 8) {
    return result;
  } else {
    Py_XDECREF(result);
    if (status == -1) {
      /* Exception already set */
      return NULL;
    } else {
      wcs_to_python_exc(&(self->x));
      return NULL;
    }
  }
}

/*@null@*/ static PyObject*
PyWcsprm_s2p(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  int            naxis     = 2;
  int            ncoord    = 0;
  int            nelem     = 0;
  PyObject*      world_obj = NULL;
  int            origin    = 1;
  PyArrayObject* world     = NULL;
  PyArrayObject* phi       = NULL;
  PyArrayObject* theta     = NULL;
  PyArrayObject* imgcrd    = NULL;
  PyArrayObject* pixcrd    = NULL;
  PyArrayObject* stat      = NULL;
  PyObject*      result    = NULL;
  int            status    = -1;
  const char*    keywords[] = {
    "world", "origin", NULL };

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "Oi:s2p", (char **)keywords,
          &world_obj, &origin)) {
    return NULL;
  }

  naxis = self->x.naxis;

  world = (PyArrayObject*)PyArray_ContiguousFromAny(
      world_obj, PyArray_DOUBLE, 2, 2);
  if (world == NULL) {
    return NULL;
  }

  if (PyArray_DIM(world, 1) < naxis) {
    PyErr_Format(
      PyExc_RuntimeError,
      "Input array must be 2-dimensional, where the second dimension >= %d",
      naxis);
    goto exit;
  }

  /* Now we allocate a bunch of numpy arrays to store the
   * results in.
   */
  phi = (PyArrayObject*)PyArray_SimpleNew(
      1, PyArray_DIMS(world), PyArray_DOUBLE);
  if (phi == NULL) {
    goto exit;
  }

  theta = (PyArrayObject*)PyArray_SimpleNew(
      1, PyArray_DIMS(world), PyArray_DOUBLE);
  if (phi == NULL) {
    goto exit;
  }

  imgcrd = (PyArrayObject*)PyArray_SimpleNew(
      2, PyArray_DIMS(world), PyArray_DOUBLE);
  if (theta == NULL) {
    goto exit;
  }

  pixcrd = (PyArrayObject*)PyArray_SimpleNew(
      2, PyArray_DIMS(world), PyArray_DOUBLE);
  if (pixcrd == NULL) {
    goto exit;
  }

  stat = (PyArrayObject*)PyArray_SimpleNew(
      1, PyArray_DIMS(world), PyArray_INT);
  if (stat == NULL) {
    goto exit;
  }

  /* Make the call */
  Py_BEGIN_ALLOW_THREADS
  ncoord = PyArray_DIM(world, 0);
  nelem = PyArray_DIM(world, 1);
  /* preoffset_array(world, origin); */
  wcsprm_python2c(&self->x);
  status = wcss2p(
      &self->x,
      ncoord,
      nelem,
      (double*)PyArray_DATA(world),
      (double*)PyArray_DATA(phi),
      (double*)PyArray_DATA(theta),
      (double*)PyArray_DATA(imgcrd),
      (double*)PyArray_DATA(pixcrd),
      (int*)PyArray_DATA(stat));
  wcsprm_c2python(&self->x);
  /* unoffset_array(world, origin); */
  unoffset_array(pixcrd, origin);
  unoffset_array(imgcrd, origin);
  if (status == 8) {
    set_invalid_to_nan(
        ncoord, 1, (double*)PyArray_DATA(phi), (int*)PyArray_DATA(stat));
    set_invalid_to_nan(
        ncoord, 1, (double*)PyArray_DATA(theta), (int*)PyArray_DATA(stat));
    set_invalid_to_nan(
        ncoord, nelem, (double*)PyArray_DATA(imgcrd), (int*)PyArray_DATA(stat));
    set_invalid_to_nan(
        ncoord, nelem, (double*)PyArray_DATA(pixcrd), (int*)PyArray_DATA(stat));
  }
  Py_END_ALLOW_THREADS

  if (status == 0 || status == 9) {
    result = PyDict_New();
    if (result == NULL ||
        PyDict_SetItemString(result, "phi", (PyObject*)phi) ||
        PyDict_SetItemString(result, "theta", (PyObject*)theta) ||
        PyDict_SetItemString(result, "imgcrd", (PyObject*)imgcrd) ||
        PyDict_SetItemString(result, "pixcrd", (PyObject*)pixcrd) ||
        PyDict_SetItemString(result, "stat", (PyObject*)stat)) {
      goto exit;
    }
  }

 exit:
  Py_XDECREF(pixcrd);
  Py_XDECREF(imgcrd);
  Py_XDECREF(phi);
  Py_XDECREF(theta);
  Py_XDECREF(world);
  Py_XDECREF(stat);

  if (status == 0 || status == 9) {
    return result;
  } else {
    Py_XDECREF(result);
    if (status == -1) {
      /* Exception already set */
      return NULL;
    } else {
      wcs_to_python_exc(&(self->x));
      return NULL;
    }
  }
}

static int
PyWcsprm_cset(
    PyWcsprm* self,
    const int convert) {

  int status = 0;

  if (convert) wcsprm_python2c(&self->x);
  status = wcsset(&self->x);
  if (convert) wcsprm_c2python(&self->x);

  if (status == 0) {
    return 0;
  } else {
    wcs_to_python_exc(&(self->x));
    return 1;
  }
}

/*@null@*/ static PyObject*
PyWcsprm_set(
    PyWcsprm* self) {

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  Py_INCREF(Py_None);
  return Py_None;
}

/*@null@*/ static PyObject*
PyWcsprm_set_ps(
    PyWcsprm* self,
    PyObject* arg,
    /*@unused@*/ PyObject* kwds) {

  if (is_null(self->x.ps)) {
    return NULL;
  }

  if (set_pscards("ps", arg, &self->x.ps, &self->x.nps, &self->x.npsmax)) {
    self->x.m_ps = self->x.ps;
    return NULL;
  }
  self->x.m_ps = self->x.ps;

  note_change(self);

  Py_INCREF(Py_None);
  return Py_None;
}

/*@null@*/ static PyObject*
PyWcsprm_set_pv(
    PyWcsprm* self,
    PyObject* arg,
    /*@unused@*/ PyObject* kwds) {

  if (is_null(self->x.pv)) {
    return NULL;
  }

  if (set_pvcards("pv", arg, &self->x.pv, &self->x.npv, &self->x.npvmax)) {
    self->x.m_pv = self->x.pv;
    return NULL;
  }
  self->x.m_pv = self->x.pv;

  note_change(self);

  Py_INCREF(Py_None);
  return Py_None;
}

/* TODO: This is convenient for debugging for now -- but it's not very
 * Pythonic.  It should probably be hooked into __str__ or something.
 */
/*@null@*/ static PyObject*
PyWcsprm_print_contents(
    PyWcsprm* self) {

  int ignored;

  /* This is not thread-safe, but since we're holding onto the GIL,
     we can assume we won't have thread conflicts */
  wcsprintf_set(NULL);

  wcsprm_python2c(&self->x);
  if (PyWcsprm_cset(self, 0)) {
    wcsprm_c2python(&self->x);
    return NULL;
  }
  ignored = wcsprt(&self->x);
  wcsprm_c2python(&self->x);

  printf("%s", wcsprintf_buf());

  Py_INCREF(Py_None);
  return Py_None;
}

/*@null@*/ static PyObject*
PyWcsprm_spcfix(
    PyWcsprm* self) {

  int status = 0;

  wcsprm_python2c(&self->x);
  status = spcfix(&self->x);
  wcsprm_c2python(&self->x);

  if (status <= 0) {
    #if PY3K
    return PyLong_FromLong((long)status);
    #else
    return PyInt_FromLong((long)status);
    #endif
  } else {
    wcserr_fix_to_python_exc(self->x.err);
    return NULL;
  }
}

/*@null@*/ static PyObject*
PyWcsprm_sptr(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  int   i                = -1;
  char* py_ctype         = NULL;
  char  ctype[9];
  int   status           = 0;
  const char* keywords[] = {"ctype", "i", NULL};

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "s|i:sptr", (char **)keywords,
          &py_ctype, &i)) {
    return NULL;
  }

  if (strlen(py_ctype) > 8) {
    PyErr_SetString(
        PyExc_ValueError,
        "ctype string has more than 8 characters.");
  }

  strncpy(ctype, py_ctype, 9);

  wcsprm_python2c(&self->x);
  status = wcssptr(&self->x, &i, ctype);
  wcsprm_c2python(&self->x);

  if (status == 0) {
    Py_INCREF(Py_None);
    return Py_None;
  } else {
    wcs_to_python_exc(&(self->x));
    return NULL;
  }
}

/*@null@*/ static PyObject*
PyWcsprm___str__(
    PyWcsprm* self) {

  /* This is not thread-safe, but since we're holding onto the GIL,
     we can assume we won't have thread conflicts */
  wcsprintf_set(NULL);

  wcsprm_python2c(&self->x);
  if (PyWcsprm_cset(self, 0)) {
    wcsprm_c2python(&self->x);
    return NULL;
  }
  wcsprt(&self->x);
  wcsprm_c2python(&self->x);

  #if PY3K
  return PyUnicode_FromString(wcsprintf_buf());
  #else
  return PyString_FromString(wcsprintf_buf());
  #endif
}

/*@null@*/ static PyObject*
PyWcsprm_sub(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  int        i              = -1;
  Py_ssize_t tmp            = 0;
  PyObject*  py_axes        = NULL;
  PyWcsprm*  py_dest_wcs    = NULL;
  PyObject*  element        = NULL;
  #if PY3K
  PyObject*  element_utf8   = NULL;
  #endif
  char*      element_str    = NULL;
  int        element_val    = 0;
  int        nsub           = 0;
  int        alloc_size     = 0;
  int*       axes           = NULL;
  int        status         = -1;
  const char*    keywords[] = {"axes", NULL};

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "|O:sub", (char **)keywords,
          &py_axes)) {
    goto exit;
  }

  if (py_axes == NULL || py_axes == Py_None) {
    /* leave all variables as is */
  } else if (PySequence_Check(py_axes)) {
    tmp = PySequence_Size(py_axes);
    if (tmp == -1) {
      goto exit;
    }
    nsub = (int)tmp;

    axes = malloc(nsub * sizeof(int));
    if (axes == NULL) {
      PyErr_SetString(PyExc_MemoryError, "Out of memory");
      goto exit;
    }

    for (i = 0; i < nsub; ++i) {
      element = PySequence_GetItem(py_axes, i);
      if (element == NULL) {
        goto exit;
      }

      #if PY3K
      if (PyUnicode_Check(element)) {
        element_utf8 = PyUnicode_AsUTF8String(element);
        if (element_utf8 == NULL) {
          goto exit;
        }
        element_str = PyBytes_AsString(element_utf8);
        Py_DECREF(element_utf8); element_utf8 = NULL;
      #else
      if (PyString_Check(element)) {
        /* Doesn't return NULL, because we already known it's a string */
        element_str = PyString_AsString(element);
      #endif
        if (strncmp(element_str, "longitude", 10) == 0) {
          element_val = WCSSUB_LONGITUDE;
        } else if (strncmp(element_str, "latitude", 9) == 0) {
          element_val = WCSSUB_LATITUDE;
        } else if (strncmp(element_str, "cubeface", 9) == 0) {
          element_val = WCSSUB_CUBEFACE;
        } else if (strncmp(element_str, "spectral", 9) == 0) {
          element_val = WCSSUB_SPECTRAL;
        } else if (strncmp(element_str, "stokes", 7) == 0) {
          element_val = WCSSUB_STOKES;
        } else if (strncmp(element_str, "celestial", 10) == 0) {
          element_val = WCSSUB_CELESTIAL;
        } else {
          PyErr_SetString(
            PyExc_ValueError,
            "string values for axis sequence must be one of 'latitude', 'longitude', 'cubeface', 'spectral', 'stokes', or 'celestial'");
          goto exit;
        }
      #if PY3K
      } else if (PyLong_Check(element)) {
        tmp = (Py_ssize_t)PyLong_AsSsize_t(element);
      #else
      } else if (PyInt_Check(element)) {
        tmp = (Py_ssize_t)PyInt_AsLong(element);
      #endif
        if (tmp == -1 && PyErr_Occurred()) {
          goto exit;
        }
        element_val = (int)tmp;
      } else {
        PyErr_SetString(
          PyExc_TypeError,
          "axes sequence must contain either strings or ints");
        goto exit;
      }

      axes[i] = element_val;

      Py_DECREF(element);
      element = NULL;
    }
  #if PY3K
  } else if (PyLong_Check(py_axes)) {
    tmp = (Py_ssize_t)PyLong_AsSsize_t(py_axes);
  #else
  } else if (PyInt_Check(py_axes)) {
    tmp = (Py_ssize_t)PyInt_AsLong(py_axes);
  #endif
    if (tmp == -1 && PyErr_Occurred()) {
      goto exit;
    }
    nsub = (int)tmp;

    if (nsub < 0 || nsub > self->x.naxis) {
      PyErr_Format(
        PyExc_ValueError,
        "If axes is an int, it must be in the range 0-self.naxis (%d)",
        self->x.naxis);
      goto exit;
    }
  } else {
    PyErr_SetString(
      PyExc_TypeError,
      "axes must None, a sequence or an integer");
    goto exit;
  }

  if (nsub == 0) {
    alloc_size = self->x.naxis;
  } else {
    alloc_size = nsub;
  }

  py_dest_wcs = (PyWcsprm*)PyWcsprm_cnew();
  py_dest_wcs->x.flag = -1;
  status = wcsini(1, alloc_size, &py_dest_wcs->x);
  if (status != 0) {
    goto exit;
  }

  wcsprm_python2c(&self->x);
  status = wcssub(0, &self->x, &nsub, axes, &py_dest_wcs->x);
  wcsprm_c2python(&self->x);
  if (PyWcsprm_cset(py_dest_wcs, 0)) {
    goto exit;
  }
  wcsprm_c2python(&py_dest_wcs->x);

  if (status != 0) {
    goto exit;
  }

 exit:
  free(axes);
  Py_XDECREF(element);
  #if PY3K
  Py_XDECREF(element_utf8);
  #endif

  if (status == 0) {
    return (PyObject*)py_dest_wcs;
  } else if (status == -1) {
    Py_XDECREF(py_dest_wcs);
    /* Exception already set */
    return NULL;
  } else {
    wcs_to_python_exc(&(py_dest_wcs->x));
    Py_XDECREF(py_dest_wcs);
    return NULL;
  }
}

/*@null@*/ static PyObject*
PyWcsprm_to_header(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  PyObject* relax_obj    = NULL;
  int       relax        = 0;
  int       nkeyrec      = 0;
  char*     header       = NULL;
  int       status       = -1;
  PyObject* result       = NULL;
  const char* keywords[] = {"relax", NULL};

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "|O:to_header",
          (char **)keywords, &relax_obj)) {
    goto exit;
  }

  if (relax_obj == Py_True) {
    relax = WCSHDO_all;
  } else if (relax_obj == NULL || relax_obj == Py_False) {
    relax = WCSHDO_none;
  } else {
    #if PY3K
    relax = PyLong_AsLong(relax_obj);
    #else
    relax = PyInt_AsLong(relax_obj);
    #endif
    if (relax == -1) {
      PyErr_SetString(
          PyExc_ValueError,
          "relax must be True, False or an integer.");
      return NULL;
    }
  }

  wcsprm_python2c(&self->x);
  status = wcshdo(relax, &self->x, &nkeyrec, &header);
  wcsprm_c2python(&self->x);

  if (status != 0) {
    PyErr_SetString(
        PyExc_RuntimeError,
        "Unknown error occurred.  Something is seriously wrong.");
    goto exit;
  }

  /* Just return the raw header string.  PyFITS on the Python side will help
     to parse and use this information. */
  #if PY3K
  result = PyBytes_FromStringAndSize(header, (Py_ssize_t)nkeyrec * 80);
  #else
  result = PyString_FromStringAndSize(header, (Py_ssize_t)nkeyrec * 80);
  #endif

 exit:
  free(header);
  return result;
}

/*@null@*/ static PyObject*
PyWcsprm_unitfix(
    PyWcsprm* self,
    PyObject* args,
    PyObject* kwds) {

  char* translate_units  = NULL;
  int   ctrl             = 0;
  int   status           = 0;
  const char* keywords[] = {"translate_units", NULL};

  if (!PyArg_ParseTupleAndKeywords(
          args, kwds, "|s:unitfix", (char **)keywords,
          &translate_units)) {
    return NULL;
  }

  if (translate_units != NULL) {
    if (parse_unsafe_unit_conversion_spec(translate_units, &ctrl)) {
      return NULL;
    }
  }

  status = unitfix(ctrl, &self->x);

  if (status <= 0) {
    #if PY3K
    return PyLong_FromLong((long)status);
    #else
    return PyInt_FromLong((long)status);
    #endif
  } else {
    wcserr_fix_to_python_exc(self->x.err);
    return NULL;
  }
}


/***************************************************************************
 * Member getters/setters (properties)
 */
/*@null@*/ static PyObject*
PyWcsprm_get_alt(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.alt)) {
    return NULL;
  }

  /* Force a null-termination of this single-character string */
  self->x.alt[1] = '\0';
  return get_string("alt", self->x.alt);
}

static int
PyWcsprm_set_alt(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  char value_string[2];

  if (is_null(self->x.alt)) {
    return -1;
  }

  if (value == NULL) { /* deletion */
    self->x.alt[0] = ' ';
    self->x.alt[1] = '\0';
    note_change(self);
    return 0;
  }

  if (set_string("alt", value, value_string, 2)) {
    return -1;
  }

  if (!is_valid_alt_key(value_string)) {
    return -1;
  }

  strncpy(self->x.alt, value_string, 2);

  note_change(self);

  return 0;
}

/*@null@*/ static PyObject*
PyWcsprm_get_axis_types(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.types)) {
    return NULL;
  }

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  return get_int_array("axis_types", self->x.types, 1, &naxis, (PyObject*)self);
}

/*@null@*/ static PyObject*
PyWcsprm_get_cd(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  npy_intp dims[2];

  if (is_null(self->x.cd)) {
    return NULL;
  }

  if ((self->x.altlin & has_cd) == 0) {
    PyErr_SetString(PyExc_AttributeError, "No cd is present.");
    return NULL;
  }

  dims[0] = self->x.naxis;
  dims[1] = self->x.naxis;

  return get_double_array("cd", self->x.cd, 2, dims, (PyObject*)self);
}

static int
PyWcsprm_set_cd(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp dims[2];

  if (is_null(self->x.cd)) {
    return -1;
  }


  if (value == NULL) {
    self->x.altlin &= ~has_cd;
    note_change(self);
    return 0;
  }

  dims[0] = self->x.naxis;
  dims[1] = self->x.naxis;

  if (set_double_array("cd", value, 2, dims, self->x.cd)) {
    return -1;
  }

  self->x.altlin |= has_cd;

  note_change(self);

  return 0;
}

 /*@null@*/ static PyObject*
PyWcsprm_get_cdelt(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.cdelt)) {
    return NULL;
  }

  naxis = self->x.naxis;

  if (self->x.altlin & has_cd) {
    PyErr_WarnEx(NULL, "cdelt will be ignored since cd is present", 1);
  }

  return get_double_array("cdelt", self->x.cdelt, 1, &naxis, (PyObject*)self);
}

/*@null@*/ static int
PyWcsprm_set_cdelt(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp dims;

  if (is_null(self->x.cdelt)) {
    return -1;
  }

  dims = (npy_int)self->x.naxis;

  if (self->x.altlin & has_cd) {
    PyErr_WarnEx(NULL, "cdelt will be ignored since cd is present", 1);
  }

  note_change(self);

  return set_double_array("cdelt", value, 1, &dims, self->x.cdelt);
}

static PyObject*
PyWcsprm_get_cel_offset(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_bool("cel_offset", self->x.cel.offset);
}

static int
PyWcsprm_set_cel_offset(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  return set_bool("cel_offset", value, &self->x.cel.offset);
}


/*@null@*/ static PyObject*
PyWcsprm_get_cname(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.cname)) {
    return NULL;
  }

  return get_str_list("cname", self->x.cname, (Py_ssize_t)self->x.naxis, 68, (PyObject*)self);
}

/*@null@*/ static int
PyWcsprm_set_cname(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {
  if (is_null(self->x.cname)) {
    return -1;
  }

  note_change(self);

  return set_str_list("cname", value, (Py_ssize_t)self->x.naxis, 0, self->x.cname);
}

/*@null@*/ static PyObject*
PyWcsprm_get_colax(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.colax)) {
    return NULL;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  return get_int_array("colax", self->x.colax, 1, &naxis, (PyObject*)self);
}

static int
PyWcsprm_set_colax(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp naxis = 0;

  if (is_null(self->x.colax)) {
    return -1;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  note_change(self);

  return set_int_array("colax", value, 1, &naxis, self->x.colax);
}

static PyObject*
PyWcsprm_get_colnum(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_int("colnum", self->x.colnum);
}

static int
PyWcsprm_set_colnum(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  return set_int("colnum", value, &self->x.colnum);
}

/*@null@*/ static PyObject*
PyWcsprm_get_crder(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.crder)) {
    return NULL;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  return get_double_array("crder", self->x.crder, 1, &naxis, (PyObject*)self);
}

static int
PyWcsprm_set_crder(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp naxis = 0;

  if (is_null(self->x.crder)) {
    return -1;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  note_change(self);

  return set_double_array("crder", value, 1, &naxis, self->x.crder);
}

/*@null@*/ static PyObject*
PyWcsprm_get_crota(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.crota)) {
    return NULL;
  }

  if ((self->x.altlin & has_crota) == 0) {
    PyErr_SetString(PyExc_AttributeError, "No crota is present.");
    return NULL;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  return get_double_array("crota", self->x.crota, 1, &naxis, (PyObject*)self);
}

static int
PyWcsprm_set_crota(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp naxis = 0;

  if (is_null(self->x.crota)) {
    return -1;
  }

  if (value == NULL) { /* Deletion */
    self->x.altlin &= ~has_crota;
    note_change(self);
    return 0;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  if (set_double_array("crota", value, 1, &naxis, self->x.crota)) {
    return -1;
  }

  self->x.altlin |= has_crota;

  note_change(self);

  return 0;
}

/*@null@*/ static PyObject*
PyWcsprm_get_crpix(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.crpix)) {
    return NULL;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  return get_double_array("crpix", self->x.crpix, 1, &naxis, (PyObject*)self);
}

static int
PyWcsprm_set_crpix(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp naxis = 0;

  if (is_null(self->x.crpix)) {
    return -1;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  note_change(self);

  return set_double_array("crpix", value, 1, &naxis, self->x.crpix);
}

/*@null@*/ static PyObject*
PyWcsprm_get_crval(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis = 0;

  if (is_null(self->x.crval)) {
    return NULL;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  return get_double_array("crval", self->x.crval, 1, &naxis, (PyObject*)self);
}

static int
PyWcsprm_set_crval(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp naxis;

  if (is_null(self->x.crval)) {
    return -1;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  note_change(self);

  return set_double_array("crval", value, 1, &naxis, self->x.crval);
}

/*@null@*/ static PyObject*
PyWcsprm_get_csyer(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t naxis;

  if (is_null(self->x.csyer)) {
    return NULL;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  return get_double_array("csyer", self->x.csyer, 1, &naxis, (PyObject*)self);
}

static int
PyWcsprm_set_csyer(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp naxis;

  if (is_null(self->x.csyer)) {
    return -1;
  }

  naxis = (Py_ssize_t)self->x.naxis;

  note_change(self);

  return set_double_array("csyer", value, 1, &naxis, self->x.csyer);
}

/*@null@*/ static PyObject*
PyWcsprm_get_ctype(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.ctype)) {
    return NULL;
  }

  return get_str_list("ctype", self->x.ctype, self->x.naxis, 68, (PyObject*)self);
}

static int
PyWcsprm_set_ctype(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.ctype)) {
    return -1;
  }

  note_change(self);

  return set_str_list("ctype", value, (Py_ssize_t)self->x.naxis, 0, self->x.ctype);
}

static PyObject*
PyWcsprm_get_cubeface(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_int("cubeface", self->x.cubeface);
}

static int
PyWcsprm_set_cubeface(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  return set_int("cubeface", value, &self->x.cubeface);
}

static int
unit_verify(char* val) {

  int status, func;
  double scale, units[WCSUNITS_NTYPE];
  struct wcserr *err = NULL;

  status = wcsulexe(val, &func, &scale, units, &err);
  if (status == 0) {
    return 1;
  } else {
    wcserr_units_to_python_exc(err);
    free(err);
    return 0;
  }
}

/*@null@*/ static PyObject*
PyWcsprm_get_cunit(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.cunit)) {
    return NULL;
  }

  return get_str_list_verified(
    "cunit", self->x.cunit, (Py_ssize_t)self->x.naxis, 68, (PyObject*)self,
    unit_verify);
}

static int
PyWcsprm_set_cunit(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.cunit)) {
    return -1;
  }

  note_change(self);

  return set_str_list_verified(
    "cunit", value, (Py_ssize_t)self->x.naxis, 0, self->x.cunit,
    unit_verify);
}

/*@null@*/ static PyObject*
PyWcsprm_get_dateavg(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.dateavg)) {
    return NULL;
  }

  return get_string("dateavg", self->x.dateavg);
}

static int
PyWcsprm_set_dateavg(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.dateavg)) {
    return -1;
  }

  note_change(self);

  /* TODO: Verify that this looks like a date string */

  return set_string("dateavg", value, self->x.dateavg, 72);
}

/*@null@*/ static PyObject*
PyWcsprm_get_dateobs(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.dateobs)) {
    return NULL;
  }

  return get_string("dateobs", self->x.dateobs);
}

static int
PyWcsprm_set_dateobs(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.dateobs)) {
    return -1;
  }

  note_change(self);

  return set_string("dateobs", value, self->x.dateobs, 72);
}

static PyObject*
PyWcsprm_get_equinox(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("equinox", self->x.equinox);
}

static int
PyWcsprm_set_equinox(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (value == NULL) { /* deletion */
    self->x.equinox = (double)NPY_NAN;
    return 0;
  }

  note_change(self);

  return set_double("equinox", value, &self->x.equinox);
}

/*@null@*/ static PyObject*
PyWcsprm_get_imgpix_matrix(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  npy_intp dims[2];

  if (is_null(self->x.lin.imgpix)) {
    return NULL;
  }

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  dims[0] = self->x.naxis;
  dims[1] = self->x.naxis;

  return get_double_array("imgpix_matrix", self->x.lin.imgpix, 2, dims,
                          (PyObject*)self);
}

static PyObject*
PyWcsprm_get_lat(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  return get_int("lat", self->x.lat);
}

static PyObject*
PyWcsprm_get_latpole(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("latpole", self->x.latpole);
}

static int
PyWcsprm_set_latpole(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  if (value == NULL) {
    self->x.latpole = 90.0;
    return 0;
  }

  return set_double("latpole", value, &self->x.latpole);
}

/*@null@*/ static PyObject*
PyWcsprm_get_lattyp(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.lattyp)) {
    return NULL;
  }

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  return get_string("lattyp", self->x.lattyp);
}

static PyObject*
PyWcsprm_get_lng(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  return get_int("lng", self->x.lng);
}

/*@null@*/ static PyObject*
PyWcsprm_get_lngtyp(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.lngtyp)) {
    return NULL;
  }

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  return get_string("lngtyp", self->x.lngtyp);
}

static PyObject*
PyWcsprm_get_lonpole(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("lonpole", self->x.lonpole);
}

static int
PyWcsprm_set_lonpole(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  if (value == NULL) {
    self->x.lonpole = (double)NPY_NAN;
    return 0;
  }

  return set_double("lonpole", value, &self->x.lonpole);
}

static PyObject*
PyWcsprm_get_mjdavg(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("mjdavg", self->x.mjdavg);
}

static int
PyWcsprm_set_mjdavg(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  if (value == NULL) {
    self->x.mjdavg = (double)NPY_NAN;
    return 0;
  }

  return set_double("mjdavg", value, &self->x.mjdavg);
}

static PyObject*
PyWcsprm_get_mjdobs(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("mjdobs", self->x.mjdobs);
}

static int
PyWcsprm_set_mjdobs(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  if (value == NULL) {
    self->x.mjdobs = (double)NPY_NAN;
    return 0;
  }

  return set_double("mjdobs", value, &self->x.mjdobs);
}

/*@null@*/ static PyObject*
PyWcsprm_get_name(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.wcsname)) {
    return NULL;
  }

  return get_string("name", self->x.wcsname);
}

static int
PyWcsprm_set_name(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.wcsname)) {
    return -1;
  }

  note_change(self);

  return set_string("name", value, self->x.wcsname, 72);
}

static PyObject*
PyWcsprm_get_naxis(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_int("naxis", self->x.naxis);
}

/*@null@*/ static PyObject*
PyWcsprm_get_obsgeo(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  Py_ssize_t size = 3;

  if (is_null(self->x.obsgeo)) {
    return NULL;
  }

  return get_double_array("obsgeo", self->x.obsgeo, 1, &size, (PyObject*)self);
}

static int
PyWcsprm_set_obsgeo(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp size = 3;

  if (is_null(self->x.obsgeo)) {
    return -1;
  }

  note_change(self);

  if (value == NULL) {
    self->x.obsgeo[0] = NPY_NAN;
    self->x.obsgeo[1] = NPY_NAN;
    self->x.obsgeo[2] = NPY_NAN;
    return 0;
  }

  return set_double_array("obsgeo", value, 1, &size, self->x.obsgeo);
}

/*@null@*/ static PyObject*
PyWcsprm_get_pc(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  npy_intp dims[2];

  if (is_null(self->x.pc)) {
    return NULL;
  }

  if (self->x.altlin != 0 && (self->x.altlin & has_pc) == 0) {
    PyErr_SetString(PyExc_AttributeError, "No pc is present.");
    return NULL;
  }

  dims[0] = self->x.naxis;
  dims[1] = self->x.naxis;

  return get_double_array("pc", self->x.pc, 2, dims, (PyObject*)self);
}

static int
PyWcsprm_set_pc(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  npy_intp dims[2];
  int i, j, naxis;
  double* pc;

  if (is_null(self->x.pc)) {
    return -1;
  }

  note_change(self);

  if (value == NULL) { /* deletion */
    self->x.altlin &= ~has_pc;

    /* If this results in deleting all flags, pc is still the default,
       so we should set the pc matrix itself to default values. */
    naxis = self->x.naxis;
    pc = self->x.pc;
    for (i = 0; i < naxis; i++) {
      for (j = 0; j < naxis; j++) {
        if (j == i) {
          *pc = 1.0;
        } else {
          *pc = 0.0;
        }
        pc++;
      }
    }

    note_change(self);

    return 0;
  }

  dims[0] = self->x.naxis;
  dims[1] = self->x.naxis;

  if (set_double_array("pc", value, 2, dims, self->x.pc)) {
    return -1;
  }

  self->x.altlin |= has_pc;

  note_change(self);

  return 0;
}

static PyObject*
PyWcsprm_get_phi0(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("phi0", self->x.cel.phi0);
}

static int
PyWcsprm_set_phi0(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  if (value == NULL) {
    self->x.cel.phi0 = (double)NPY_NAN;
    return 0;
  }

  return set_double("phi0", value, &(self->x.cel.phi0));
}

static PyObject*
PyWcsprm_get_piximg_matrix(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  npy_intp dims[2];

  if (is_null(self->x.lin.piximg)) {
    return NULL;
  }

  if (PyWcsprm_cset(self, 1)) {
    return NULL;
  }

  dims[0] = self->x.naxis;
  dims[1] = self->x.naxis;

  return get_double_array("piximg_matrix", self->x.lin.piximg, 2, dims,
                          (PyObject*)self);
}

static PyObject*
PyWcsprm_get_radesys(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.radesys)) {
    return NULL;
  }

  return get_string("radesys", self->x.radesys);
}

static int
PyWcsprm_set_radesys(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.radesys)) {
    return -1;
  }

  note_change(self);

  return set_string("radesys", value, self->x.radesys, 72);
}

static PyObject*
PyWcsprm_get_restfrq(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("restfrq", self->x.restfrq);
}

static int
PyWcsprm_set_restfrq(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (value == NULL) { /* deletion */
    self->x.restfrq = (double)NPY_NAN;
    return 0;
  }

  note_change(self);

  return set_double("restfrq", value, &self->x.restfrq);
}

static PyObject*
PyWcsprm_get_restwav(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("restwav", self->x.restwav);
}

static int
PyWcsprm_set_restwav(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (value == NULL) { /* deletion */
    self->x.restwav = (double)NPY_NAN;
    return 0;
  }

  note_change(self);

  return set_double("restwav", value, &self->x.restwav);
}

static PyObject*
PyWcsprm_get_spec(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_int("spec", self->x.spec);
}

/*@null@*/ static PyObject*
PyWcsprm_get_specsys(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.specsys)) {
    return NULL;
  }

  return get_string("specsys", self->x.specsys);
}

static int
PyWcsprm_set_specsys(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.specsys)) {
    return -1;
  }

  note_change(self);

  return set_string("specsys", value, self->x.specsys, 72);
}

/*@null@*/ static PyObject*
PyWcsprm_get_ssysobs(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.ssysobs)) {
    return NULL;
  }

  return get_string("ssysobs", self->x.ssysobs);
}

static int
PyWcsprm_set_ssysobs(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.ssysobs)) {
    return -1;
  }

  note_change(self);

  return set_string("ssysobs", value, self->x.ssysobs, 72);
}

/*@null@*/ static PyObject*
PyWcsprm_get_ssyssrc(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.ssyssrc)) {
    return NULL;
  }

  return get_string("ssyssrc", self->x.ssyssrc);
}

static int
PyWcsprm_set_ssyssrc(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (is_null(self->x.ssyssrc)) {
    return -1;
  }

  note_change(self);

  return set_string("ssyssrc", value, self->x.ssyssrc, 72);
}

static PyObject*
PyWcsprm_get_tab(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  PyObject* result;
  PyObject* subresult;
  int i, ntab;

  ntab = self->x.ntab;

  result = PyList_New(ntab);
  if (result == NULL) {
    return NULL;
  }

  for (i = 0; i < ntab; ++i) {
    subresult = (PyObject *)PyTabprm_cnew((PyObject *)self, &(self->x.tab[i]));
    if (subresult == NULL) {
      Py_DECREF(result);
      return NULL;
    }

    if (PyList_SetItem(result, i, subresult) == -1) {
      Py_DECREF(subresult);
      Py_DECREF(result);
      return NULL;
    }
  }

  return result;
}

static PyObject*
PyWcsprm_get_theta0(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("theta0", self->x.cel.theta0);
}

static int
PyWcsprm_set_theta0(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  note_change(self);

  if (value == NULL) {
    self->x.cel.theta0 = (double)NPY_NAN;
    return 0;
  }

  return set_double("theta0", value, &self->x.cel.theta0);
}

static PyObject*
PyWcsprm_get_velangl(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("velangl", self->x.velangl);
}

static int
PyWcsprm_set_velangl(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (value == NULL) { /* deletion */
    self->x.velangl = (double)NPY_NAN;
    return 0;
  }

  note_change(self);

  return set_double("velangl", value, &self->x.velangl);
}

static PyObject*
PyWcsprm_get_velosys(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("velosys", self->x.velosys);
}

static int
PyWcsprm_set_velosys(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (value == NULL) { /* deletion */
    self->x.velosys = (double)NPY_NAN;
    return 0;
  }

  note_change(self);

  return set_double("velosys", value, &self->x.velosys);
}

/* static PyObject* */
/* PyWcsprm_get_wtb( */
/*     PyWcsprm* self, */
/*     /\*@unused@*\/ void* closure) { */

/*   PyObject* result; */
/*   PyObject* subresult; */
/*   int i, nwtb; */

/*   nwtb = self->x.nwtb; */

/*   result = PyList_New(nwtb); */
/*   if (result == NULL) { */
/*     return NULL; */
/*   } */

/*   for (i = 0; i < nwtb; ++i) { */
/*     subresult = (PyObject *)PyWtbarr_cnew((PyObject *)self, &(self->x.wtb[i])); */
/*     if (subresult == NULL) { */
/*       Py_DECREF(result); */
/*       return NULL; */
/*     } */

/*     if (PyList_SetItem(result, i, subresult) == -1) { */
/*       Py_DECREF(subresult); */
/*       Py_DECREF(result); */
/*       return NULL; */
/*     } */
/*   } */

/*   return result; */
/* } */

static PyObject*
PyWcsprm_get_zsource(
    PyWcsprm* self,
    /*@unused@*/ void* closure) {

  return get_double("zsource", self->x.zsource);
}

static int
PyWcsprm_set_zsource(
    PyWcsprm* self,
    PyObject* value,
    /*@unused@*/ void* closure) {

  if (value == NULL) { /* deletion */
    self->x.zsource = (double)NPY_NAN;
    return 0;
  }

  note_change(self);

  return set_double("zsource", value, &self->x.zsource);
}

/***************************************************************************
 * PyWcsprm definition structures
 */

static PyGetSetDef PyWcsprm_getset[] = {
  {"alt", (getter)PyWcsprm_get_alt, (setter)PyWcsprm_set_alt, (char *)doc_alt},
  {"axis_types", (getter)PyWcsprm_get_axis_types, NULL, (char *)doc_axis_types},
  {"cd", (getter)PyWcsprm_get_cd, (setter)PyWcsprm_set_cd, (char *)doc_cd},
  {"cdelt", (getter)PyWcsprm_get_cdelt, (setter)PyWcsprm_set_cdelt, (char *)doc_cdelt},
  {"cel_offset", (getter)PyWcsprm_get_cel_offset, (setter)PyWcsprm_set_cel_offset, (char *)doc_cel_offset},
  {"cname", (getter)PyWcsprm_get_cname, (setter)PyWcsprm_set_cname, (char *)doc_cname},
  {"colax", (getter)PyWcsprm_get_colax, (setter)PyWcsprm_set_colax, (char *)doc_colax},
  {"colnum", (getter)PyWcsprm_get_colnum, (setter)PyWcsprm_set_colnum, (char *)doc_colnum},
  {"crder", (getter)PyWcsprm_get_crder, (setter)PyWcsprm_set_crder, (char *)doc_crder},
  {"crota", (getter)PyWcsprm_get_crota, (setter)PyWcsprm_set_crota, (char *)doc_crota},
  {"crpix", (getter)PyWcsprm_get_crpix, (setter)PyWcsprm_set_crpix, (char *)doc_crpix},
  {"crval", (getter)PyWcsprm_get_crval, (setter)PyWcsprm_set_crval, (char *)doc_crval},
  {"csyer", (getter)PyWcsprm_get_csyer, (setter)PyWcsprm_set_csyer, (char *)doc_csyer},
  {"ctype", (getter)PyWcsprm_get_ctype, (setter)PyWcsprm_set_ctype, (char *)doc_ctype},
  {"cubeface", (getter)PyWcsprm_get_cubeface, (setter)PyWcsprm_set_cubeface, (char *)doc_cubeface},
  {"cunit", (getter)PyWcsprm_get_cunit, (setter)PyWcsprm_set_cunit, (char *)doc_cunit},
  {"dateavg", (getter)PyWcsprm_get_dateavg, (setter)PyWcsprm_set_dateavg, (char *)doc_dateavg},
  {"dateobs", (getter)PyWcsprm_get_dateobs, (setter)PyWcsprm_set_dateobs, (char *)doc_dateobs},
  {"equinox", (getter)PyWcsprm_get_equinox, (setter)PyWcsprm_set_equinox, (char *)doc_equinox},
  {"imgpix_matrix", (getter)PyWcsprm_get_imgpix_matrix, NULL, (char *)doc_imgpix_matrix},
  {"lat", (getter)PyWcsprm_get_lat, NULL, (char *)doc_lat},
  {"latpole", (getter)PyWcsprm_get_latpole, (setter)PyWcsprm_set_latpole, (char *)doc_latpole},
  {"lattyp", (getter)PyWcsprm_get_lattyp, NULL, (char *)doc_lattyp},
  {"lng", (getter)PyWcsprm_get_lng, NULL, (char *)doc_lng},
  {"lngtyp", (getter)PyWcsprm_get_lngtyp, NULL, (char *)doc_lngtyp},
  {"lonpole", (getter)PyWcsprm_get_lonpole, (setter)PyWcsprm_set_lonpole, (char *)doc_lonpole},
  {"mjdavg", (getter)PyWcsprm_get_mjdavg, (setter)PyWcsprm_set_mjdavg, (char *)doc_mjdavg},
  {"mjdobs", (getter)PyWcsprm_get_mjdobs, (setter)PyWcsprm_set_mjdobs, (char *)doc_mjdobs},
  {"name", (getter)PyWcsprm_get_name, (setter)PyWcsprm_set_name, (char *)doc_name},
  {"naxis", (getter)PyWcsprm_get_naxis, NULL, (char *)doc_naxis},
  {"obsgeo", (getter)PyWcsprm_get_obsgeo, (setter)PyWcsprm_set_obsgeo, (char *)doc_obsgeo},
  {"pc", (getter)PyWcsprm_get_pc, (setter)PyWcsprm_set_pc, (char *)doc_pc},
  {"phi0", (getter)PyWcsprm_get_phi0, (setter)PyWcsprm_set_phi0, (char *)doc_phi0},
  {"piximg_matrix", (getter)PyWcsprm_get_piximg_matrix, NULL, (char *)doc_piximg_matrix},
  {"radesys", (getter)PyWcsprm_get_radesys, (setter)PyWcsprm_set_radesys, (char *)doc_radesys},
  {"restfrq", (getter)PyWcsprm_get_restfrq, (setter)PyWcsprm_set_restfrq, (char *)doc_restfrq},
  {"restwav", (getter)PyWcsprm_get_restwav, (setter)PyWcsprm_set_restwav, (char *)doc_restwav},
  {"spec", (getter)PyWcsprm_get_spec, NULL, (char *)doc_spec},
  {"specsys", (getter)PyWcsprm_get_specsys, (setter)PyWcsprm_set_specsys, (char *)doc_specsys},
  {"ssysobs", (getter)PyWcsprm_get_ssysobs, (setter)PyWcsprm_set_ssysobs, (char *)doc_ssysobs},
  {"ssyssrc", (getter)PyWcsprm_get_ssyssrc, (setter)PyWcsprm_set_ssyssrc, (char *)doc_ssyssrc},
  {"tab", (getter)PyWcsprm_get_tab, NULL, (char *)doc_tab},
  {"theta0", (getter)PyWcsprm_get_theta0, (setter)PyWcsprm_set_theta0, (char *)doc_theta0},
  {"velangl", (getter)PyWcsprm_get_velangl, (setter)PyWcsprm_set_velangl, (char *)doc_velangl},
  {"velosys", (getter)PyWcsprm_get_velosys, (setter)PyWcsprm_set_velosys, (char *)doc_velosys},
  /* {"wtb", (getter)PyWcsprm_get_wtb, NULL, (char *)doc_tab}, */
  {"zsource", (getter)PyWcsprm_get_zsource, (setter)PyWcsprm_set_zsource, (char *)doc_zsource},
  {NULL}
};

static PyMethodDef PyWcsprm_methods[] = {
  {"celfix", (PyCFunction)PyWcsprm_celfix, METH_NOARGS, doc_celfix},
  {"__copy__", (PyCFunction)PyWcsprm_copy, METH_NOARGS, doc_copy},
  {"cylfix", (PyCFunction)PyWcsprm_cylfix, METH_VARARGS|METH_KEYWORDS, doc_cylfix},
  {"datfix", (PyCFunction)PyWcsprm_datfix, METH_NOARGS, doc_datfix},
  {"__deepcopy__", (PyCFunction)PyWcsprm_copy, METH_O, doc_copy},
  {"fix", (PyCFunction)PyWcsprm_fix, METH_VARARGS|METH_KEYWORDS, doc_fix},
  {"get_cdelt", (PyCFunction)PyWcsprm_get_cdelt_func, METH_NOARGS, doc_get_cdelt},
  {"get_pc", (PyCFunction)PyWcsprm_get_pc_func, METH_NOARGS, doc_get_pc},
  {"get_ps", (PyCFunction)PyWcsprm_get_ps, METH_NOARGS, doc_get_ps},
  {"get_pv", (PyCFunction)PyWcsprm_get_pv, METH_NOARGS, doc_get_pv},
  {"has_cd", (PyCFunction)PyWcsprm_has_cdi_ja, METH_NOARGS, doc_has_cd},
  {"has_cdi_ja", (PyCFunction)PyWcsprm_has_cdi_ja, METH_NOARGS, doc_has_cdi_ja},
  {"has_crota", (PyCFunction)PyWcsprm_has_crotaia, METH_NOARGS, doc_has_crota},
  {"has_crotaia", (PyCFunction)PyWcsprm_has_crotaia, METH_NOARGS, doc_has_crotaia},
  {"has_pc", (PyCFunction)PyWcsprm_has_pci_ja, METH_NOARGS, doc_has_pc},
  {"has_pci_ja", (PyCFunction)PyWcsprm_has_pci_ja, METH_NOARGS, doc_has_pci_ja},
  {"is_unity", (PyCFunction)PyWcsprm_is_unity, METH_NOARGS, doc_is_unity},
  {"mix", (PyCFunction)PyWcsprm_mix, METH_VARARGS|METH_KEYWORDS, doc_mix},
  {"p2s", (PyCFunction)PyWcsprm_p2s, METH_VARARGS|METH_KEYWORDS, doc_p2s},
  {"print_contents", (PyCFunction)PyWcsprm_print_contents, METH_NOARGS, doc_print_contents},
  {"s2p", (PyCFunction)PyWcsprm_s2p, METH_VARARGS|METH_KEYWORDS, doc_s2p},
  {"set", (PyCFunction)PyWcsprm_set, METH_NOARGS, doc_set},
  {"set_ps", (PyCFunction)PyWcsprm_set_ps, METH_O, doc_set_ps},
  {"set_pv", (PyCFunction)PyWcsprm_set_pv, METH_O, doc_set_pv},
  {"spcfix", (PyCFunction)PyWcsprm_spcfix, METH_NOARGS, doc_spcfix},
  {"sptr", (PyCFunction)PyWcsprm_sptr, METH_VARARGS|METH_KEYWORDS, doc_sptr},
  {"sub", (PyCFunction)PyWcsprm_sub, METH_VARARGS|METH_KEYWORDS, doc_sub},
  {"to_header", (PyCFunction)PyWcsprm_to_header, METH_VARARGS|METH_KEYWORDS, doc_to_header},
  {"unitfix", (PyCFunction)PyWcsprm_unitfix, METH_VARARGS|METH_KEYWORDS, doc_unitfix},
  {NULL}
};

PyTypeObject PyWcsprmType = {
  #if PY3K
  PyVarObject_HEAD_INIT(NULL, 0)
  #else
  PyObject_HEAD_INIT(NULL)
  0,                            /*ob_size*/
  #endif
  "pywcs._Wcsprm",              /*tp_name*/
  sizeof(PyWcsprm),             /*tp_basicsize*/
  0,                            /*tp_itemsize*/
  (destructor)PyWcsprm_dealloc, /*tp_dealloc*/
  0,                            /*tp_print*/
  0,                            /*tp_getattr*/
  0,                            /*tp_setattr*/
  0,                            /*tp_compare*/
  (reprfunc)PyWcsprm___str__,   /*tp_repr*/
  0,                            /*tp_as_number*/
  0,                            /*tp_as_sequence*/
  0,                            /*tp_as_mapping*/
  0,                            /*tp_hash */
  0,                            /*tp_call*/
  (reprfunc)PyWcsprm___str__,   /*tp_str*/
  0,                            /*tp_getattro*/
  0,                            /*tp_setattro*/
  0,                            /*tp_as_buffer*/
  Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE, /*tp_flags*/
  doc_Wcsprm,                   /* tp_doc */
  0,                            /* tp_traverse */
  0,                            /* tp_clear */
  0,                            /* tp_richcompare */
  0,                            /* tp_weaklistoffset */
  0,                            /* tp_iter */
  0,                            /* tp_iternext */
  PyWcsprm_methods,             /* tp_methods */
  0,                            /* tp_members */
  PyWcsprm_getset,              /* tp_getset */
  0,                            /* tp_base */
  0,                            /* tp_dict */
  0,                            /* tp_descr_get */
  0,                            /* tp_descr_set */
  0,                            /* tp_dictoffset */
  (initproc)PyWcsprm_init,      /* tp_init */
  0,                            /* tp_alloc */
  PyWcsprm_new,                 /* tp_new */
};

#define CONSTANT(a) PyModule_AddIntConstant(m, #a, a)

int
_setup_wcsprm_type(
    PyObject* m) {

  if (PyType_Ready(&PyWcsprmType) < 0) {
    return -1;
  }

  Py_INCREF(&PyWcsprmType);

  wcsprintf_set(NULL);
  wcserr_enable(1);

  return (
    PyModule_AddObject(m, "_Wcsprm", (PyObject *)&PyWcsprmType) ||
    CONSTANT(WCSSUB_LONGITUDE) ||
    CONSTANT(WCSSUB_LATITUDE)  ||
    CONSTANT(WCSSUB_CUBEFACE)  ||
    CONSTANT(WCSSUB_SPECTRAL)  ||
    CONSTANT(WCSSUB_STOKES)    ||
    CONSTANT(WCSSUB_CELESTIAL) ||
    CONSTANT(WCSHDR_IMGHEAD)   ||
    CONSTANT(WCSHDR_BIMGARR)   ||
    CONSTANT(WCSHDR_PIXLIST)   ||
    CONSTANT(WCSHDR_none)      ||
    CONSTANT(WCSHDR_all)       ||
    CONSTANT(WCSHDR_CROTAia)   ||
    CONSTANT(WCSHDR_EPOCHa)    ||
    CONSTANT(WCSHDR_VELREFa)   ||
    CONSTANT(WCSHDR_CD00i00j)  ||
    CONSTANT(WCSHDR_PC00i00j)  ||
    CONSTANT(WCSHDR_PROJPn)    ||
    CONSTANT(WCSHDR_RADECSYS)  ||
    CONSTANT(WCSHDR_VSOURCE)   ||
    CONSTANT(WCSHDR_DOBSn)     ||
    CONSTANT(WCSHDR_LONGKEY)   ||
    CONSTANT(WCSHDR_CNAMn)     ||
    CONSTANT(WCSHDR_AUXIMG)    ||
    CONSTANT(WCSHDR_ALLIMG)    ||
    CONSTANT(WCSHDO_none)      ||
    CONSTANT(WCSHDO_all)       ||
    CONSTANT(WCSHDO_safe)      ||
    CONSTANT(WCSHDO_DOBSn)     ||
    CONSTANT(WCSHDO_TPCn_ka)   ||
    CONSTANT(WCSHDO_PVn_ma)    ||
    CONSTANT(WCSHDO_CRPXna)    ||
    CONSTANT(WCSHDO_CNAMna)    ||
    CONSTANT(WCSHDO_WCSNna));
}
