/*
 * Copyright (c) 2012 Elizabeth J. Myers. All rights reserved.
 *
 * Permission to use, copy, modify, and/or 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.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#ifndef MOWGLI_SRC_LIBMOWGLI_VIO_VIO_H_INCLUDE_GUARD
#define MOWGLI_SRC_LIBMOWGLI_VIO_VIO_H_INCLUDE_GUARD 1

#include "eventloop/eventloop.h"

/* Types and structs */
typedef struct _mowgli_vio mowgli_vio_t;

/* Error type */
typedef enum
{
	MOWGLI_VIO_ERR_NONE = 0,/* Wat, no error. */
	MOWGLI_VIO_ERR_REMOTE_HANGUP,	/* Remote end hung up on us, how rude */
	MOWGLI_VIO_ERR_ERRCODE,	/* An errno error or something like that */
	MOWGLI_VIO_ERR_API,	/* Programmer was a dumbass */
	MOWGLI_VIO_ERR_CUSTOM,	/* Use this for custom errors */
} mowgli_vio_error_type_t;

/* Errors with specific functions correspondng to the VIO op it's named
 * after */
typedef enum
{
	MOWGLI_VIO_ERR_OP_NONE = 0,	/* Wat. */
	MOWGLI_VIO_ERR_OP_SOCKET,
	MOWGLI_VIO_ERR_OP_LISTEN,
	MOWGLI_VIO_ERR_OP_ACCEPT,
	MOWGLI_VIO_ERR_OP_REUSEADDR,
	MOWGLI_VIO_ERR_OP_CONNECT,
	MOWGLI_VIO_ERR_OP_READ,
	MOWGLI_VIO_ERR_OP_WRITE,
	MOWGLI_VIO_ERR_OP_BIND,
	MOWGLI_VIO_ERR_OP_SEEK,
	MOWGLI_VIO_ERR_OP_TELL,
	MOWGLI_VIO_ERR_OP_OTHER,
} mowgli_vio_error_op_t;

typedef struct _mowgli_vio_error
{
	mowgli_vio_error_op_t op;
	mowgli_vio_error_type_t type;
	unsigned long code;	/* Unsigned long for OpenSSL fuckery */
	char string[128];	/* Friendly name for error */
} mowgli_vio_error_t;

/* Custom sockaddr member to have a uniform sockaddr as opposed to the
 * bullshit in the Berkeley sockets API with struct sockaddr/struct
 * sockaddr_storage/struct sockaddr_in/struct sockaddr_in6 and associated
 * API inconsistency and braindamage
 */
typedef struct _mowgli_vio_sockaddr
{
	struct sockaddr_storage addr;

	socklen_t addrlen;
} mowgli_vio_sockaddr_t;

#ifndef INET6_ADDRSTRLEN
#  define INET6_ADDRSTRLEN 46	/* Good enough I tell you */
#endif

/* Socket data */
typedef struct _mowgli_vio_sockdata
{
	char host[INET6_ADDRSTRLEN];	/* max length of IPv6 address */
	uint16_t port;
} mowgli_vio_sockdata_t;

/* Various typedefs bleh */
typedef int mowgli_vio_func_t (mowgli_vio_t *);
typedef int mowgli_vio_bind_connect_func_t (mowgli_vio_t *, mowgli_vio_sockaddr_t *);
typedef int mowgli_vio_read_func_t (mowgli_vio_t *, void *, size_t);
typedef int mowgli_vio_write_func_t (mowgli_vio_t *, const void *, size_t);
typedef int mowgli_vio_sendto_func_t (mowgli_vio_t *, const void *, size_t, mowgli_vio_sockaddr_t *);
typedef int mowgli_vio_recvfrom_func_t (mowgli_vio_t *, void *, size_t, mowgli_vio_sockaddr_t *);
typedef int mowgli_vio_connect_func_t (mowgli_vio_t *);
typedef int mowgli_vio_accept_func_t (mowgli_vio_t *, mowgli_vio_t *);
typedef int mowgli_vio_listen_func_t (mowgli_vio_t *, int);
typedef int mowgli_vio_socket_func_t (mowgli_vio_t *, int, int, int);
typedef int mowgli_vio_seek_func_t (mowgli_vio_t *, long, int);

