/*
 * No rights reserved.  This code is public domain.
 *
 * - Rob Mayoff <mayoff@dqd.com>
 */

static char rcsid[] = "@(#) $Id: utils8.c,v 1.13 2005/05/28 06:19:41 rob2 Exp $";

#include "ns.h"
#include "nsdb.h"
#include "utils.h"

int Ns_ModuleVersion = 1;

static Tcl_ObjCmdProc unlistCmd;
static Tcl_ObjCmdProc nssetToListCmd;
static Tcl_ObjCmdProc rowsToXmlCmd;

static int
unlistCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj * CONST objv[])
{
    Tcl_Obj *list;
    int listc;
    Tcl_Obj **listv;
    int code;
    int i;

    if (objc < 2) {
	Tcl_AppendResult(interp,
	    "usage: ", Tcl_GetStringFromObj(objv[0], NULL),
	    " list ?var ...?", NULL);
	return TCL_ERROR;
    }

    else if (objc == 2) {
	return TCL_OK;
    }

    list = objv[1];
    objc -= 2;
    objv += 2;

    code = Tcl_ListObjGetElements(interp, list, &listc, &listv);
    if (code != TCL_OK)
	return TCL_ERROR;

    if (listc > objc)
	listc = objc;

    for (i = 0; i < listc; i++) {
	Tcl_Obj *value = Tcl_ObjSetVar2(interp, objv[i], NULL, listv[i],
	    TCL_PARSE_PART1 | TCL_LEAVE_ERR_MSG);
	if (value == NULL)
	    return TCL_ERROR;
    }

    if (i < objc) {
	Tcl_Obj *emptyObj = Tcl_NewObj();
	for ( ; i < objc; i++) {
	    Tcl_Obj *value = Tcl_ObjSetVar2(interp, objv[i], NULL,
		emptyObj, TCL_PARSE_PART1 | TCL_LEAVE_ERR_MSG);
	    if (value == NULL)
		return TCL_ERROR;
	}
    }

    return TCL_OK;
}

static int
nssetToListCmd(ClientData clientData, Tcl_Interp *interp,
    int objc, Tcl_Obj * CONST objv[])
{
    Ns_Set *set;
    Tcl_Obj *list;
    int i, size;

    if (objc != 3) {
	Tcl_AppendResult(interp, "usage: ",
	    Tcl_GetStringFromObj(objv[0], NULL), " setid listvar", NULL);
	return TCL_ERROR;
    }

    if (Ns_TclGetSet2(interp, Tcl_GetStringFromObj(objv[1], NULL), &set)
	    == TCL_ERROR) {
	return TCL_ERROR;
    }

    list = Tcl_NewListObj(0, NULL);

    for (i = 0, size = Ns_SetSize(set); i < size; i++) {
	Tcl_ListObjAppendElement(interp, list,
	    Tcl_NewStringObj(Ns_SetValue(set, i), -1));
    }

    Tcl_ObjSetVar2(interp, objv[2], NULL, list, TCL_PARSE_PART1);

    return TCL_OK;
}

static int
reportDbFailure(Tcl_Interp* interp, Ns_DbHandle* db, char* cmd)
{
    Tcl_AppendResult(interp, cmd, " failed", NULL);
    if (db->cExceptionCode[0] != '\0') {
	Tcl_AppendResult(interp, " (exception ",
	    db->cExceptionCode, NULL);
	if (Ns_DStringLength(&db->dsExceptionMsg) > 0) {
	    Tcl_AppendResult(interp, ", \"",
		Ns_DStringValue(&db->dsExceptionMsg),
		"\"", NULL);
	}
	Tcl_AppendResult(interp, ")", NULL);
    }
    return TCL_ERROR;
}

static void
addTextAndEscape(char** beginp, char* end,
    char* escape, int escapeLen, Tcl_Obj* obj)
{
    if (end != *beginp)
	Tcl_AppendToObj(obj, *beginp, end - *beginp);
    *beginp = end + 1;

    if (escapeLen != 0)
	Tcl_AppendToObj(obj, escape, escapeLen);
}

