using Posix;

[CCode(cheader_filename = "openssl/ssl.h")]
void* SSLv3_method();
[CCode(cheader_filename = "openssl/ssl.h")]
void* SSLv23_method();

[CCode(cheader_filename = "openssl/bio.h")]
void* BIO_s_mem();

namespace SSL {
	
  public errordomain SSLException {
	SSL,
	WANT_READ,
	WANT_WRITE,
	WANT_X509_LOOKUP,
	SYSCALL,
	ZERO_RETURN,
	WANT_CONNECT,
	WANT_ACCEPT
  }

  [CCode(cheader_filename = "openssl/ssl.h", cprefix="SSL_ERROR_")]
  public enum Error {
	NONE	=	0,
    SSL		=	1,
	WANT_READ	=	2,
	WANT_WRITE	=	3,
	WANT_X509_LOOKUP =	4,
	SYSCALL	=	5, /* look at error stack/return value/errno */
	ZERO_RETURN	=	6,
	WANT_CONNECT	=	7,
	WANT_ACCEPT	=	8
  }

  [CCode(cheader_filename = "openssl/ssl.h", cprefix="SSL_OP_")]
  public enum Options {
    NO_SSLv2	=				0x01000000L,
	NO_SSLv3	=				0x02000000L,
	NO_TLSv1	=				0x04000000L
  }

  [CCode(cheader_filename = "openssl/ssl.h", cprefix="SSL_FILETYPE_")]
  public enum Filetype {
	PEM	 = 1,
	ASN1 = 2
  }


  [CCode(cheader_filename = "openssl/ssl.h", cname = "SSL_library_init")]
  int library_init();


  /**
   * 
   * BIO
   * 
   */

  [Compact]
  [CCode(cheader_filename = "openssl/bio.h", cname = "BIO", free_function = "" /*, free_function = "BIO_free"*/)]
  public class BIO {
	[CCode(cname = "BIO_new")]
	public BIO(void* type);
	[CCode(cname = "BIO_read")]
	public int read(void* buf, int len);
	[CCode(cname = "BIO_write")]
	public int write(void* buf, int len);
	[CCode(cname = "BIO_set_mem_eof_return")]
	public void set_mem_eof_return(int v);
  }


  /**
   * 
   * CONTEXT
   * 
   */
   
  [Compact]
  [CCode(cheader_filename = "openssl/ssl.h", cname = "SSL_CTX", free_function = "SSL_CTX_free")]
  public class Context {
	[CCode(cname = "SSL_CTX_new")]
	public Context(void* method);
	[CCode(cname = "SSL_CTX_set_options")]
	public long set_options(ulong op);
	[CCode(cname = "SSL_CTX_use_certificate_file")]
	public int use_certificate_file(char *file, int type);
	[CCode(cname = "SSL_CTX_use_PrivateKey_file")]
	public int use_PrivateKey_file(char *file, int type);
  }


  /**
   * 
   * CONNECTION
   * 
   */

  [Compact]
  [CCode(cheader_filename = "openssl/ssl.h", cname = "SSL", free_function = "SSL_free", copy_function="SSL_dup")]
  public class Connection {
	
	[CCode(cname = "SSL_new")]
	public Connection(Context ctx);
	
	[CCode(cname = "SSL_do_handshake")]
	public int do_handshake();

	[CCode(cname = "SSL_get_error")]
	public int get_error(int i);
	
	[CCode(cname = "SSL_get_fd")]
	public int get_fd();
	
	[CCode(cname = "SSL_get_rbio")]
	public BIO get_rbio();

	[CCode(cname = "SSL_get_wbio")]
	public BIO get_wbio();
	
	[CCode(cname = "SSL_set_app_data")]
	public void set_app_data(char* arg);
	
	[CCode(cname = "SSL_set_accept_state")]
	public void set_accept_state();
	
	[CCode(cname = "SSL_set_bio")]
	public void set_bio(BIO rbio, BIO wbio);
	
	[CCode(cname = "SSL_set_connect_state")]
	public void set_connect_state();

	[CCode(cname = "SSL_set_fd")]
	public int set_fd(int fd);
	
	public int connect(SockAddrIn* dest) {
		this.set_connect_state();
		return Posix.connect(this.get_fd(), (SockAddr*)dest, sizeof(SockAddrIn));
	}
	
	[CCode(cname = "SSL_read")]
	public int read(void* buf, int num);
	
	[CCode(cname = "SSL_write")]
	public int write(void* buf, int num);
	
	[CCode(cname = "SSL_shutdown")]
	public int shutdown();

	public int bio_read(BIO wbio, void* buf, int num) /*throws SSLException*/ {
		var ret = wbio.read(buf, num);
		if (ret > 0) {
			return ret;
		} else {
			var err = get_error(ret);
//			_throw_ssl_error(err);
			return -err;
		}
	}
	
	public int bio_write(BIO rbio, void* buf, int num) /*throws SSLException*/ {
		var ret = rbio.write(buf, num);
		if (ret > 0) {
			return ret;
		} else {
			var err = get_error(ret);
//			_throw_ssl_error(err);
			return -err;
		}
	}
	
	public int bio_shutdown() {
		get_rbio().set_mem_eof_return(0);
	}

	public int recv(void* buf, int num) /*throws SSLException*/ {
		var ret = this.read(buf, num);
		var err = this.get_error(ret);
		if (err == Error.NONE) {
			return ret;
		} else {
//			_throw_ssl_error(err);
			return -err;
		}
	}
	
	public int send(void* buf, int num) /*throws SSLException*/ {
		var ret = this.write(buf, num);
		var err = this.get_error(ret);
		if (err == Error.NONE) {
			return ret;
		} else {
//			_throw_ssl_error(err);
			return -err;
		}
	}
	
//	public void _throw_ssl_error(int err) throws SSLException {
//		switch (err) {
//			case Error.SSL:
//				throw new SSLException.SSL("SSL Error");
//			case Error.WANT_READ:
//				throw new SSLException.WANT_READ("SSL WANT_READ Error");
//			case Error.WANT_WRITE:
//				throw new SSLException.WANT_WRITE("SSL WANT_WRITE Error");
//			case Error.WANT_X509_LOOKUP:
//				throw new SSLException.WANT_X509_LOOKUP("SSL X509_LOOKUP Error");
//			case Error.SYSCALL:
//				throw new SSLException.SYSCALL("SSL SYSCALL Error");
//			case Error.ZERO_RETURN:
//				throw new SSLException.ZERO_RETURN("SSL ZERO_RETURN Error");
//			case Error.WANT_CONNECT:
//				throw new SSLException.WANT_CONNECT("SSL WANT_CONNECT Error");
//			case Error.WANT_ACCEPT:
//				throw new SSLException.WANT_ACCEPT("SSL WANT_ACCEPT Error");
//		}
//	}
	
  }

}
