/* Insert description here
 * Copyright (C) 2005 0x539 dev group
 * This file is part of the liboriel c++ library.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

#ifndef LORIEL_POSIX_SOCKET_HPP
#define LORIEL_POSIX_SOCKET_HPP

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <netdb.h>

#include <list>

#include <boost/cstdint.hpp>
#include "file.hpp"

namespace Loriel {

namespace posix {
	class resolve_error : public std::runtime_error {
	public:
		resolve_error(error_type errnum_)
			: std::runtime_error(gai_strerror(errnum_)),
			  m_errnum(errnum_) {}
		error_type errnum() { return m_errnum; }
	private:
		error_type m_errnum;
	};

	class socket : public file {
	public:
		class address
		{
			friend class socket;
		public:
			class family {
			public:
				enum val {
					UNIX = AF_UNIX,
					LOCAL = AF_LOCAL,
					INET = AF_INET,
					INET6 = AF_INET6,
					IPX = AF_IPX,
					NETLINK = AF_NETLINK,
					X25 = AF_X25,
					AX25 = AF_AX25,
					ATMPVC = AF_ATMPVC,
					APPLETALK = AF_APPLETALK,
					PACKET = AF_PACKET
				};

				family(val value) : m_value(value) { }
				operator val() const { return m_value; }
			private:
				val m_value;
			};

			family get_family() const;
			virtual int get_len() const = 0;
		protected:
			address();

			struct sockaddr* addr;
		};

		class ipv4_address : public address
		{
		public:
			struct data : public in_addr {
				data(boost::uint32_t address) {
					s_addr = address;
				};
				operator boost::uint32_t() const {
					return s_addr;
				}
				operator boost::uint32_t&() { return s_addr; }
			};

			static const boost::uint32_t ANY; // = INADDR_ANY;
			static const boost::uint32_t NONE; // = INADDR_NONE;
			static const boost::uint32_t BROADCAST; // =
				//INADDR_BROADCAST;
			static const boost::uint32_t LOOPBACK; // = INADDR_LOOPBACK;
			
			ipv4_address();
			ipv4_address(boost::uint32_t address,
			             boost::uint16_t port);
			ipv4_address(const char* hostname,
			             boost::uint16_t port); // TODO: Flags

			ipv4_address(const ipv4_address& other);
			virtual ~ipv4_address();

			ipv4_address& operator=(const ipv4_address& other);

			virtual int get_len() const {
				return sizeof(sockaddr_in);
			}

			static std::list<data> list(const char* hostname);

			void set_port(boost::uint16_t port);
			void set_address(boost::uint32_t address);

			boost::uint16_t get_port() const;
			boost::uint32_t get_address() const;
		protected:
		};

		class ipv6_address : public address
		{
		public:
			struct data : public in6_addr {
				typedef boost::uint8_t (&raw_data)[16];
				typedef const boost::uint8_t
					(&const_raw_data)[16];

				data(const boost::uint8_t address[16]) {
					std::copy(address, address + 16,
						  s6_addr);
				};
				operator raw_data() { return s6_addr; }
				operator const_raw_data() const {
					return s6_addr;
				}
			};

			static const char ANY[16]; // = IN6ADDR_ANY_INIT;
			static const char LOOPBACK[16]; // = IN6ADDR_LOOPBACK_INIT;

			ipv6_address();
			// TODO: char address[16] const machen. Das konfliktet]
			// nicht, weil die resolvende Version noch einen
			// flags-Parameter kriegen sollte.
			ipv6_address(char address[16], boost::uint16_t port,
			             boost::uint32_t flowinfo = 0,
				     boost::uint32_t scope_id = 0);
			ipv6_address(const char* hostname,
			             boost::uint16_t port,
			             boost::uint32_t flowinfo = 0,
				     boost::uint32_t scope_id = 0);
			ipv6_address(const ipv6_address& other);
			virtual ~ipv6_address();

			ipv6_address& operator=(const ipv6_address& other);
			
			virtual int get_len() const {
				return sizeof(sockaddr_in6);
			}

			static std::list<data> list(const char* hostname);

			void set_port(boost::uint16_t port);
			void set_address(const char address[16]);
			void set_flowinfo(boost::uint32_t flowinfo);
			void set_scope_id(boost::uint32_t scope_id);

			boost::uint16_t get_port() const;
			const char (&get_address() const)[16];
			boost::uint32_t get_flowinfo() const;
			boost::uint32_t get_scope_id() const;
		};
		
		class domain
		{
		public:
			enum val {
				UNIX = PF_UNIX,
				LOCAL = PF_LOCAL,
				INET = PF_INET,
				INET6 = PF_INET6,
				IPX = PF_IPX,
				NETLINK = PF_NETLINK,
				X25 = PF_X25,
				AX25 = PF_AX25,
				ATMPVC = PF_ATMPVC,
				APPLETALK = PF_APPLETALK,
				PACKET = PF_PACKET
			};

			domain(val value) : m_value(value) { }
			operator val() const { return m_value; }

		private:
			val m_value;
		};

		class type
		{
		public:
			enum val {
				STREAM = SOCK_STREAM,
				DGRAM = SOCK_DGRAM,
				SEQPACKET = SOCK_SEQPACKET,
				RAW = SOCK_RAW,
				RDM = SOCK_RDM,
				PACKET = SOCK_PACKET
			};

			type(val value) : m_value(value) { }
			operator val() const { return m_value; }
		private:
			val m_value;
		};

		class flags
		{
		public:
			enum val {
				OOB = MSG_OOB,
				EOR = MSG_EOR,
				DONTROUTE = MSG_DONTROUTE,
				DONTWAIT = MSG_DONTWAIT,
				NOSIGNAL = MSG_NOSIGNAL,
				CONFIRM = MSG_CONFIRM,
				MORE = MSG_MORE
			};

			flags(val value) : m_value(value) { }
			operator val() const { return m_value; }
		private:
			val m_value;
		};

		socket(domain domain_, type type_, int protocol = 0);
		socket(int fd, bool do_dispose);
		socket(int fd);
		~socket();

		void bind(const address& to);
		void listen(int backlog = 0);
		socket accept(address& from);

		void connect(const address& to);

		size_type send(const void* buf, size_type len, int flags = 0);
		size_type sendto(const void* buf, size_type len,
		                 const address& to, int flags = 0);
		// ^ - TODO: Noch eine Version ohne address, die an das
		// default sendet? Dasselbe dann bei recv?
	
		// TODO: sendmsg bauen. recvmsg wohl auch.
		size_type recv(void* buf, size_type len, int flags = 0);
		size_type recvfrom(void* buf, size_type len, 
		                   address& from, int flags = 0);
	};
} // namespace posix

} // namespace Loriel

#endif // LORIEL_POSIX_SOCKET_HPP 