/* These are workalikes vis-a-vis the Berkeley sockets API */
typedef struct
{
	mowgli_vio_socket_func_t *socket;
	mowgli_vio_bind_connect_func_t *bind;
	mowgli_vio_bind_connect_func_t *connect;
	mowgli_vio_listen_func_t *listen;
	mowgli_vio_accept_func_t *accept;
	mowgli_vio_func_t *reuseaddr;
	mowgli_vio_read_func_t *read;
	mowgli_vio_write_func_t *write;
	mowgli_vio_sendto_func_t *sendto;
	mowgli_vio_recvfrom_func_t *recvfrom;
	mowgli_vio_func_t *error;
	mowgli_vio_func_t *close;
	mowgli_vio_seek_func_t *seek;
	mowgli_vio_func_t *tell;
} mowgli_vio_ops_t;

/* Callbacks for eventloop stuff */
typedef struct
{
	mowgli_eventloop_io_cb_t *read_cb;
	mowgli_eventloop_io_cb_t *write_cb;
} mowgli_vio_evops_t;

struct _mowgli_vio
{
	mowgli_vio_ops_t *ops;	/* VIO operations */
	mowgli_vio_evops_t *evops;	/* Eventloop operations */

	/* eventloop IO object or descriptor
	 * If the eventloop member is non-null use io
	 * else use fd
	 */
	union
	{
		mowgli_eventloop_io_t *e;
		mowgli_descriptor_t fd;
	} io;

	/* Eventloop object we're attached to */
	mowgli_eventloop_t *eventloop;

	/* struct sockaddr portable workalike -- usage is
	 * context specific.
	 *
	 * For accept()'ed VIO objects, this is the client's
	 * struct sockaddr stuff, for connect()'ed objects it
	 * is the remote end's sockaddr (even with previous call
	 * to bind, for bind()'ed objects it is the sockaddr passed
	 * to bind
	 */
	mowgli_vio_sockaddr_t addr;

	mowgli_vio_error_t error;	/* Error information lives here */

	unsigned int flags;	/* Connection flags */

	void *userdata;	/* User data for VIO object */
	void *privdata;	/* Private data for stuff like SSL */
};

/* SSL settings... members subject to change */
typedef struct _mowgli_vio_ssl_settings
{
	const char *cert_path;
	const char *privatekey_path;
	int (*password_func)(char *, int, int, void *);
	int (*verify_func)(int, void *);
} mowgli_vio_ssl_settings_t;

/* Flags */
#define MOWGLI_VIO_FLAGS_ISCONNECTING 0x00001
#define MOWGLI_VIO_FLAGS_ISSSLCONNECTING 0x00002
#define MOWGLI_VIO_FLAGS_ISCLOSED 0x00004

#define MOWGLI_VIO_FLAGS_ISCLIENT 0x00008
#define MOWGLI_VIO_FLAGS_ISSERVER 0x00010

#define MOWGLI_VIO_FLAGS_ISONHEAP 0x00020

#define MOWGLI_VIO_FLAGS_NEEDREAD 0x00040
#define MOWGLI_VIO_FLAGS_NEEDWRITE 0x00080

/* Flag setting/getting */
static inline bool
mowgli_vio_hasflag(mowgli_vio_t *vio, unsigned int flag)
{
	return (vio->flags & flag) != 0 ? true : false;
}

static inline void
mowgli_vio_setflag(mowgli_vio_t *vio, unsigned int flag, bool setting)
{
	if (setting)
		vio->flags |= flag;
	else
		vio->flags &= ~flag;
}

/* Get file descriptor */
static inline mowgli_descriptor_t
mowgli_vio_getfd(mowgli_vio_t *vio)
{
	return_val_if_fail(vio, -1);

	if (vio->eventloop)
	{
		mowgli_eventloop_pollable_t *pollable = mowgli_eventloop_io_pollable(vio->io.e);

		if (pollable)
			return pollable->fd;
	}

	return vio->io.fd;
}

