/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
 *
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 *
 * The Original Code is Basic Socket Library
 *
 * The Initial Developer of the Original Code is New Dimensions Consulting,
 * Inc. Portions created by New Dimensions Consulting, Inc. are
 * Copyright (C) 1999 New Dimenstions Consulting, Inc. All
 * Rights Reserved.
 *
 * Contributor(s): 
 *
 *
 * Contributor(s):
 *  Robert Ginda, rginda@ndcico.com, original author
 *  Simon Fraser, smfr@netscape.com, Mac warning fixes 
 */

#include "string.h"

#include "prtypes.h"
#include "prerror.h"
#include "prmem.h"
#include "prio.h"
#include "prlog.h"

#include "bspubtd.h"
#include "bsutil.h"
#include "bsconfig.h"
#include "bserror.h"
#include "bsevent.h"
#include "bsqueue.h"
#include "bsapi.h"
#include "bsserver.h"
#include "bsconnection.h"

PR_BEGIN_EXTERN_C

BSConnectionClass *
bs_connection_new (BSServerClass *parent, PRFileDesc *fd, bsint port)
{
    BSConnectionClass *new_connection;
    
    new_connection = PR_NEW (BSConnectionClass);
    if (!new_connection)
    {
        BS_ReportError (BSERR_OUT_OF_MEMORY);
        return PR_FALSE;
    }

    memset (new_connection->callback_table, 0, BSCONNECTION_EVENT_COUNT);
    
    new_connection->parent_server = parent;
    new_connection->linebuffer = NULL;
    new_connection->port = port;
    new_connection->fd = fd;
    new_connection->linebuffer_flag = BS_LINEBUFFER_FLAG_DEFAULT;
    new_connection->is_connected = PR_TRUE;
    
    return new_connection;
}

void
bs_connection_free (BSConnectionClass *connection)
{
    PR_ASSERT (connection);

     /* partially received line will be lost */
    PR_FREEIF (connection->linebuffer);
    
    bs_connection_disconnect (connection);
    
    PR_Free (connection);
    
}

PRBool
bs_connection_disconnect (BSConnectionClass *connection)
{
    PRStatus rv;
    
    PR_ASSERT (connection);

    if (connection->is_connected)
    {
        PR_FREEIF (connection->linebuffer);

        rv = PR_Shutdown (connection->fd, PR_SHUTDOWN_BOTH);
        if (PR_FAILURE == rv)
            BS_ReportPRError (PR_GetError());
            
        rv = PR_Close (connection->fd);
        if (PR_FAILURE == rv)
            BS_ReportPRError (PR_GetError());
    }

    connection->is_connected = PR_FALSE;
    
    return PR_FALSE;
} 

PRBool /* public method */
bs_connection_send_data (BSConnectionClass *connection, bschar *data,
                         bsuint length)
{
    PRIntervalTime to;
    PRInt32 count;
    
    PR_ASSERT (connection);

    to = PR_MillisecondsToInterval (BS_SEND_TIMEOUT_MS);

    count = PR_Send (connection->fd, data, length, 0, to);

    if (count == -1)
    {
        BS_ReportPRError (PR_GetError());
        return PR_FALSE;
    }

    return PR_TRUE;
    
}

PRBool /* public method */
bs_connection_send_string (BSConnectionClass *connection, const bschar *data)
{
    bsuint length;

    PR_ASSERT (connection);

    length =  strlen (data);

    return bs_connection_send_data (connection, (bschar *)data, length);
    
}   

PRInt32 /* friend function */
bs_connection_recv_data (BSConnectionClass *connection, PRInt32 timeout,
                         bschar **buf)
{
    PRIntervalTime  to;
    PRInt32         count;
    PRErrorCode     err;
    static bschar   recv_buf[BSMAX_SERVER_RECV];

    PR_ASSERT (connection);    
	*buf = recv_buf;

    to = PR_MillisecondsToInterval (timeout);

    count = PR_Recv(connection->fd, recv_buf, BSMAX_SERVER_RECV, 0, to);
    
    switch (count)
    {
        case -1: /* recv error */
            err = PR_GetError();
            switch (err)
            {
                case PR_IO_TIMEOUT_ERROR:
                    buf[0] = '\00';
                    count = 0;
                    break;

                default:
                    BS_ReportPRError (err);
                    goto err_out;
                    break;
            }
            break;
            
        case 0: /* connection closed */
            count = -1;
            goto err_out;
            
        default: /* byte count */
            ((char *)(*buf))[count] = 0;
            
    }

    return count;

  err_out:
    *buf = NULL;
    return count;

}    

