/*
Copyright (C) 2006  Damien Katz

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.    See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA    02110-1301, USA.

*/

// This file is the C port driver for Erlang. It provides a low overhead
// means of calling into C code, however unlike the Fabric engine, coding
// errors in this module can crash the entire Erlang server.

#include "erl_driver.h"
#include "unicode/ucol.h"
#include "unicode/ucasemap.h"
#ifndef WIN32
#include <string.h> // for memcpy
#endif

typedef struct {
    ErlDrvPort port;
    UCollator* collNoCase;
    UCollator* coll;
} couch_drv_data;

static void couch_drv_stop(ErlDrvData data)
{
    couch_drv_data* pData = (couch_drv_data*)data;
    if (pData->coll) {
        ucol_close(pData->coll);
    }
    if (pData->collNoCase) {
        ucol_close(pData->collNoCase);
    }
    driver_free((char*)pData);
}

static ErlDrvData couch_drv_start(ErlDrvPort port, char *buff)
{
    UErrorCode status = U_ZERO_ERROR;
    couch_drv_data* pData = (couch_drv_data*)driver_alloc(sizeof(couch_drv_data));

    if (pData == NULL)
        return ERL_DRV_ERROR_GENERAL;

    pData->port = port;
    pData->coll = NULL;
    pData->collNoCase = NULL;
    pData->coll = ucol_open("", &status);

    if (U_FAILURE(status)) {
        couch_drv_stop((ErlDrvData)pData);
        return ERL_DRV_ERROR_GENERAL;
    }

    pData->collNoCase = ucol_open("", &status);
    if (U_FAILURE(status)) {
        couch_drv_stop((ErlDrvData)pData);
        return ERL_DRV_ERROR_GENERAL;
    }

    ucol_setAttribute(pData->collNoCase, UCOL_STRENGTH, UCOL_PRIMARY, &status);
    if (U_FAILURE(status)) {
        couch_drv_stop((ErlDrvData)pData);
        return ERL_DRV_ERROR_GENERAL;
    }

    return (ErlDrvData)pData;
}

static int return_control_result(void* pLocalResult, int localLen, char **ppRetBuf, int returnLen)
{
    if (*ppRetBuf == NULL || localLen > returnLen) {
        *ppRetBuf = (char*)driver_alloc_binary(localLen);
        if(*ppRetBuf == NULL) {
            return -1;
        }
    }
    memcpy(*ppRetBuf, pLocalResult, localLen);
    return localLen;
}

static int couch_drv_control(ErlDrvData drv_data, unsigned int command, const char *pBuf,
             int bufLen, char **rbuf, int rlen)
{
    #define COLLATE 0
    #define COLLATE_NO_CASE 1

    couch_drv_data* pData = (couch_drv_data*)drv_data;

    UErrorCode status = U_ZERO_ERROR;
    int collResult;
    char response;
    UCharIterator iterA;
    UCharIterator iterB;
    int32_t length;

    // 2 strings are in the buffer, consecutively
    // The strings begin first with a 32 bit integer byte length, then the actual
    // string bytes follow.

    // first 32bits are the length
    memcpy(&length, pBuf, sizeof(length));
    pBuf += sizeof(length);

    // point the iterator at it.
    uiter_setUTF8(&iterA, pBuf, length);

    pBuf += length; // now on to string b

    // first 32bits are the length
    memcpy(&length, pBuf, sizeof(length));
    pBuf += sizeof(length);

    // point the iterator at it.
    uiter_setUTF8(&iterB, pBuf, length);

    if (command == COLLATE)
        collResult = ucol_strcollIter(pData->coll, &iterA, &iterB, &status);
    else if (command == COLLATE_NO_CASE)
        collResult = ucol_strcollIter(pData->collNoCase, &iterA, &iterB, &status);
    else
        return -1;
    
    if (collResult < 0)
        response = 0; //lt
    else if (collResult > 0)
        response = 1; //gt
    else
        response = 2; //eq

    return return_control_result(&response, sizeof(response), rbuf, rlen);
}

ErlDrvEntry couch_driver_entry = {
        NULL,                                               /* F_PTR init, N/A */
        couch_drv_start,                    /* L_PTR start, called when port is opened */
        couch_drv_stop,                     /* F_PTR stop, called when port is closed */
        NULL,                   /* F_PTR output, called when erlang has sent */
        NULL,                                               /* F_PTR ready_input, called when input descriptor ready */
        NULL,                                               /* F_PTR ready_output, called when output descriptor ready */
        "couch_erl_driver",                          /* char *driver_name, the argument to open_port */
        NULL,                                               /* F_PTR finish, called when unloaded */
        NULL,                       /* Not used */
        couch_drv_control,               /* F_PTR control, port_command callback */
        NULL,                                               /* F_PTR timeout, reserved */
        NULL                                                /* F_PTR outputv, reserved */
};

DRIVER_INIT(couch_erl_driver) /* must match name in driver_entry */
{
        return &couch_driver_entry;
}
