/* teamtrack-module.cpp -- Python extension interfacing to TeamTrack
   Gareth Rees, Ravenbrook Limited, 2000-08-02
   $Id: //info.ravenbrook.com/project/p4dti/branch/2000-12-07/document-history/code/python-teamtrack-interface/teamtrack-module.cpp#2 $

   See "Python interface to TeamTrack: design"
   <master/design/python-teamtrack-interface/> for the design. */

/* Copyright 2000 Ravenbrook Limited.  This document is provided "as is",
   without any express or implied warranty. In no event will the authors
   be held liable for any damages arising from the use of this document.
   You may make and distribute copies and derivative works of this
   document provided that (1) you do not charge a fee for this document or
   for its distribution, and (2) you retain as they appear all copyright
   and licence notices and document history entries, and (3) you append
   descriptions of your modifications to the document history. */

#include "teamtrack-module.h"
#include "teamtrack-server.h"
#include "TSServer.h"


/* Objects representing Python exceptions. They are assigned in
   initteamtrack(). */

/* teamtrack_error represents errors that represents errors generated by this
   interface module.  In Python it's called teamtrack.error. */

PyObject *teamtrack_error;

/* teamtrack_tsapi_error represents errors generated by the TeamShare API.  In
   Python it's called teamtrack.tsapi_error. */

static PyObject *teamtrack_tsapi_error;


/* teamtrack_set_error(server) sets the current Python error to the most recent
   TeamTrack error for the given TeamShare server. */

void teamtrack_set_error(TSServer *server) {
  char *error_name;
  switch(TSGetLastError()) {
    case TS_ERROR: error_name = "ERROR"; break;
    case TS_INVALID_DATA_TYPE: error_name = "INVALID_DATA_TYPE"; break;
    case TS_NO_PERMISSION: error_name = "NO_PERMISSION"; break;
    case TS_NO_SUCH_FIELD: error_name = "NO_SUCH_FIELD"; break;
    case TS_MEMORY_ERROR: error_name = "MEMORY_ERROR"; break;
    case TS_SOCKET_READ_ERROR: error_name = "SOCKET_READ_ERROR"; break;
    case TS_SOCKET_WRITE_ERROR: error_name = "SOCKET_WRITE_ERROR"; break;
    case TS_SOCKET_CONNECT_FAILED: error_name = "SOCKET_CONNECT_FAILED"; break;
    case TS_SOCKET_CREATE_FAILED: error_name = "SOCKET_CREATE_FAILED"; break;
    case TS_INVALID_DATATYPE: error_name = "INVALID_DATATYPE"; break;
    case TS_INVALID_USER: error_name = "INVALID_USER"; break;
    case TS_NO_RESULTS: error_name = "NO_RESULTS"; break;
    case TS_SERVER_ERROR: error_name = "SERVER_ERROR"; break;
    case TS_INVALID_VERSION: error_name = "INVALID_VERSION"; break;
    default: error_name = "(unknown)"; break;
  }
  PyObject *separator = PyString_FromString(": ");
  const char *ts_message = server->GetLastErrorMessage();
  PyObject *error_message;
  if (ts_message != NULL) {
    error_message = PyString_FromString(ts_message);
  } else {
    error_message = PyString_FromString("(no message from the TeamShare API)");
  }
  PyObject *message = PyString_FromString(error_name);
  PyString_ConcatAndDel(&message, separator);
  PyString_ConcatAndDel(&message, error_message);
  PyErr_SetObject(teamtrack_tsapi_error, message);
  Py_XDECREF(message); /* Owned by Python's exception handler. */
}


/* teamtrack.connect(user,password,hostname) connects to a TeamShare server on
   hostname with the given user and password and returns an object representing
   that server. */

static PyObject *
teamtrack_connect(PyObject *self, PyObject *args) {
  char *user;
  char *password;
  char *hostname;
  if (!PyArg_ParseTuple(args, "sss", &user, &password, &hostname)) {
    return NULL;
  }
  TSServer *s = new TSServer;
  if (s == NULL) {
    return NULL;
  }
  if (s->Connect(user, password, hostname) != TS_OK) {
    teamtrack_set_error(s);
    delete s;
    return NULL;
  }
  return teamtrack_server_new(s, 1);
}


static PyMethodDef teamtrack_methods[] = {
  { "connect", teamtrack_connect, METH_VARARGS },
  { NULL, NULL }		/* End of methods. */
};


typedef struct {
  char *name;
  int id;
} string_to_int_map;


/* teamtrack_make_map(c_map, size, name, dict) turns the C map c_map (with size
   items) into a Python map, and installs it with the given name in the given
   dictionary. */