PRBool /* friend function */
bs_connection_recv (BSConnectionClass *connection, BSQueueClass *event_queue)
{
    bschar *buf, *lastline = NULL;
    bschar **lineary;
    int    i, lines;    
    BSEventClass    *event;
    PRBool completeline_flag;
    PRInt32 count;
    
    PR_ASSERT (connection);
    
    count = bs_connection_recv_data (connection, BS_RECV_TIMEOUT_MS, &buf);
    
    if (count == 0)
    {
        event = bs_event_new (BSOBJ_CONNECTION, connection,
                              BSEVENT_CONNECTION_DISCONNECT, NULL,
                              NULL, NULL);
        if (!event)
            return PR_FALSE;
        
        if (0 == bs_queue_add (event_queue, event, 0))
        {
            BS_ReportError (BSERR_OUT_OF_MEMORY);
            return PR_FALSE;
            
        }
        return PR_FALSE;
    }
    if (count < 0)
    {
        BS_ReportError (BSERR_UNDEFINED_ERROR);
        return PR_FALSE;
    }

    if (connection->linebuffer_flag)
    {
        completeline_flag = (buf[count - 1] == '\n') ? PR_TRUE :
            PR_FALSE;

        lineary = bs_util_delimbuffer_to_array (buf, &lines, '\n');
        
        for (i = 0; i < lines - 1; i++)
        { 
            event = bs_event_new_copyZ (BSOBJ_CONNECTION, connection,
                                        BSEVENT_CONNECTION_RAWDATA,
                                        lineary[i], NULL);
            if (!event)
                return PR_FALSE;

            if (0 == bs_queue_add (event_queue, event, 0))
            {
                BS_ReportError (BSERR_OUT_OF_MEMORY);
                return PR_FALSE;
            }
        }

        buf = lastline = bs_util_linebuffer (lineary[lines - 1],
											 &connection->linebuffer,
											 completeline_flag);

        PR_Free (lineary);
    }
            
    if (buf)
    {
        connection->linebuffer = NULL;
                
        event = bs_event_new_copyZ (BSOBJ_CONNECTION, connection,
                                    BSEVENT_CONNECTION_RAWDATA,
                                    buf, NULL);
        if (!event)
            return PR_FALSE;

        if (0 == bs_queue_add (event_queue, event, 0))
        {
            BS_ReportError (BSERR_OUT_OF_MEMORY);
            return PR_FALSE;
        }

    }

	PR_FREEIF (lastline);
    return PR_TRUE;

}
    
PRFileDesc * /* friend function */
bs_connection_get_fd (BSConnectionClass *connection)
{
    PR_ASSERT (connection);

    if (!connection->is_connected)
        return NULL;
    
    return connection->fd;
    
}

BSEventClass *
bs_connection_route (BSConnectionClass *connection, BSEventClass *event)
{

    PR_ASSERT (connection);

    if ((event->id < BSCONNECTION_EVENT_COUNT) &&
        (connection->callback_table[event->id]))
        return connection->callback_table[event->id](connection, event);
    else
        return NULL;
    
}                               

PRBool
bs_connection_set_handler (BSConnectionClass *connection,
                           BSConnectionEvent event_type,
                           BSConnectionEventCallback *handler)
{
    PR_ASSERT (connection);

    if (event_type > BSCONNECTION_EVENT_COUNT)
    {
        BS_ReportError (BSERR_NO_SUCH_EVENT);
        return PR_FALSE;
    }

    connection->callback_table[event_type] = handler;

    return PR_TRUE;
}