static void
escapeXmlText(char* s, Tcl_Obj* xml)
{
    char* p;
    for (p = s; *p != '\000'; ++p) {
	// Invariant: everything before s has been added to xml;
	// s...p have not been added to xml yet;
	// s...p contain no funny chars
	switch (*p) {
	    case '<':
		addTextAndEscape(&s, p, "&lt;", 4, xml);
		break;

	    case '>':
		addTextAndEscape(&s, p, "&gt;", 4, xml);
		break;

	    case '&':
		addTextAndEscape(&s, p, "&amp;", 5, xml);
		break;

	    case '\'':
		addTextAndEscape(&s, p, "&apos;", 6, xml);
		break;

	    case '"':
		addTextAndEscape(&s, p, "&quot;", 6, xml);
		break;
	}
    }

    addTextAndEscape(&s, p, 0, 0, xml);
}

static void
rowToXml(Ns_Set* row, Tcl_Obj* xml)
{
    int i;
    int size = Ns_SetSize(row);
    Tcl_AppendToObj(xml, "<r>", 3);
    for (i = 0; i < size; ++i) {
	char* key = Ns_SetKey(row, i);
	Tcl_AppendStringsToObj(xml, "<", key, ">", NULL);
	escapeXmlText(Ns_SetValue(row, i), xml);
	Tcl_AppendStringsToObj(xml, "</", key, ">", NULL);
    }
    Tcl_AppendToObj(xml, "</r>", 4);
}

static int
rowsToXml(Tcl_Interp* interp, Ns_DbHandle* db, Ns_Set* row)
{
    Tcl_Obj* xml;
    xml = Tcl_NewStringObj("<table>", -1);

    while (1) {
	int rc = Ns_DbGetRow(db, row);

	if (rc == NS_ERROR) {
	    Tcl_DecrRefCount(xml);
	    return reportDbFailure(interp, db, "dqd_rowsToXml");
	}

	if (rc == NS_END_DATA)
	    break;

	rowToXml(row, xml);
    }

    Tcl_AppendToObj(xml, "</table>", -1);
    Tcl_SetObjResult(interp, xml);
    return TCL_OK;
}

static int
rowsToXmlCmd(ClientData clientData, Tcl_Interp* interp,
    int objc, Tcl_Obj* CONST objv[])
{
    Ns_DbHandle* db;
    Ns_Set* row;

    if (objc != 3) {
	Tcl_AppendResult(interp, "usage: ",
	    Tcl_GetString(objv[0]), " dbId row", NULL);
	return TCL_ERROR;
    }

    if (Ns_TclDbGetHandle(interp, Tcl_GetString(objv[1]), &db) != TCL_OK)
	return TCL_ERROR;

    if (Ns_TclGetSet2(interp, Tcl_GetString(objv[2]), &row) != TCL_OK)
	return TCL_ERROR;

    return rowsToXml(interp, db, row);
}

static void
escapeJavaScriptText(char* s, Tcl_Obj* js)
{
    char* p;
    for (p = s; *p != '\000'; ++p) {
	// Invariant: everything before s has been added to js;
	// s...p have not been added to js yet;
	// s...p contain no funny chars
	switch (*p) {
	    case '\'':
		addTextAndEscape(&s, p, "\\'", 2, js);
		break;

	    case '\\':
		addTextAndEscape(&s, p, "\\\\", 2, js);
		break;

	    case '\n':
		addTextAndEscape(&s, p, "\\n", 2, js);
	}
    }

    addTextAndEscape(&s, p, 0, 0, js);
}

static void
columnToJavaScript(Ns_Set* row, int column, Tcl_Obj* js)
{
    escapeJavaScriptText(Ns_SetKey(row, column), js);
    Tcl_AppendToObj(js, ":'", 2);
    escapeJavaScriptText(Ns_SetValue(row, column), js);
    Tcl_AppendToObj(js, "'", 1);
}

