#include <xs/export.h>

MODULE = UniEvent::WebSocket                PACKAGE = UniEvent::WebSocket::Connection
PROTOTYPES: DISABLE

BOOT {
    Stash s(__PACKAGE__);
    xs::exp::create_constants(s, {
        {"STATE_INITIAL",        (int)Connection::State::INITIAL},
        {"STATE_TCP_CONNECTING", (int)Connection::State::TCP_CONNECTING},
        {"STATE_CONNECTING",     (int)Connection::State::CONNECTING},
        {"STATE_CONNECTED",      (int)Connection::State::CONNECTED},
        {"STATE_HALT",           (int)Connection::State::HALT},
    });
}

LoopSP Connection::loop()

StreamSP Connection::stream()

void Connection::sockaddr () : ALIAS(peeraddr=1) {
    auto res = ix == 0 ? THIS->sockaddr() : THIS->peeraddr();
    XSRETURN_EXPECTED(res);
}

void Connection::send (...) {
    auto count = items;
    auto builder = THIS->message();
    string payload;
    // need to track if for the cases, if payload was provided, but it was empty.
    bool has_payload = false;

    Connection::send_fn callback;

    auto process = [&](string_view key, Scalar val) {
        if (key == "opcode") builder.opcode((Opcode)val.as_number<int>());
        if (key == "deflate") builder.deflate(val.as_number<int>());
        if (key == "payload") {
            payload = val.as_string();
            has_payload = true;
        }
        if (key == "cb") callback = xs::in<Connection::send_fn>(val);
    };

    if (count % 2) {
        for (decltype(count) i = 1; i < count; i += 2) {
            process(xs::in<string_view>(ST(i)), ST(i + 1));
        }
    } else if (count == 2 && SvROK(ST(1))) {
        for (HashEntry e : Hash(ST(1))) {
            process(e.key(), e.value());
        }
    } else {
        throw "Usage Connection::send(payload => $data,  opcode => ..., deflate => ..., cb => sub { ... }) or Connection::send({payload => $data, same_keys...});";
    }

    if (!has_payload) throw ("payload is mandatory argument");
    builder.send(payload, callback);
}

void Connection::send_ping (string payload = {})

void Connection::send_pong (string payload = {})

void Connection::send_message (string payload)

void Connection::send_text (string payload)

void Connection::close (uint16_t code = uint16_t(CloseCode::DONE), Sv payload = {}) {
    if (payload) THIS->close(code, xs::in<string>(payload));
    else         THIS->close(code);
}

XSCallbackDispatcher* Connection::message_event () {
    RETVAL = XSCallbackDispatcher::create(THIS->message_event);
}

void Connection::message_callback (Connection::message_fn cb) {
    THIS->message_event.remove_all();
    if (cb) THIS->message_event.add(cb);
}

XSCallbackDispatcher* Connection::error_event () {
    RETVAL = XSCallbackDispatcher::create(THIS->error_event);
}

void Connection::error_callback (Connection::error_fn cb) {
    THIS->error_event.remove_all();
    if (cb) THIS->error_event.add(cb);
}

XSCallbackDispatcher* Connection::close_event () {
    RETVAL = XSCallbackDispatcher::create(THIS->close_event);
}

void Connection::close_callback (Connection::close_fn cb) {
    THIS->close_event.remove_all();
    if (cb) THIS->close_event.add(cb);
}

XSCallbackDispatcher* Connection::peer_close_event () {
    RETVAL = XSCallbackDispatcher::create(THIS->peer_close_event);
}

void Connection::peer_close_callback (Connection::peer_close_fn cb) {
    THIS->peer_close_event.remove_all();
    if (cb) THIS->peer_close_event.add(cb);
}

XSCallbackDispatcher* Connection::ping_event () {
    RETVAL = XSCallbackDispatcher::create(THIS->ping_event);
}

void Connection::ping_callback (Connection::ping_fn cb) {
    THIS->ping_event.remove_all();
    if (cb) THIS->ping_event.add(cb);
}

XSCallbackDispatcher* Connection::pong_event () {
    RETVAL = XSCallbackDispatcher::create(THIS->pong_event);
}

void Connection::pong_callback (Connection::pong_fn cb) {
    THIS->pong_event.remove_all();
    if (cb) THIS->pong_event.add(cb);
}

Connection::StatisticsSP Connection::stats_counter(Sv val = {}) {
    if (val) {
        THIS->stats_counter(xs::in<Connection::StatisticsSP>(val));
        XSRETURN_UNDEF;
    } else {
        RETVAL = THIS->stats_counter();
    }
}

int Connection::state() {
    RETVAL = (int)THIS->state();
}