/*
    This file is part of libmicrospdy
    Copyright Copyright (C) 2012 Andrey Uzunov

    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 3 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, see <http://www.gnu.org/licenses/>.
*/

/**
 * @file microspdy/daemon.c
 * @brief  daemon functionality
 * @author Andrey Uzunov
 */
 
#include "platform.h"
#include "structures.h"
#include "internal.h"
#include "session.h"
#include "io.h"


/**
 * Default implementation of the panic function,
 * prints an error message and aborts.
 *
 * @param cls unused
 * @param file name of the file with the problem
 * @param line line number with the problem
 * @param reason error message with details
 */
static void 
spdyf_panic_std (void *cls,
	       const char *file,
	       unsigned int line,
	       const char *reason)
{
	(void)cls;
	fprintf (stdout, "Fatal error in libmicrospdy %s:%u: %s\n",
		file, line, reason);
	//raise(SIGINT); //used for gdb
	abort ();
}


/**
 * Global handler for fatal errors.
 */
SPDY_PanicCallback spdyf_panic = &spdyf_panic_std;


/**
 * Global closure argument for "spdyf_panic".
 */
void *spdyf_panic_cls;


/**
 * Free resources associated with all closed connections.
 * (destroy responses, free buffers, etc.).
 *
 * @param daemon daemon to clean up
 */
static void
spdyf_cleanup_sessions (struct SPDY_Daemon *daemon)
{
	struct SPDY_Session *session;
	
	while (NULL != (session = daemon->cleanup_head))
	{
		DLL_remove (daemon->cleanup_head,
			daemon->cleanup_tail,
			session);
			
		SPDYF_session_destroy(session);
	}
}


/**
 * Closing of all connections handled by the daemon.
 *
 * @param daemon SPDY daemon
 */
static void
spdyf_close_all_sessions (struct SPDY_Daemon *daemon)
{
	struct SPDY_Session *session;
	
	while (NULL != (session = daemon->sessions_head))
	{	
		//prepare GOAWAY frame
		SPDYF_prepare_goaway(session, SPDY_GOAWAY_STATUS_OK, true);
		//try to send the frame (it is best effort, so it will maybe sent)
		SPDYF_session_write(session,true);
		SPDYF_session_close(session);
	}
	
	spdyf_cleanup_sessions(daemon);
}


/**
 * Parse a list of options given as varargs.
 * 
 * @param daemon the daemon to initialize
 * @param valist the options
 * @return SPDY_YES on success, SPDY_NO on error
 */
static int
spdyf_parse_options_va (struct SPDY_Daemon *daemon,
		  va_list valist)
{
	enum SPDY_DAEMON_OPTION opt;

	while (SPDY_DAEMON_OPTION_END != (opt = (enum SPDY_DAEMON_OPTION) va_arg (valist, int)))
	{
		if(opt & daemon->options)
		{
			SPDYF_DEBUG("Daemon option %i used twice",opt);
			return SPDY_NO;
		}
		daemon->options |= opt;
		
		switch (opt)
		{
			case SPDY_DAEMON_OPTION_SESSION_TIMEOUT:
				daemon->session_timeout = va_arg (valist, unsigned int) * 1000;
				break;
			case SPDY_DAEMON_OPTION_SOCK_ADDR:
				daemon->address = va_arg (valist, struct sockaddr *);
				break;
			case SPDY_DAEMON_OPTION_FLAGS:
				daemon->flags = va_arg (valist, enum SPDY_DAEMON_FLAG);
				break;
			case SPDY_DAEMON_OPTION_IO_SUBSYSTEM:
				daemon->io_subsystem = va_arg (valist, enum SPDY_IO_SUBSYSTEM);
				break;
			case SPDY_DAEMON_OPTION_MAX_NUM_FRAMES:
				daemon->max_num_frames = va_arg (valist, uint32_t);
				break;
			default:
				SPDYF_DEBUG("Wrong option for the daemon %i",opt);
				return SPDY_NO;
		}
	}
	return SPDY_YES;
}


void 
SPDY_set_panic_func (SPDY_PanicCallback cb,
					void *cls)
{
	spdyf_panic = cb;
	spdyf_panic_cls = cls;
}


