#include "Tcp.h"
#include "util.h"
#include <ostream>

namespace panda { namespace unievent {

const HandleType Tcp::TYPE("tcp");

const AddrInfoHints Tcp::defhints = AddrInfoHints(AF_UNSPEC, SOCK_STREAM, 0, 0);

Tcp::Tcp (const LoopSP& loop, int domain) : domain(domain) {
    panda_log_ctor();
    _init(loop, loop->impl()->new_tcp(this, domain));
}

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

backend::HandleImpl* Tcp::new_impl () {
    return loop()->impl()->new_tcp(this, domain);
}

excepted<void, ErrorCode> Tcp::open (sock_t sock, Ownership ownership) {
    if (ownership == Ownership::SHARE) sock = sock_dup(sock);
    auto err = impl()->open(sock);
    if (err) return make_unexpected(ErrorCode(err));

    auto res = peeraddr();
    if (res && res.value()) {
        err = set_connect_result(true);
    }
    return make_excepted(err);
}

excepted<void, ErrorCode> Tcp::bind (const net::SockAddr& addr, unsigned flags) {
    auto code = impl()->bind(addr, flags);
    if (code) {
        panda_log_info("Tcp::bind error:" << code);
        return make_unexpected(ErrorCode(errc::bind_error, code));
    } else {
        return {};
    }
}

excepted<void, ErrorCode> Tcp::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<sock_t, ErrorCode> Tcp::socket () const {
    auto res = fileno();
    if (!res) return make_unexpected(res.error());
    return (sock_t)res.value();
}

StreamSP Tcp::create_connection () {
    return new Tcp(loop());
}

void Tcp::connect (const TcpConnectRequestSP& req) {
    req->set(this);
    queue.push(req);
}

void TcpConnectRequest::exec () {
    panda_log_debug("TcpConnectRequest::exec " << this);
    ConnectRequest::exec();
    if (handle->filters().size()) {
        last_filter = handle->filters().front();
        last_filter->tcp_connect(this);
    }
    else finalize_connect();
}

void TcpConnectRequest::finalize_connect () {
    panda_log_debug("TcpConnectRequest::finalize_connect " << this);

    if (addr) {
        auto err = handle->impl()->connect(addr, impl());
        if (err) delay([=]{ cancel(err); });
        return;
    }

    resolve_request = handle->loop()->resolver()->resolve();
    resolve_request
        ->node(host)
        ->port(port)
        ->hints(hints)
        ->use_cache(cached)
        ->on_resolve([this](const AddrInfo& res, const std::error_code& res_err, const Resolver::RequestSP) {
            resolve_request = nullptr;
            if (res_err) return cancel(nest_error(errc::resolve_error, res_err));
            addr = res.addr();
            auto err = handle->impl()->connect(addr, impl());
            if (err) cancel(err);
        })
        ->run();
}

void TcpConnectRequest::handle_event (const ErrorCode& err) {
    if (err && !(err & std::errc::operation_canceled) && host && cached) {
        handle->loop()->resolver()->cache().mark_bad_address(Resolver::CacheKey(host, panda::to_string(port), hints), addr);
    }

    if (resolve_request) {
        resolve_request->event.remove_all();
        resolve_request->cancel();
        resolve_request = nullptr;
    }
    ConnectRequest::handle_event(err);
}

excepted<net::SockAddr, ErrorCode> Tcp::sockaddr() const {
    return handle_fd_excepted(impl()->sockaddr());
}

excepted<net::SockAddr, ErrorCode> Tcp::peeraddr() const {
    return handle_fd_excepted(impl()->peeraddr());
}

excepted<std::pair<TcpSP, TcpSP>, ErrorCode> Tcp::pair (const LoopSP& loop, int type, int protocol) {
    return pair(new Tcp(loop), new Tcp(loop), type, protocol);
}

excepted<std::pair<TcpSP, TcpSP>, ErrorCode> Tcp::pair (const TcpSP& h1, const TcpSP& h2, int type, int protocol) {
    std::pair<TcpSP, TcpSP> p = {h1, h2};

    auto spres = panda::unievent::socketpair(type, protocol);
    if (!spres) return make_unexpected<ErrorCode>(spres.error());
    auto fds = spres.value();

    auto res = p.first->open(fds.first);
    if (res) res = p.second->open(fds.second);
    if (res) return p;

    p.first->reset();
    p.second->reset();
    panda::unievent::close(fds.first).nevermind();
    panda::unievent::close(fds.second).nevermind();
    return make_unexpected(res.error());
}

std::ostream& operator<< (std::ostream& os, const Tcp& tcp) {
    return os << "local:" << tcp.sockaddr().value_or(net::SockAddr{}) << " peer:" << tcp.peeraddr().value_or(net::SockAddr{})  << " connected:" << (tcp.connected() ? "yes" : "no");
}

std::ostream& operator<< (std::ostream& os, const TcpConnectRequest& r) {
    if (r.addr) return os << r.addr;
    else        return os << r.host << ':' << r.port;
}

}}