void teamtrack_make_map(string_to_int_map c_map[], size_t size,
			char *name, PyObject *dict) {
  PyObject *py_map = PyDict_New();
  PyDict_SetItemString(dict, name, py_map);
  for (int i = 0; i < size; ++i) {
    PyObject *id = PyInt_FromLong((long)c_map[i].id);
    PyDict_SetItemString(py_map, c_map[i].name, id);
    Py_XDECREF(id); /* Owned by py_map. */
  }
  Py_XDECREF(py_map); /* Owned by dict. */
}


/* teamtrack_table[] is used to initialize the teamtrack.table dictionary that
   maps table name to table id.  The table identifiers appear in TSDef.h. */

string_to_int_map teamtrack_table[] = {
  { "CASES", TS_TBLID_CASES },
  { "CHANGES", TS_TBLID_CHANGES },
  { "FIELDS", TS_TBLID_FIELDS },
  { "GROUPS", TS_TBLID_GROUPS },
  { "LICENSES", TS_TBLID_LICENSES },
  { "MEMBERS", TS_TBLID_MEMBERS },
  { "PRIVILEGES", TS_TBLID_PRIVILEGES },
  { "PROJECTS", TS_TBLID_PROJECTS },
  { "PROJECTSELECTIONS", TS_TBLID_PROJECTSELECTIONS },
  { "PROPERTIES", TS_TBLID_PROPERTIES },
  { "REPORTS", TS_TBLID_REPORTS },
  { "SELECTIONS", TS_TBLID_SELECTIONS },
  { "STATES", TS_TBLID_STATES },
  { "TRANSISSUETYPES", TS_TBLID_TRANSISSUETYPES },
  { "SYSTEMINFO", TS_TBLID_SYSTEMINFO },
  { "TRANSITIONS", TS_TBLID_TRANSITIONS },
  { "USERS", TS_TBLID_USERS },
  { "TABLES", TS_TBLID_TABLES },
  { "ATTACHMENTS", TS_TBLID_ATTACHMENTS },
  { "FOLDERS", TS_TBLID_FOLDERS },
  { "FOLDERITEMS", TS_TBLID_FOLDERITEMS },
  { "FOLDERCOLUMNS", TS_TBLID_FOLDERCOLUMNS },
  { "VCACTIONS", TS_TBLID_VCACTIONS },
  { "PROJECTTRANSITIONS", TS_TBLID_PROJECTTRANSITIONS },
  { "NOTIFICATIONS", TS_TBLID_NOTIFICATIONS },
  { "NOTIFICATIONRULES", TS_TBLID_NOTIFICATIONRULES },
  { "NOTIFICATIONCONDITIONS", TS_TBLID_NOTIFICATIONCONDITIONS },
  { "NOTIFICATIONEVENTS", TS_TBLID_NOTIFICATIONEVENTS },
  { "NOTIFICATIONFIELDS", TS_TBLID_NOTIFICATIONFIELDS },
  { "NOTIFICATIONMESSAGES", TS_TBLID_NOTIFICATIONMESSAGES },
  { "NOTIFICATIONPERMISSIONS", TS_TBLID_NOTIFICATIONPERMISSIONS },
  { "NOTIFICATIONSUBSCRIPTIONS", TS_TBLID_NOTIFICATIONSUBSCRIPTIONS },
  { "MACROS", TS_TBLID_MACROS },
  { "WORKFLOWS", TS_TBLID_WORKFLOWS },
  { "FIELDORDERINGS", TS_TBLID_FIELDORDERINGS },
  { "INCIDENTS", TS_TBLID_INCIDENTS },
  { "COMPANIES", TS_TBLID_COMPANIES },
  { "CONTACTS", TS_TBLID_CONTACTS },
  { "MERCHANDISE", TS_TBLID_MERCHANDISE },
  { "SERVICEAGREEMENTS", TS_TBLID_SERVICEAGREEMENTS },
  { "PROBLEMS", TS_TBLID_PROBLEMS },
  { "RESOLUTIONS", TS_TBLID_RESOLUTIONS },
  { "PRODUCTS", TS_TBLID_PRODUCTS },
  { "KEYWORDS", TS_TBLID_KEYWORDS },
  { "PRODUCTUSAGES", TS_TBLID_PRODUCTUSAGES },
  { "KEYWORDUSAGES", TS_TBLID_KEYWORDUSAGES },
  { "TRANSTRIGGERS", TS_TBLID_TRANSTRIGGERS },
  { "TRANSTRIGGERSTATES", TS_TBLID_TRANSTRIGGERSTATES },
  { "TRANSTRIGGERTRANSITIONS", TS_TBLID_TRANSTRIGGERTRANSITIONS },
};