struct SPDY_Daemon *
SPDYF_start_daemon_va (uint16_t port,
					const char *certfile,
					const char *keyfile,
					SPDY_NewSessionCallback nscb,
					SPDY_SessionClosedCallback sccb,
					SPDY_NewRequestCallback nrcb,
					SPDY_NewDataCallback npdcb,
					SPDYF_NewStreamCallback fnscb,
					SPDYF_NewDataCallback fndcb,
					void * cls,
					void * fcls,
					va_list valist)
{
	struct SPDY_Daemon *daemon = NULL;
	int afamily;
	int option_on = 1;
	int ret;
	struct sockaddr_in* servaddr4 = NULL;
#if HAVE_INET6
	struct sockaddr_in6* servaddr6 = NULL;
#endif
	socklen_t addrlen;

	if (NULL == (daemon = malloc (sizeof (struct SPDY_Daemon))))
	{
		SPDYF_DEBUG("malloc");
		return NULL;
	}
	memset (daemon, 0, sizeof (struct SPDY_Daemon));
	daemon->socket_fd = -1;
	daemon->port = port;

	if(SPDY_YES != spdyf_parse_options_va (daemon, valist))
	{
		SPDYF_DEBUG("parse");
		goto free_and_fail;
	}
  
  if(0 == daemon->max_num_frames)
    daemon->max_num_frames = SPDYF_NUM_SENT_FRAMES_AT_ONCE;
	
	if(!port && NULL == daemon->address)
	{
		SPDYF_DEBUG("Port is 0");
		goto free_and_fail;
	}
  if(0 == daemon->io_subsystem)
    daemon->io_subsystem = SPDY_IO_SUBSYSTEM_OPENSSL;
  
  if(SPDY_YES != SPDYF_io_set_daemon(daemon, daemon->io_subsystem))
		goto free_and_fail;
  
  if(SPDY_IO_SUBSYSTEM_RAW != daemon->io_subsystem)
  {
    if (NULL == certfile
      || NULL == (daemon->certfile = strdup (certfile)))
    {
      SPDYF_DEBUG("strdup (certfile)");
      goto free_and_fail;
    }
    if (NULL == keyfile
      || NULL == (daemon->keyfile = strdup (keyfile)))
    {
      SPDYF_DEBUG("strdup (keyfile)");
      goto free_and_fail;
    }
  }
  
	daemon->new_session_cb = nscb;
	daemon->session_closed_cb = sccb;
	daemon->new_request_cb = nrcb;
	daemon->received_data_cb = npdcb;
	daemon->cls = cls;
	daemon->fcls = fcls;
	daemon->fnew_stream_cb = fnscb;
	daemon->freceived_data_cb = fndcb;

#if HAVE_INET6
	//handling IPv6
	if((daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6)
		&& NULL != daemon->address && AF_INET6 != daemon->address->sa_family)
	{
		SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but IPv4 address provided");
		goto free_and_fail;
	}
  
  addrlen = sizeof (struct sockaddr_in6);
    
	if(NULL == daemon->address)
	{		
		if (NULL == (servaddr6 = malloc (addrlen)))
		{
			SPDYF_DEBUG("malloc");
			goto free_and_fail;
		}
		memset (servaddr6, 0, addrlen);
		servaddr6->sin6_family = AF_INET6;
		servaddr6->sin6_addr = in6addr_any;
		servaddr6->sin6_port = htons (port);
		daemon->address = (struct sockaddr *) servaddr6;
	}
	
  if(AF_INET6 == daemon->address->sa_family)
  {
    afamily = PF_INET6;
  }
  else
  {
    afamily = PF_INET;
  }
#else
	//handling IPv4
	if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6)
	{
		SPDYF_DEBUG("SPDY_DAEMON_FLAG_ONLY_IPV6 set but no support");
		goto free_and_fail;
	}
	
  addrlen = sizeof (struct sockaddr_in);
    
	if(NULL == daemon->address)
	{		
		if (NULL == (servaddr4 = malloc (addrlen)))
		{
			SPDYF_DEBUG("malloc");
			goto free_and_fail;
		}
		memset (servaddr4, 0, addrlen);
		servaddr4->sin_family = AF_INET;
		servaddr4->sin_addr = INADDR_ANY;
		servaddr4->sin_port = htons (port);
		daemon->address = (struct sockaddr *) servaddr4;
	}
	
	afamily = PF_INET;
#endif	

	daemon->socket_fd = socket (afamily, SOCK_STREAM, 0);
	if (-1 == daemon->socket_fd)
	{
		SPDYF_DEBUG("sock");
		goto free_and_fail;
	}

	//setting option for the socket to reuse address
	ret = setsockopt(daemon->socket_fd, SOL_SOCKET, SO_REUSEADDR, &option_on, sizeof(option_on));
	if(ret)
	{
		SPDYF_DEBUG("WARNING: SO_REUSEADDR was not set for the server");
	}
	
#if HAVE_INET6
	if(daemon->flags & SPDY_DAEMON_FLAG_ONLY_IPV6)
	{
		ret = setsockopt(daemon->socket_fd, IPPROTO_IPV6, IPV6_V6ONLY, &option_on, sizeof(option_on));
		if(ret)
		{
			SPDYF_DEBUG("setsockopt with IPPROTO_IPV6 failed");
			goto free_and_fail;
		}
	}
#endif
	
	if (-1 == bind (daemon->socket_fd, daemon->address, addrlen))
	{
		SPDYF_DEBUG("bind %i",errno);
		goto free_and_fail;
	}

	if (listen (daemon->socket_fd, 20) < 0)
	{
		SPDYF_DEBUG("listen %i",errno);
		goto free_and_fail;
	}

	if(SPDY_YES != daemon->fio_init(daemon))
	{
		SPDYF_DEBUG("tls");
		goto free_and_fail;
	}

	return daemon;

	//for GOTO
	free_and_fail:
	if(daemon->socket_fd > 0)
		(void)close (daemon->socket_fd);
		
	free(servaddr4);
#if HAVE_INET6
	free(servaddr6);
