/*
 * Copyright (c) 2004-2009, Luiz Otavio O Souza <loos.br@gmail.com>
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

static const char rcsid[] = "$Id: server.c 112 2009-03-15 17:30:28Z loos-br $";

#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <strings.h>

#include "io.h"
#include "net-io.h"
#include "server.h"
#include "return.h"
#include "msn-proxy.h"

int
server_flush_commands(server_ *server) {
 command		*cmd = server->commands.cmd;
 command		*next;

    /* clean commands */
    while (cmd) {
	next = cmd->next;
	free_command(cmd);
	cmd = next;
    }

    /* reset pointers */
    server->commands.cmd      = (command *)0;
    server->commands.cmd_last = (command *)0;

    /* clean rx buffer */
    str_free(&server->commands.buf);

    return(0);
}

void
server_close(server_ *server) {

    /* delete i/o events */
    if (EVENT_FD((&server->read)) != -1) {
	event_del(&server->read);
	server->read.ev_fd = -1;
    }
    if (EVENT_FD((&server->write)) != -1) {
	event_del(&server->write);
	server->write.ev_fd = -1;
    }

    /* close connection */
    if (server->fd != -1) {
	while (close(server->fd) != 0 && errno == EINTR);
	server->fd = -1;
    }
}

server_ *
server_disconnect(server_ *server) {

    if (server == (server_ *)0)
	return((server_ *)0);

    /* close connection */
    server_close(server);

    /* clean commands */
    server_flush_commands(server);

    /* clean ns host and port and free memory */
    str_free(&server->host);
    str_free(&server->port);
    free(server);

    return((server_ *)0);
}

server_ *
server_alloc(void) {
 server_	*server = (server_ *)0;

    /* alloc */
    server = (server_ *)malloc(sizeof(server_));
    if (server == (server_ *)0)
	die_nomem();

    /* zero everything */
    memset(server, 0, sizeof(server_));
    server->fd		= -1;
    server->read.ev_fd  = -1;
    server->write.ev_fd = -1;

    /* alloc command buffer */
    commands_init(&server->commands);

    return(server);
}

void
server_sched_read(server_ *server) {
    if (EVENT_FD((&server->read)) == -1) return;
    event_add(&server->read, &config.timeout_server_read);
}

void
server_sched_write(server_ *server) {
    if (EVENT_FD((&server->write)) == -1) return;
    event_add(&server->write, &config.timeout_server_write);
}

server_ *
server_connect2(string *host,
			 void (*fn_read)(int, short, void *),
			 void (*fn_write)(int, short, void *),
			 void *arg) {
 server_	*server = (server_ *)0;
 string		tmp;

    if (!host || !host->s)
	return(server);

    server = server_alloc();
    if (server == (server_ *)0)
	die_nomem();

    /* XXX */
    str_zero(&tmp);
    tmp.s = host->s;
    tmp.p = (unsigned char *)strchr((char *)tmp.s, ':');
    tmp.len = tmp.p - tmp.s;
    if (tmp.len <= 0 ||	str_copy(&server->host, tmp.s, tmp.len) == 0) {
	server = server_disconnect(server);
	return(server);
    }

    /* XXX */
    tmp.s += tmp.len + 1;
    tmp.len = host->len - server->host.len - 1;
    if (tmp.len <= 0 || str_copy(&server->port, tmp.s, tmp.len) == 0) {
	/* XXX */
	server = server_disconnect(server);
	return(server);
    }

    if (server_real_connect(server, fn_read, fn_write, arg) == -1) {
	/* XXX */
	server = server_disconnect(server);
	return(server);
    }

    return(server);
}

server_ *
server_connect( string *host, string *port,
			 void (*fn_read)(int, short, void *),
			 void (*fn_write)(int, short, void *),
			 void *arg) {
 server_	*server = (server_ *)0;

    if (!host || !host->s || !port || !port->s)
	return(server);

    server = server_alloc();
    if (server == (server_ *)0)
	return(server);

    if (str_copy(&server->host, host->s, host->len) == 0 ||
		str_copy(&server->port, port->s, port->len) == 0) {

	server = server_disconnect(server);
	return(server);
    }

    if (server_real_connect(server, fn_read, fn_write, arg) == -1) {
	server = server_disconnect(server);
	return(server);
    }

    return(server);
}

int
server_real_connect(server_ *server,
			void (*fn_read)(int, short, void *),
			void (*fn_write)(int, short, void *),
			void *arg) {
    /* connect to NS */
    if (server->fd > 0)
	io_printf(1, "ATENCAO\n");

    if (server->fd == -1) {
	server->fd = net_connect((char *)server->host.s,
				 (char *)server->port.s);
	if (server->fd == -1) {
	    log("cannot connect to server");
	    return(-1);
	}
    }

    /* set io events */
    event_set(&server->read, server->fd, EV_READ, fn_read, arg);
    event_set(&server->write, server->fd, EV_WRITE, fn_write, arg);

    /* sched a read */
    server_sched_read(server);

    return(0);
}

void
shift_server_commands(server_ *server, command *cmd) {
    server->commands.cmd = cmd->next;
    if (server->commands.cmd == (command *)0) {
	server->commands.cmd_last = (command *)0;
    }
}

int
read_server_command(server_ *server, void (*sched_write)(), void *ev_write) {
 log_		*log = &config.log;

    /* read command */
    switch (read_command(&server->commands, server->fd, sched_write, ev_write)) {

	case END:
	    /* connection closed by peer */
	    return(END);

	case RFAIL:
	    /* error */
	    log->debug("server_read_command cmd buf: [%s]\n",
			&server->commands.buf);
	    return(RFAIL);

	case AGAIN:
	    /* sched */
	    return(AGAIN);
    }

    return(ROK);
}

