#pragma once
#include "../forward.h"
#include <openssl/ssl.h>
#include <openssl/bio.h>
#include <panda/string.h>
#include <panda/memory.h>

namespace panda { namespace unievent { namespace ssl {

extern log::Module panda_log_module;

#ifdef LIBRESSL_VERSION_NUMBER
#  define _PATCH_BIO 1
#else
#  define _PATCH_BIO 0
#endif

#if OPENSSL_VERSION_NUMBER < 0x10100000L || _PATCH_BIO

#define BIO_get_data(bio) bio->ptr
#define BIO_set_data(bio, b) bio->ptr = b
#define BIO_get_shutdown(bio) bio->shutdown
#define BIO_set_shutdown(bio, s) bio->shutdown = 1
#define BIO_get_init(bio) bio->init
#define BIO_set_init(bio, i) bio->init = i

#define BIO_meth_set_write(bio, bio_write) bio->bwrite = bio_write
#define BIO_meth_set_read(bio, bio_read) bio->bread = bio_read
#define BIO_meth_set_puts(bio, bio_puts) bio->bputs = bio_puts
#define BIO_meth_set_gets(bio, bio_gets) bio->bgets = bio_gets
#define BIO_meth_set_ctrl(bio, bio_ctrl) bio->ctrl = bio_ctrl
#define BIO_meth_set_create(bio, bio_new) bio->create = bio_new
#define BIO_meth_set_destroy(bio, bio_free) bio->destroy = bio_free
#define BIO_meth_set_callback_ctrl(bio, bio_callback) bio->callback_ctrl = bio_callback

#define BIO_meth_new(TYPE, NAME) ({ \
    BIO_METHOD* bio = new BIO_METHOD; \
    bio->name = NAME; \
    bio->type = TYPE; \
    bio; \
})

#endif

struct SslBio {
    struct membuf_t : AllocatedObject<membuf_t> {
        Stream* handle;
        string  buf;    // current read buf
    };

    static const int TYPE = 63 | BIO_TYPE_SOURCE_SINK;

    static BIO_METHOD* method () {
        #if OPENSSL_VERSION_NUMBER < 0x10100000L
        return &_method;
        #else
        return _method();
        #endif
    }

    static void set_handle (BIO* bio, Stream* handle) {
        membuf_t* b = (membuf_t*) BIO_get_data(bio);
        b->handle = handle;
    }

    static void set_buf (BIO* bio, const string& buf) {
        membuf_t* b = (membuf_t*) BIO_get_data(bio);
        panda_log_debug(bio << " len=" << buf.length() << ", was=" << b->buf.length());
        b->buf += buf;
    }

    static string steal_buf (BIO* bio) {
        membuf_t* b = (membuf_t*) BIO_get_data(bio);
        panda_log_debug(bio << " len=" << b->buf.length());
        string ret;
        ret.swap(b->buf);
        return ret;
    }

private:
    #if OPENSSL_VERSION_NUMBER < 0x10100000L
    static BIO_METHOD _method;
    #else
    static BIO_METHOD* _method();
    #endif
};

}}}