#include "Udp.h"
#include "util.h"

namespace panda { namespace unievent {

const HandleType Udp::TYPE("udp");

AddrInfoHints Udp::defhints = AddrInfoHints(AF_UNSPEC, SOCK_DGRAM, 0, 0);

backend::HandleImpl* Udp::new_impl () {
    return loop()->impl()->new_udp(this, domain, flags);
}

const HandleType& Udp::type () const {
    return TYPE;
}

panda::string Udp::buf_alloc (size_t cap) noexcept {
    try {
        return buf_alloc_callback ? buf_alloc_callback(cap) : string(cap);
    } catch (...) {
        return {};
    }
}

excepted<void, panda::ErrorCode> Udp::open(sock_t sock, Ownership ownership) {
    if (ownership == Ownership::SHARE) sock = sock_dup(sock);
    return make_excepted(impl()->open(sock));
}

excepted<void, panda::ErrorCode> Udp::bind (const net::SockAddr& sa, unsigned flags) {
    return make_excepted(impl()->bind(sa, flags));
}

excepted<void, panda::ErrorCode> Udp::bind (string_view host, uint16_t port, const AddrInfoHints& hints, unsigned flags) {
    if (host == "*") return bind(broadcast_addr(port, hints), flags);
    auto res = sync_resolve(loop()->backend(), host, port, hints);
    if (!res) return make_unexpected<ErrorCode>(res.error());
    return bind(res.value().addr(), flags);
}

excepted<void, panda::ErrorCode> Udp::connect (const net::SockAddr& addr) {
    return make_excepted(impl()->connect(addr));
}

excepted<void, panda::ErrorCode> Udp::connect (string_view host, uint16_t port, const AddrInfoHints& hints) {
    auto res = sync_resolve(loop()->backend(), host, port, hints);
    if (!res) return make_unexpected<ErrorCode>(res.error());
    return connect(res.value().addr());
}

excepted<void, panda::ErrorCode> Udp::set_membership (string_view multicast_addr, string_view interface_addr, Membership membership) {
    return make_excepted(impl()->set_membership(multicast_addr, interface_addr, membership));
}

excepted<void, panda::ErrorCode> Udp::set_source_membership (string_view multicast_addr, string_view interface_addr, string_view source_addr, Membership membership) {
    return make_excepted(impl()->set_source_membership(multicast_addr, interface_addr, source_addr, membership));
}

excepted<void, panda::ErrorCode> Udp::set_multicast_loop (bool on) {
    return make_excepted(impl()->set_multicast_loop(on));
}

excepted<void, panda::ErrorCode> Udp::set_multicast_ttl (int ttl) {
    return make_excepted(impl()->set_multicast_ttl(ttl));
}

excepted<void, panda::ErrorCode> Udp::set_multicast_interface (string_view interface_addr) {
    return make_excepted(impl()->set_multicast_interface(interface_addr));
}

excepted<void, panda::ErrorCode> Udp::set_broadcast (bool on) {
    return make_excepted(impl()->set_broadcast(on));
}

excepted<void, panda::ErrorCode> Udp::set_ttl (int ttl) {
    return make_excepted(impl()->set_ttl(ttl));
}

excepted<void, panda::ErrorCode> Udp::recv_start (receive_fn callback) {
    if (callback) receive_event.add(callback);
    return make_excepted(impl()->recv_start());
}

excepted<void, panda::ErrorCode> Udp::recv_stop () {
    return make_excepted(impl()->recv_stop());
}

void Udp::send (const SendRequestSP& req) {
    for (const auto& buf : req->bufs) _sq_size += buf.length();
    req->set(this);
    queue.push(req);
}

void SendRequest::exec () {
    for (const auto& buf : bufs) handle->_sq_size -= buf.length();
    auto err = handle->impl()->send(bufs, addr, impl());
    if (err) delay([=]{ cancel(err); });
}

void SendRequest::notify (const ErrorCode& err) { handle->notify_on_send(err, this); }

void SendRequest::handle_event (const ErrorCode& err) {
    handle->queue.done(this, [=]{ handle->notify_on_send(err, this); });
}

void Udp::notify_on_send (const ErrorCode& err, const SendRequestSP& req) {
    UdpSP self = this;
    req->event(self, err, req);
    send_event(self, err, req);
    if (_listener) _listener->on_send(self, err, req);
}

void Udp::reset () {
    queue.cancel([&]{ BackendHandle::reset(); });
}

void Udp::clear () {
    queue.cancel([&]{
        BackendHandle::clear();
        domain = AF_UNSPEC;
        buf_alloc_callback = nullptr;
        _listener = nullptr;
        receive_event.remove_all();
        send_event.remove_all();
    });
}

void Udp::handle_receive (string& buf, const net::SockAddr& sa, unsigned flags, const std::error_code& err) {
    UdpSP self = this;
    receive_event(self, buf, sa, flags, err);
    if (_listener) _listener->on_receive(self, buf, sa, flags, err);
}

}}