/* 	      Perforce Defect Tracking Integration Project
	       <http://www.ravenbrook.com/project/p4dti/>

      TEAMTRACK-SERVER.CPP -- PYTHON WRAPPER ROUND TSSERVER CLASS

	      Gareth Rees, Ravenbrook Limited, 2000-08-08


   1. INTRODUCTION

   This file implements a Python interface to TSServer objects
   (connections to TeamTrack servers).  Each TSServer object is
   represented in Python by an object belonging to the teamtrack_server
   type.

   See [GDR 2000-08-08] for the design.

   The intended readership is project developers.

   This document is not confidential. */


#include "teamtrack-module.h"
#include "teamtrack-server.h"
#include "teamtrack-record.h"
#include "TSField.C"


/* teamtrack_server_new(s, delete_p) returns a Python wrapper for the TSServer
   object s, or NULL if there is an error.  Iff delete_p is true, then the
   TSServer object will be deleted when the Python wrapper is deleted. */

PyObject *
teamtrack_server_new(TSServer *s, int delete_p) {
  teamtrack_server *self =
    PyObject_NEW(teamtrack_server, &teamtrack_server_type);
  if (self == NULL) {
    return NULL;
  }
  self->s = s;
  self->delete_p = delete_p;

  /* Try to work out which table contains the cases; see [GDR
     2000-08-08].  If this doesn't work we will assume the table id is
     given by TS_TBLID_CASES and we'll leave the name as NULL, meaning
     "TS_CASES". */
  self->case_table_id = TS_TBLID_CASES;
  self->case_table_name = NULL;
  TSRecord r(TS_TBLID_TABLES, s);
  if (s->ReadRecord(&r, TS_TBLID_CASES) != TS_OK
      && s->ReadRecordWithWhere(&r, "TS_SNAME LIKE 'Issue'") == TS_OK) {
    TSField *f = r.fieldList.FindFieldByName("ID", r.fieldType);
    TSField *g = r.fieldList.FindFieldByName("DBNAME", r.fieldType);
    if (f && f->dataType == TS_DATATYPE_INTEGER
	&& g && g->dataType == TS_DATATYPE_STRING) {
      self->case_table_id = f->intValue;
      self->case_table_name = new TSString(g->charValue);
    }
  }
  return (PyObject *)self;
}


/* teamtrack_record_list_to_python_list(tts, ts_records) returns a Python list
   containing a teamtrack_record object for each TSRecord object in the
   ts_records argument.  The tts argument is the teamtrack_server object to
   which the record list belongs. */

static PyObject *
teamtrack_record_list_to_python_list(teamtrack_server *tts, TSRecordList *ts_records) {
  int n_records = ts_records->Length();
  PyObject *py_records = PyList_New(n_records);
  if (py_records == NULL) {
    return NULL;
  }
  int i;			/* Position in Python list */
  TSPosition *p;		/* Position in TSRecordList */
  for (i = 0, p = ts_records->GetFirst();
       i < n_records && p;
       ++i, p = ts_records->GetNext(p)) {
    TSRecord *ts_record = ts_records->GetAt(p);
    if (ts_record == NULL) {
      /* Can't just 'raise' here, since py_records would leak. */
      teamtrack_set_error(tts->s);
      break;
    }
    /* I believe it's safe to pass ownership of the TSRecord objects to Python,
       because the TSRecordList class doesn't actually delete the records in it
       unless you call TSList::EmptyAndDestroyList.  See TSList.C. */
    PyObject *py_record = teamtrack_record_new(ts_record, tts, 1);
    if (py_record == NULL) {
      break;
    }
    PyList_SetItem(py_records, i, py_record);
    /* No need to decref py_record since PyList_SetItem doesn't incref it */
  }
  if (PyErr_Occurred()) {
    Py_DECREF(py_records);	/* Delete py_records. */
    return NULL;
  }
  return py_records;
}


/* Instance methods */

static PyObject *
teamtrack_server_case_table_id(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  return PyInt_FromLong((long)tts->case_table_id);
}

static PyObject *
teamtrack_server_case_table_name(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  if (tts->case_table_name != NULL) {
    return PyString_FromString(tts->case_table_name->GetBuffer());
  } else {
    return PyString_FromString("TS_CASES");
  }
}

static PyObject *
teamtrack_server_delete_record(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  int table_id, record_id;
  if (!PyArg_ParseTuple(args, "ii", &table_id, &record_id)) {
    return NULL;
  }
  int rc = tts->s->DeleteRecord(table_id, record_id);
  teamtrack_try(rc, tts->s, NULL);
  Py_INCREF(Py_None); /* It will be on Python's stack when returned. */
  return Py_None;
}

static PyObject *
teamtrack_server_new_record(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  int table_id;
  if (!PyArg_ParseTuple(args, "i", &table_id)) {
    return NULL;
  }
  TSRecord *r = new TSRecord(table_id, tts->s);
  teamtrack_assert(r, NULL);
  return teamtrack_record_new(r, tts, 1);
}

static PyObject *
teamtrack_server_query(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  int table_id;
  char *where_clause;
  if (!PyArg_ParseTuple(args, "is", &table_id, &where_clause)) {
    return NULL;
  }
  TSRecordList ts_records;
  int rc = tts->s->ReadRecordListWithWhere(&ts_records, table_id,
					   where_clause);
  teamtrack_try(rc, tts->s, NULL);
  return teamtrack_record_list_to_python_list(tts, &ts_records);
}

