#include <rpc/rpc.h>
#include <arpa/inet.h>
#include <errno.h>
#include <debug.h>

extern int r_open(const char *router);
extern void r_close(int handle);
extern int r_read(int handle, char *buf, uint32 size);
extern int r_write(int handle, const char *buf, uint32 size);
extern int r_control(int handle, const uint32 cmd, void *arg);

static void xdr_std_destroy(xdr_s_type *xdr)
{
    /* whatever */
}

static bool_t xdr_std_control(xdr_s_type *xdr, int request, void *info)
{
    return r_control(xdr->fd, request, info);
}

static bool_t xdr_std_msg_done(xdr_s_type *xdr)
{
    /* whatever */
    return TRUE;
}

/* Outgoing message control functions */
static bool_t xdr_std_msg_start(xdr_s_type *xdr, 
                                 rpc_msg_e_type rpc_msg_type)
{

    /* xid is does not matter under our set of assumptions: that for a single
     * program/version channel, communication is synchronous.  If several
     * processes attempt to call functions on a program, then the rpcrouter
     * driver will ensure that the calls are properly muxed, because the
     * processes will have separate PIDs, and the rpcrouter driver uses PIDs to
     * keep track of RPC transactions.  For multiple threads in the same
     * process accessing the same program, we serialize access in clnt_call()
     * by locking a mutex around the RPC call.  If threads in the same process
     * call into different programs, then there is no issue, again because of
     * the use of a mutex in clnt_call().
     * 
     * NOTE: This comment assumes that the only way we talk to the RPC router
     *       from a client is by using clnt_call(), which is the case for all
     *       client code generated by rpcgen().
     *
     * NOTE: The RPC router driver will soon be able to open a separate device
     *       file for each program/version channel.  This will allow for
     *       natural multiplexing among clients, as we won't have to rely on
     *       the mutex for the case where different programs are being called
     *       into by separate threads in the same process.  When this happens,
     *       we'll need to optimize the RPC library to add a separate mutex for
     *       each program/version channel, which will require some sort of
     *       registry.
     */

    if (rpc_msg_type == RPC_MSG_CALL) xdr->xid++;

    /* We start writing into the outgoing-message buffer at index 32, because
       we need to write header information before we send the message.  The
       header information includes the destination address and the pacmark
       header.
    */
    xdr->out_next = (RPC_OFFSET+2)*sizeof(uint32);

    /* we write the pacmark header when we send the message. */
    ((uint32 *)xdr->out_msg)[RPC_OFFSET] = htonl(xdr->xid);
    /* rpc call or reply? */
    ((uint32 *)xdr->out_msg)[RPC_OFFSET+1] = htonl(rpc_msg_type);

    return TRUE;
}

static bool_t xdr_std_msg_abort(xdr_s_type *xdr)
{
    /* dummy */
    return TRUE;
}

/* Can be used to send both calls and replies. */

extern bool_t xdr_recv_reply_header(xdr_s_type *xdr, rpc_reply_header *reply);

#include <stdio.h>

static bool_t xdr_std_msg_send(xdr_s_type *xdr)
{  
    /* Send the RPC packet. */
    if (r_write(xdr->fd, (void *)xdr->out_msg, xdr->out_next) !=
            xdr->out_next)
        return FALSE;
        
    return TRUE;
}

static bool_t xdr_std_read(xdr_s_type *xdr)
{
    xdr->in_len = r_read(xdr->fd, (void *)xdr->in_msg, RPCROUTER_MSGSIZE_MAX);
    if (xdr->in_len < 0) return FALSE;

    if (xdr->in_len < (RPC_OFFSET+2)*4) {
        xdr->in_len = -1;
        return FALSE;
    }

    xdr->in_next = (RPC_OFFSET+2)*4;
    return TRUE;
}

/* Message data functions */
static bool_t xdr_std_send_uint32(xdr_s_type *xdr, const uint32 *value)
{
    if (xdr->out_next >= RPCROUTER_MSGSIZE_MAX - 3) return FALSE;
    *(int32 *)(xdr->out_msg + xdr->out_next) = htonl(*value);
    xdr->out_next += 4;
    return TRUE;
}

static bool_t xdr_std_send_int8(xdr_s_type *xdr, const int8 *value)
{
    uint32 val = *value;
    return xdr_std_send_uint32(xdr, &val);
}

static bool_t xdr_std_send_uint8(xdr_s_type *xdr, const uint8 *value)
{
    uint32 val = *value;
    return xdr_std_send_uint32(xdr, &val);
}

static bool_t xdr_std_send_int16(xdr_s_type *xdr, const int16 *value)
{
    uint32 val = *value;
    return xdr_std_send_uint32(xdr, &val);
}

static bool_t xdr_std_send_uint16(xdr_s_type *xdr, const uint16 *value)
{
    uint32 val = *value;
    return xdr_std_send_uint32(xdr, &val);
}

static bool_t xdr_std_send_int32(xdr_s_type *xdr, const int32 *value)
{
    return xdr_std_send_uint32(xdr, (uint32_t *)value);
}