/* Macros */
#define MOWGLI_VIO_SET_CLOSED(v) \
	mowgli_vio_setflag(v, MOWGLI_VIO_FLAGS_ISCONNECTING, false); \
	mowgli_vio_setflag(v, MOWGLI_VIO_FLAGS_ISCLOSED, true);	\
	mowgli_vio_setflag(v, MOWGLI_VIO_FLAGS_ISSSLCONNECTING, false);	\
	mowgli_vio_setflag(v, MOWGLI_VIO_FLAGS_NEEDREAD, false); \
	mowgli_vio_setflag(v, MOWGLI_VIO_FLAGS_NEEDWRITE, false)

#define MOWGLI_VIO_IS_CLOSED(v) mowgli_vio_hasflag(v, MOWGLI_VIO_FLAGS_ISCLOSED)

#define MOWGLI_VIO_SETREAD(vio)	\
	if (vio->eventloop && vio->io.e && vio->evops && vio->evops->read_cb) \
	{ \
		mowgli_pollable_setselect(vio->eventloop, vio->io.e, MOWGLI_EVENTLOOP_IO_READ, vio->evops->read_cb); \
	}

#define MOWGLI_VIO_SETWRITE(vio) \
	if (vio->eventloop && vio->io.e && vio->evops && vio->evops->write_cb) \
	{ \
		mowgli_pollable_setselect(vio->eventloop, vio->io.e, MOWGLI_EVENTLOOP_IO_WRITE, vio->evops->write_cb); \
	}

#define MOWGLI_VIO_UNSETWRITE(vio) \
	if (vio->eventloop && vio->io.e) \
	{ \
		mowgli_pollable_setselect(vio->eventloop, vio->io.e, MOWGLI_EVENTLOOP_IO_WRITE, NULL); \
	}

/* Decls */
extern mowgli_vio_t *mowgli_vio_create(void *userdata);
extern void mowgli_vio_init(mowgli_vio_t *vio, void *userdata);
extern void mowgli_vio_destroy(mowgli_vio_t *vio);

extern void mowgli_vio_eventloop_attach(mowgli_vio_t *vio, mowgli_eventloop_t *eventloop, mowgli_vio_evops_t *evops);
extern void mowgli_vio_eventloop_detach(mowgli_vio_t *vio);

extern mowgli_vio_sockaddr_t *mowgli_vio_sockaddr_create(mowgli_vio_sockaddr_t *naddr, int proto, const char *addr, int port);
extern mowgli_vio_sockaddr_t *mowgli_vio_sockaddr_from_struct(mowgli_vio_sockaddr_t *naddr, const void *addr, socklen_t size);
extern int mowgli_vio_sockaddr_info(const mowgli_vio_sockaddr_t *addr, mowgli_vio_sockdata_t *data);

extern int mowgli_vio_default_socket(mowgli_vio_t *vio, int family, int type, int proto);
extern int mowgli_vio_default_bind(mowgli_vio_t *vio, mowgli_vio_sockaddr_t *addr);
extern int mowgli_vio_default_listen(mowgli_vio_t *vio, int backlog);
extern int mowgli_vio_default_accept(mowgli_vio_t *vio, mowgli_vio_t *newvio);
extern int mowgli_vio_default_reuseaddr(mowgli_vio_t *vio);
extern int mowgli_vio_default_connect(mowgli_vio_t *vio, mowgli_vio_sockaddr_t *addr);
extern int mowgli_vio_default_read(mowgli_vio_t *vio, void *buffer, size_t len);
extern int mowgli_vio_default_write(mowgli_vio_t *vio, const void *buffer, size_t len);
extern int mowgli_vio_default_sendto(mowgli_vio_t *vio, const void *buffer, size_t len, mowgli_vio_sockaddr_t *addr);
extern int mowgli_vio_default_recvfrom(mowgli_vio_t *vio, void *buffer, size_t len, mowgli_vio_sockaddr_t *addr);
extern int mowgli_vio_default_error(mowgli_vio_t *vio);
extern int mowgli_vio_default_close(mowgli_vio_t *vio);
extern int mowgli_vio_default_seek(mowgli_vio_t *vio, long offset, int whence);
extern int mowgli_vio_default_tell(mowgli_vio_t *vio);