/* teamtrack_field_type[] is used to initialize the teamtrack.field_type
   dictionary that maps field type name to field type id.  The field type
   identifiers appear in TSDef.h. */

string_to_int_map teamtrack_field_type[] = {
  { "NUMERIC", TS_FLDTYPE_NUMERIC },
  { "TEXT", TS_FLDTYPE_TEXT },
  { "MEMO", TS_FLDTYPE_MEMO },
  { "DATETIME", TS_FLDTYPE_DATETIME },
  { "SELECTION", TS_FLDTYPE_SELECTION },
  { "BINARY", TS_FLDTYPE_BINARY },
  { "STATE", TS_FLDTYPE_STATE },
  { "USER", TS_FLDTYPE_USER },
  { "PROJECT", TS_FLDTYPE_PROJECT },
  { "SUMMATION", TS_FLDTYPE_SUMMATION },
  { "MULTIPLE_SELECTION", TS_FLDTYPE_MULTIPLE_SELECTION },
  { "CONTACT", TS_FLDTYPE_CONTACT },
  { "COMPANY", TS_FLDTYPE_COMPANY },
  { "INCIDENT", TS_FLDTYPE_INCIDENT },
  { "PRODUCT", TS_FLDTYPE_PRODUCT },
  { "SERVICEAGREEMENT", TS_FLDTYPE_SERVICEAGREEMENT },
  { "FOLDER", TS_FLDTYPE_FOLDER },
  { "KEYWORDLIST", TS_FLDTYPE_KEYWORDLIST },
  { "PRODUCTLIST", TS_FLDTYPE_PRODUCTLIST },
  { "PROBLEM", TS_FLDTYPE_PROBLEM },
  { "RESOLUTION", TS_FLDTYPE_RESOLUTION },
  { "MERCHANDISE", TS_FLDTYPE_MERCHANDISE },
};


extern "C" void TEAMTRACK_EXPORTED
initteamtrack() {
  /* Create the "teamtrack" module. */
  PyObject *m = Py_InitModule("teamtrack", teamtrack_methods);
  PyObject *d = PyModule_GetDict(m);

  /* Create an object to represent errors in the Python interface to TeamTrack
     (a string with the value "TeamTrack interface error") and give it the
     Python name "teamtrack.error". */
  teamtrack_error = PyString_FromString("TeamTrack interface error");
  PyDict_SetItemString(d, "error", teamtrack_error);
  /* Don't decref teamtrack_error since we continue to own it through a global
     variable. */

  /* Create an object to represent TeamTrack errors (a string with the value
     "TeamTrack error") and give it the Python name "teamtrack.error". */
  teamtrack_tsapi_error = PyString_FromString("TeamShare API error");
  PyDict_SetItemString(d, "tsapi_error", teamtrack_tsapi_error);
  /* Don't decref teamtrack_tsapi_error since we continue to own it through a
     global variable. */

  /* Make teamtrack.tables (a mapping from table name to table id). */
  teamtrack_make_map(teamtrack_table, sizeof(teamtrack_table)
		     / sizeof(teamtrack_table[0]), "table", d);

  /* Make teamtrack.tables (a mapping from table name to table id). */
  teamtrack_make_map(teamtrack_field_type, sizeof(teamtrack_field_type)
		     / sizeof(teamtrack_field_type[0]),
		     "field_type", d);

  /* Check for errors that occurred during module initialization. */
  if (PyErr_Occurred()) {
    Py_FatalError("Can't initialise teamtrack module.");
  }

#if defined(WIN32)
  if (TSInitializeWinsock() != TS_OK) {
    Py_FatalError("Can't initialise Winsock.");
  }
#endif /* defined(WIN32) */
}


/* B. Document History

   2000-08-02 GDR Created.

   2000-08-07 GDR Function ttrack.connect() passes arguments correctly to
   TSServer::Connect.  New function ttrack.read_case() for reading a case as a
   string.  Module initialization calls TSInitializeWinsock().

   2000-08-08 GDR New dictionary ttrack.tables, which maps TeamShare table name
   to table id.  Call to TSInitializeWinsock in ttrackmodule.cpp wrapped with
   #ifdef WIN32.

   2000-08-23 GDR ttrack_init isn't so paranoid about its error checking --
   Python API functions returns 0 if any of their arguments are 0 so you can
   generally do a bunch of Python API calls without checking all the return
   values, and then call PyErr_Occurred to see if anything went wrong.  This
   makes the code more readable at the cost of having to check the
   justification given above.

   2000-09-07 GDR Added teamtrack.field_type map.  Wrote function
   teamtrack_make_map to build these maps.

*/