static int
rowToJavaScript(Tcl_Interp* interp, Ns_DbHandle* db, Ns_Set* row, Tcl_Obj* js)
{
    int rc = Ns_DbGetRow(db, row);
    if (rc == NS_ERROR) {
	return reportDbFailure(interp, db, "rowToJavaScript");
    }

    else if (rc == NS_END_DATA) {
	return TCL_BREAK;
    }

    else {
	int i;
    	int size = Ns_SetSize(row);

	Tcl_AppendToObj(js, "{", 1);
	if (size > 0) {
	    columnToJavaScript(row, 0, js);
	    for (i = 1; i < size; ++i) {
		Tcl_AppendToObj(js, ",", 1);
		columnToJavaScript(row, i, js);
	    }
	}
	Tcl_AppendToObj(js, "}", 1);

	return TCL_OK;
    }
}

static int
rowToJavaScriptCmd(ClientData clientData, Tcl_Interp* interp,
    int objc, Tcl_Obj* CONST objv[])
{
    Ns_DbHandle* db;
    Ns_Set* row;

    if (objc != 3) {
	Tcl_AppendResult(interp, "usage: ",
	    Tcl_GetString(objv[0]), " dbId row", NULL);
	return TCL_ERROR;
    }

    if (Ns_TclDbGetHandle(interp, Tcl_GetString(objv[1]), &db) != TCL_OK)
	return TCL_ERROR;

    if (Ns_TclGetSet2(interp, Tcl_GetString(objv[2]), &row) != TCL_OK)
	return TCL_ERROR;

    Tcl_Obj* js = Tcl_NewObj();
    int rc = rowToJavaScript(interp, db, row, js);

    if (rc == TCL_OK) {
	Tcl_SetObjResult(interp, js);
	return TCL_OK;
    }

    else {
	Tcl_DecrRefCount(js);

	if (rc == TCL_BREAK) {
	    Tcl_AppendResult(interp, Tcl_GetString(objv[0]),
		" failed: no rows left", NULL);
	};

	return TCL_ERROR;
    }
}

static int
rowsToJavaScript(Tcl_Interp* interp, Ns_DbHandle* db, Ns_Set* row)
{
    Tcl_Obj* js = Tcl_NewStringObj("[", 1);
    int needs_comma = 0;

    while (1) {
	if (needs_comma) Tcl_AppendToObj(js, ",", 1);
	else needs_comma = 1;

	int rc = rowToJavaScript(interp, db, row, js);

	if (rc == TCL_ERROR) {
	    Tcl_DecrRefCount(js);
	    return rc;
	}

	else if (rc == TCL_BREAK)
	    break;
    }

    Tcl_AppendToObj(js, "]", 1);
    Tcl_SetObjResult(interp, js);
    return TCL_OK;
}

static int
rowsToJavaScriptCmd(ClientData clientData, Tcl_Interp* interp,
    int objc, Tcl_Obj* CONST objv[])
{
    Ns_DbHandle* db;
    Ns_Set* row;

    if (objc != 3) {
	Tcl_AppendResult(interp, "usage: ",
	    Tcl_GetString(objv[0]), " dbId row", NULL);
	return TCL_ERROR;
    }

    if (Ns_TclDbGetHandle(interp, Tcl_GetString(objv[1]), &db) != TCL_OK)
	return TCL_ERROR;

    if (Ns_TclGetSet2(interp, Tcl_GetString(objv[2]), &row) != TCL_OK)
	return TCL_ERROR;

    return rowsToJavaScript(interp, db, row);
}

static int
tclInterpInit(Tcl_Interp *interp, void *context) {
    addNonversionedCommands(interp);
    Tcl_CreateObjCommand(interp, "unlist", unlistCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "dqd_nssetToList", nssetToListCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "dqd_rowsToXml", rowsToXmlCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "dqd_rowToJavaScript",
	rowToJavaScriptCmd, NULL, NULL);
    Tcl_CreateObjCommand(interp, "dqd_rowsToJavaScript",
	rowsToJavaScriptCmd, NULL, NULL);
    return NS_OK;
}

int
Ns_ModuleInit(char *hServer, char *hModule)
{
    Ns_Log(Notice, "dqd_utils version 1.7 for Tcl 8");
    detachInit(hServer, hModule);
    return (Ns_TclInitInterps(hServer, tclInterpInit, NULL));
}