#endif
	if(NULL != daemon->certfile)
		free(daemon->certfile);
	if(NULL != daemon->keyfile)
		free(daemon->keyfile);
	free (daemon);
	
	return NULL;
}


void 
SPDYF_stop_daemon (struct SPDY_Daemon *daemon)
{
	daemon->fio_deinit(daemon);
	
	shutdown (daemon->socket_fd, SHUT_RDWR);
	spdyf_close_all_sessions (daemon);
	(void)close (daemon->socket_fd);
	
	if(!(SPDY_DAEMON_OPTION_SOCK_ADDR & daemon->options))
		free(daemon->address);
	
	free(daemon->certfile);
	free(daemon->keyfile);
	
	free(daemon);
}


int
SPDYF_get_timeout (struct SPDY_Daemon *daemon, 
		     unsigned long long *timeout)
{
	unsigned long long earliest_deadline = 0;
	unsigned long long now;
	struct SPDY_Session *pos;
	bool have_timeout;
	
	if(0 == daemon->session_timeout)
		return SPDY_NO;

	now = SPDYF_monotonic_time();
	have_timeout = false;
	for (pos = daemon->sessions_head; NULL != pos; pos = pos->next)
	{
		if ( (! have_timeout) ||
			(earliest_deadline > pos->last_activity + daemon->session_timeout) )
			earliest_deadline = pos->last_activity + daemon->session_timeout;

		have_timeout = true;
		
		if (SPDY_YES == pos->fio_is_pending(pos))
		{
			earliest_deadline = 0;
			break;
		}
	}
	
	if (!have_timeout)
		return SPDY_NO;
	if (earliest_deadline <= now)
		*timeout = 0;
	else
		*timeout = earliest_deadline - now;
		
	return SPDY_YES;
}


int
SPDYF_get_fdset (struct SPDY_Daemon *daemon,
				fd_set *read_fd_set,
				fd_set *write_fd_set, 
				fd_set *except_fd_set,
				bool all)
{
	(void)except_fd_set;
	struct SPDY_Session *pos;
	int fd;
	int max_fd = -1;

	fd = daemon->socket_fd;
	if (-1 != fd)
	{
		FD_SET (fd, read_fd_set);
		/* update max file descriptor */
		max_fd = fd;
	}

	for (pos = daemon->sessions_head; NULL != pos; pos = pos->next)
	{
		fd = pos->socket_fd;
		FD_SET(fd, read_fd_set);
		if (all
		    || (NULL != pos->response_queue_head) //frames pending
		    || (NULL != pos->write_buffer) //part of last frame pending
		    || (SPDY_SESSION_STATUS_CLOSING == pos->status) //the session is about to be closed
		    || (daemon->session_timeout //timeout passed for the session
			&& (pos->last_activity + daemon->session_timeout < SPDYF_monotonic_time()))
		    || (SPDY_YES == pos->fio_is_pending(pos)) //data in TLS' read buffer pending
		    || ((pos->read_buffer_offset - pos->read_buffer_beginning) > 0) // data in lib's read buffer pending
		    )
			FD_SET(fd, write_fd_set);
		if(fd > max_fd)
			max_fd = fd;
	}

	return max_fd;
}


void 
SPDYF_run (struct SPDY_Daemon *daemon)
{
	struct SPDY_Session *pos;
	struct SPDY_Session *next;
	int num_ready;
	fd_set rs;
	fd_set ws;
	fd_set es;
	int max;
	struct timeval timeout;
	int ds;

	timeout.tv_sec = 0;
	timeout.tv_usec = 0;
	FD_ZERO (&rs);
	FD_ZERO (&ws);
	FD_ZERO (&es);
	//here we need really all descriptors to see later which are ready
	max = SPDYF_get_fdset(daemon,&rs,&ws,&es, true);

	num_ready = select (max + 1, &rs, &ws, &es, &timeout);

	if(num_ready < 1)
		return;

	if ( (-1 != (ds = daemon->socket_fd)) &&
		(FD_ISSET (ds, &rs)) ){
		SPDYF_session_accept(daemon);
	}

	next = daemon->sessions_head;
	while (NULL != (pos = next))
	{
		next = pos->next;
		ds = pos->socket_fd;
		if (ds != -1)
		{
			//fill the read buffer
			if (FD_ISSET (ds, &rs) || pos->fio_is_pending(pos)){
				SPDYF_session_read(pos);
			}
			
			//do something with the data in read buffer
			if(SPDY_NO == SPDYF_session_idle(pos))
			{
				//the session was closed, cannot write anymore
				//continue;
			}
			
			//write whatever has been put to the response queue
			//during read or idle operation, something might be put
			//on the response queue, thus call write operation
			if (FD_ISSET (ds, &ws)){
				if(SPDY_NO == SPDYF_session_write(pos, false))
				{
					//SPDYF_session_close(pos);
					//continue;
				}
			}
			
			/* the response queue has been flushed for half closed
			 * connections, so let close them */
			/*if(pos->read_closed)
			{
				SPDYF_session_close(pos);
			}*/
		}
	}
	
	spdyf_cleanup_sessions(daemon);
}