static PyObject *
teamtrack_server_read_record(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  int table_id, record_id;
  if (!PyArg_ParseTuple(args, "ii", &table_id, &record_id)) {
    return NULL;
  }
  TSRecord *r = new TSRecord(table_id, tts->s);
  teamtrack_assert(r, NULL);
  if (tts->s->ReadRecord(r, record_id) != TS_OK) {
    teamtrack_set_error(tts->s);
    delete r;
    return NULL;
  }
  return teamtrack_record_new(r, tts, 1);
}

static PyObject *
teamtrack_server_read_state_list(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  int workflow_id;
  int include_parent = 0;
  if (!PyArg_ParseTuple(args, "i|i", &workflow_id, &include_parent)) {
    return NULL;
  }
  TSRecordList ts_records;
  int rc = tts->s->ReadStateList(&ts_records, workflow_id, include_parent);
  teamtrack_try(rc, tts->s, NULL);
  return teamtrack_record_list_to_python_list(tts, &ts_records);
}

static PyObject *
teamtrack_server_read_transition_list(PyObject *self, PyObject *args) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  int project_id;
  if (!PyArg_ParseTuple(args, "i", &project_id)) {
    return NULL;
  }
  TSRecordList ts_records;
  int rc = tts->s->ReadTransitionList(&ts_records, project_id);
  teamtrack_try(rc, tts->s, NULL);
  return teamtrack_record_list_to_python_list(tts, &ts_records);
}

static struct PyMethodDef teamtrack_server_methods[] = {
  { "case_table_id",        teamtrack_server_case_table_id,        1 },
  { "case_table_name",      teamtrack_server_case_table_name,      1 },
  { "delete_record",        teamtrack_server_delete_record,        1 },
  { "new_record",           teamtrack_server_new_record,           1 },
  { "query",                teamtrack_server_query,                1 },
  { "read_record",          teamtrack_server_read_record,          1 },
  { "read_state_list",      teamtrack_server_read_state_list,      1 },
  { "read_transition_list", teamtrack_server_read_transition_list, 1 },
  { NULL, NULL, 0 }		/* End of methods */
};


/* Basic Python operators */

static void
teamtrack_server_dealloc(PyObject *self) {
  if (!teamtrack_server_p(self)) {
    Py_FatalError("teamtrack_server_dealloc called on object that's not "
		  "a teamtrack_server.");
  }
  teamtrack_server *tts = (teamtrack_server *)self;
  if (tts->case_table_name != NULL) {
    delete tts->case_table_name;
  }
  if (tts->delete_p) {
    delete tts->s;
  }
  PyMem_DEL(self);
}

static PyObject *
teamtrack_server_repr(PyObject *self) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  teamtrack_server *tts = (teamtrack_server *)self;
  return PyString_FromString("<TeamShare server>");
}

static PyObject *
teamtrack_server_getattr(PyObject *self, char *name) {
  teamtrack_check_type(teamtrack_server, self, NULL);
  return Py_FindMethod(teamtrack_server_methods, self, name);
}


/* Create the teamtrack.server class */

PyTypeObject teamtrack_server_type = {
  /* Type header */
  PyObject_HEAD_INIT(&PyType_Type)
  0,				/* ob_size */
  "server",			/* tp_name */
  sizeof(teamtrack_server),	/* tp_basicsize */
  0,				/* tp_itemsize */

  /* Basic methods */
  teamtrack_server_dealloc,
  NULL,				/* print */
  teamtrack_server_getattr,
  NULL,				/* setatts */
  NULL,				/* compare */
  teamtrack_server_repr,

  /* Type categories */
  NULL,				/* number operators */
  NULL,				/* sequence operators */
  NULL,				/* mapping operators */

  /* Other methods are NULL: see Python's object.h for details */
};


/* A. REFERENCES

   [GDR 2000-08-08] "Python interface to TeamTrack: design"; Gareth
   Rees; Ravenbrook Limited; 2000-08-08;
   <http://www.ravenbrook.com/project/p4dti/version/1.4/design/python-teamtrack-interface/>.


   B. DOCUMENT HISTORY

   2000-08-08 GDR Created.

   2000-08-29 GDR Changed "tTrack" to "TeamTrack" throughout.

   1976 2000-08-31 GDR Removed obsolete read_case method from the server class.
   Added new_record method to the server class.

   2000-08-31 GDR Added delete_record method to server class.

   2000-09-06 GDR Added add_field method to teamtrack server class.

   2000-09-07 GDR Moved add_field() method implementation from server class to
   record class.

   2000-10-13 GDR Added read_state_list and read_transition_list to the
   teamtrack server object.  The former returns a list of available states in a
   workflow.  The latter returns a list of available transitions in a project.
   It is more maintainable (and more likely to be correct) to use these
   functions to query TeamTrack's workflow model than to try to duplicate
   TeamTrack's logic using only the schema documentation.

   2001-06-26 GDR Added methods case_table_name() and case_table_id().

   2001-06-28 GDR The case table id and name are now worked out by the
   procedure documented in [GDR 2000-08-08].


   C. COPYRIGHT AND LICENCE

   This file is copyright (c) 2001 Perforce Software, Inc.  All rights
   reserved.

   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.

   THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
   "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 THE COPYRIGHT
   HOLDERS AND CONTRIBUTORS 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.


   $Id: //info.ravenbrook.com/project/p4dti/branch/2002-10-11/bugzilla-win32/code/python-teamtrack-interface/teamtrack-server.cpp#1 $ */