extern int mowgli_vio_err_errcode(mowgli_vio_t *vio, char *(*int_to_error)(int), int errcode);
extern int mowgli_vio_err_sslerrcode(mowgli_vio_t *vio, unsigned long int errcode);

extern int mowgli_vio_openssl_setssl(mowgli_vio_t *vio, mowgli_vio_ssl_settings_t *settings, mowgli_vio_ops_t *ops);

/* These are void ptr's so they can be null ops if SSL isn't available */
extern void *mowgli_vio_openssl_getsslhandle(mowgli_vio_t *vio);
extern void *mowgli_vio_openssl_getsslcontext(mowgli_vio_t *vio);

#ifdef HAVE_OPENSSL

extern int mowgli_vio_openssl_default_connect(mowgli_vio_t *vio, mowgli_vio_sockaddr_t *addr);
extern int mowgli_vio_openssl_default_listen(mowgli_vio_t *vio, int backlog);
extern int mowgli_vio_openssl_default_accept(mowgli_vio_t *vio, mowgli_vio_t *newvio);
extern int mowgli_vio_openssl_default_read(mowgli_vio_t *vio, void *buffer, size_t len);
extern int mowgli_vio_openssl_default_write(mowgli_vio_t *vio, const void *buffer, size_t len);
extern int mowgli_vio_openssl_default_close(mowgli_vio_t *vio);

#else
#  define NOSSLSUPPORT { mowgli_log("Attempting to use default OpenSSL op with no SSL support; this will not work!"); return -255; }

static inline int mowgli_vio_openssl_default_connect(mowgli_vio_t *vio, mowgli_vio_sockaddr_t *addr) NOSSLSUPPORT
static inline int mowgli_vio_openssl_default_listen(mowgli_vio_t *vio, int backlog) NOSSLSUPPORT
static inline int mowgli_vio_openssl_default_accept(mowgli_vio_t *vio, mowgli_vio_t *newvio) NOSSLSUPPORT
static inline int mowgli_vio_openssl_default_read(mowgli_vio_t *vio, void *buffer, size_t len) NOSSLSUPPORT
static inline int mowgli_vio_openssl_default_write(mowgli_vio_t *vio, const void *buffer, size_t len) NOSSLSUPPORT
static inline int mowgli_vio_openssl_default_close(mowgli_vio_t *vio) NOSSLSUPPORT

#endif

/* Default ops -- change these if you want something besides the default */
extern mowgli_vio_ops_t mowgli_vio_default_ops;

/* Default evops -- they do nothing unless you change them */
extern mowgli_vio_evops_t mowgli_vio_default_evops;

/* Sundry operations on vio functables */
#define mowgli_vio_ops_set_op(ops, op, func) ops->op = func

/* Wrappers for the VIO ops */
#define mowgli_vio_socket(vio, ...) vio->ops->socket(vio, __VA_ARGS__)
#define mowgli_vio_listen(vio, ...) vio->ops->listen(vio, __VA_ARGS__)
#define mowgli_vio_bind(vio, ...) vio->ops->bind(vio, __VA_ARGS__)
#define mowgli_vio_accept(vio, ...) vio->ops->accept(vio, __VA_ARGS__)
#define mowgli_vio_reuseaddr(vio) vio->ops->reuseaddr(vio)
#define mowgli_vio_connect(vio, ...) vio->ops->connect(vio, __VA_ARGS__)
#define mowgli_vio_read(vio, ...) vio->ops->read(vio, __VA_ARGS__)
#define mowgli_vio_write(vio, ...) vio->ops->write(vio, __VA_ARGS__)
#define mowgli_vio_sendto(vio, ...) vio->ops->sendto(vio, __VA_ARGS__)
#define mowgli_vio_recvfrom(vio, ...) vio->ops->recvfrom(vio, __VA_ARGS__)
#define mowgli_vio_error(vio) vio->ops->error(vio)
#define mowgli_vio_close(vio) vio->ops->close(vio)
#define mowgli_vio_seek(vio, ...) vio->ops->seek(vio, __VA_ARGS__)
#define mowgli_vio_tell(vio) vio->ops->tell(vio)

#endif /* MOWGLI_SRC_LIBMOWGLI_VIO_VIO_H_INCLUDE_GUARD */