static bool_t xdr_std_send_bytes(xdr_s_type *xdr, const uint8 *buf, 
                                   uint32 len)
{
    if (xdr->out_next + len > RPCROUTER_MSGSIZE_MAX) return FALSE; 
    while(len--)
        xdr->out_msg[xdr->out_next++] = *buf++;
    while(xdr->out_next % 4)
        xdr->out_msg[xdr->out_next++] = 0;
    return TRUE;
}

#if 0
#include <unwind.h> 
typedef struct
{
    size_t count;
    intptr_t* addrs;
} stack_crawl_state_t;

static _Unwind_Reason_Code trace_function(_Unwind_Context *context, void *arg)
{
    stack_crawl_state_t* state = (stack_crawl_state_t*)arg;
    if (state->count) {
        intptr_t ip = (intptr_t)_Unwind_GetIP(context);
        if (ip) {
            state->addrs[0] = ip;
            state->addrs++;
            state->count--;
        }
    }
    return _URC_NO_REASON;
}

static inline
int get_backtrace(intptr_t* addrs, size_t max_entries)
{
    stack_crawl_state_t state;
    state.count = max_entries;
    state.addrs = (intptr_t*)addrs;
    _Unwind_Backtrace(trace_function, (void*)&state);
    return max_entries - state.count;
}
#endif

static bool_t xdr_std_recv_uint32(xdr_s_type *xdr, uint32 *value)
{
#if 0
    intptr_t *trace[20], *tr;
    int nc = get_backtrace(trace, 20);
    tr = trace;
    while(nc--)
        D("\t%02d: %p\n", nc, *tr++);
#endif
        
    if (xdr->in_next + 4 > xdr->in_len) { return FALSE; }
    if (value) *value = ntohl(*(uint32 *)(xdr->in_msg + xdr->in_next));
    xdr->in_next += 4;
    return TRUE;
}

#define RECEIVE                                 \
    uint32 val;                                 \
    if (xdr_std_recv_uint32(xdr, &val)) {       \
        *value = val;                           \
        return TRUE;                            \
    }                                           \
    return FALSE

static bool_t xdr_std_recv_int8(xdr_s_type *xdr, int8 *value)
{
    RECEIVE;
}

static bool_t xdr_std_recv_uint8(xdr_s_type *xdr, uint8 *value)
{
    RECEIVE;
}

static bool_t xdr_std_recv_int16(xdr_s_type *xdr, int16 *value)
{
    RECEIVE;
}

static bool_t xdr_std_recv_uint16(xdr_s_type *xdr, uint16 *value)
{
    RECEIVE;
}

#undef RECEIVE

static bool_t xdr_std_recv_int32(xdr_s_type *xdr, int32 *value)
{
    return xdr_std_recv_uint32(xdr, (uint32 * )value);
}

static bool_t xdr_std_recv_bytes(xdr_s_type *xdr, uint8 *buf, uint32 len)
{
    if (xdr->in_next + (int)len > xdr->in_len) return FALSE;     
    if (buf) memcpy(buf, &xdr->in_msg[xdr->in_next], len);
    xdr->in_next += len;
    xdr->in_next = (xdr->in_next + 3) & ~3;
    return TRUE;
}

const xdr_ops_s_type xdr_std_xops = {

    xdr_std_destroy,
    xdr_std_control,
    xdr_std_read,
    xdr_std_msg_done,
    xdr_std_msg_start,
    xdr_std_msg_abort,
    xdr_std_msg_send,

    xdr_std_send_int8,
    xdr_std_send_uint8,
    xdr_std_send_int16,
    xdr_std_send_uint16,
    xdr_std_send_int32,
    xdr_std_send_uint32,
    xdr_std_send_bytes,
    xdr_std_recv_int8,
    xdr_std_recv_uint8,
    xdr_std_recv_int16,
    xdr_std_recv_uint16,
    xdr_std_recv_int32,
    xdr_std_recv_uint32,
    xdr_std_recv_bytes,
};

xdr_s_type *xdr_init_common(const char *router, int is_client)
{
    xdr_s_type *xdr = (xdr_s_type *)calloc(1, sizeof(xdr_s_type)); 

    xdr->xops = &xdr_std_xops;

    xdr->fd = r_open(router);
    if (xdr->fd < 0) {
        E("ERROR OPENING [%s]: %s\n", router, strerror(errno));
        free(xdr);
        return NULL;
    }
    xdr->is_client = is_client;

    D("OPENED [%s] fd %d\n", router, xdr->fd);
    return xdr;
}

xdr_s_type *xdr_clone(xdr_s_type *other)
{
    xdr_s_type *xdr = (xdr_s_type *)calloc(1, sizeof(xdr_s_type)); 

    xdr->xops = &xdr_std_xops;

    xdr->fd = dup(other->fd);
    if (xdr->fd < 0) {
        E("ERROR DUPLICATING FD %d: %s\n", other->fd, strerror(errno));
        free(xdr);
        return NULL;
    }

    xdr->xid = xdr->xid;
    xdr->x_prog = other->x_prog;
    xdr->x_vers = other->x_vers;
    xdr->is_client = other->is_client;

    D("CLONED fd %d --> %d\n", other->fd, xdr->fd);
    return xdr;
}

void xdr_destroy_common(xdr_s_type *xdr)
{
    D("CLOSING fd %d\n", xdr->fd);
    r_close(xdr->fd);
    free(xdr);
}