PRBool
bs_connection_set_property (BSConnectionClass *connection,
			    BSConnectionProperty prop,
                            bsint type, void *v)
{
    PR_ASSERT (connection);

    switch (prop)
    {
        case BSPROP_PARENT:
            if (!BS_CHECK_TYPE (type, BSTYPE_OBJECT))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }

            connection->parent_server = (BSServerClass *)v;
            break;
            
        case BSPROP_LINE_BUFFER:
            if (!BS_CHECK_TYPE (type, BSTYPE_BOOLEAN))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }                

            connection->linebuffer_flag = *(PRBool *)v;
            break;

        case BSPROP_STATUS:
            if (!BS_CHECK_TYPE (type, BSTYPE_BOOLEAN))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }

            BS_ReportError (BSERR_READ_ONLY);
            return PR_FALSE;
            break;

        case BSPROP_PORT:
            if (!BS_CHECK_TYPE (type, BSTYPE_UINT))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }                

            if (connection->is_connected)
            {
                BS_ReportError (BSERR_READ_ONLY);
                return PR_FALSE;
            }
            
            connection->port = *(bsuint *)v;
            break;

        default:
            BS_ReportError (BSERR_NO_SUCH_PARAM);
            return PR_FALSE;
            break;
            
    }
    
    return PR_FALSE;
}

PRBool
bs_connection_set_uint_property (BSConnectionClass *connection,
                                 BSConnectionProperty prop,
                                 bsuint v)
{
    return bs_connection_set_property (connection, prop, BSTYPE_UINT, &v);
    
}    

PRBool
bs_connection_set_string_property (BSConnectionClass *connection,
                                   BSConnectionProperty prop,
                                   char *v)
{
    return bs_connection_set_property (connection, prop, BSTYPE_STRING, v);
    
}    

PRBool
bs_connection_set_bool_property (BSConnectionClass *connection,
                                 BSConnectionProperty prop,
                                 PRBool v)
{
    return bs_connection_set_property (connection, prop, BSTYPE_BOOLEAN, &v);
    
}

PRBool
bs_connection_set_object_property (BSConnectionClass *connection,
                                   BSConnectionProperty prop,
                                   void *v)
{
    return bs_connection_set_property (connection, prop, BSTYPE_OBJECT, v);
    
}

PRBool
bs_connection_get_property (BSConnectionClass *connection,
                            BSConnectionProperty prop,
                            bsint type, void *v)
{
    PR_ASSERT (connection);

    switch (prop)
    {
        case BSPROP_PARENT:
            if (!BS_CHECK_TYPE (type, BSTYPE_OBJECT))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }

            v = connection->parent_server;
            break;
            
        case BSPROP_LINE_BUFFER:
            if (!BS_CHECK_TYPE (type, BSTYPE_BOOLEAN))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }                

            *(PRBool *)v = connection->linebuffer_flag;
            break;

        case BSPROP_PORT:
            if (!BS_CHECK_TYPE (type, BSTYPE_UINT))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }                

            *(int *)v = connection->port;
            break;

        case BSPROP_STATUS:
            if (!BS_CHECK_TYPE (type, BSTYPE_BOOLEAN))
            {
                BS_ReportError (BSERR_PROPERTY_MISMATCH);
                return PR_FALSE;
            }                

            *(PRBool *)v = connection->is_connected;
            break;
            
        default:
            BS_ReportError (BSERR_NO_SUCH_PARAM);
            return PR_FALSE;
            break;
            
    }
    
    return PR_TRUE;
    
}

PRBool
bs_connection_get_uint_property (BSConnectionClass *connection,
                                 BSConnectionProperty prop,
                                 bsuint *v)

{
    return bs_connection_get_property (connection, prop, BSTYPE_UINT, v);
    
}

PRBool
bs_connection_get_string_property (BSConnectionClass *connection,
                                   BSConnectionProperty prop,
                                   bschar **v)
{
    return bs_connection_get_property (connection, prop, BSTYPE_STRING, v);
    
}

PRBool
bs_connection_get_bool_property (BSConnectionClass *connection,
                                 BSConnectionProperty prop,
                                 PRBool *v)
{
    return bs_connection_get_property (connection, prop, BSTYPE_BOOLEAN, v);
    
}

PRBool
bs_connection_get_object_property (BSConnectionClass *connection,
                                   BSConnectionProperty prop,
                                   void **v)

{
    return bs_connection_get_property (connection, prop, BSTYPE_OBJECT, v);
    
}

PR_END_EXTERN_C
