#include <xs/export.h>
#include <xs/unievent/Pipe.h>
#include <xs/typemap/expected.h>
#include <panda/unievent/util.h>

using namespace xs;
using namespace panda::unievent;
using namespace xs::unievent;
using panda::string;
using panda::ErrorCode;

static inline PipeSP create_pipe (const LoopSP& loop, bool ipc) {
    PipeSP ret = make_backref<Pipe>(loop, ipc);
    ret->connection_factory = [](const StreamSP& h) {
        auto srv = panda::dyn_cast<Pipe*>(h.get());
        PipeSP client = make_backref<Pipe>(srv->loop(), srv->ipc());
        xs::out(client); // fill backref
        return client;
    };
    return ret;
}


MODULE = UniEvent::Pipe                PACKAGE = UniEvent::Pipe
PROTOTYPES: DISABLE

BOOT {
    Stash s(__PACKAGE__);
    s.inherit("UniEvent::Stream");
    s.add_const_sub("TYPE", Simple(Pipe::TYPE.name));
    unievent::register_perl_class(Pipe::TYPE, s);
    
    xs::exp::create_constants(s, {
        {"MODE_NOT_CONNECTED", Pipe::Mode::not_connected},
        {"MODE_READABLE",      Pipe::Mode::readable},
        {"MODE_WRITABLE",      Pipe::Mode::writable}
    });
    xs::exp::autoexport(s);
}

PipeSP Pipe::new (LoopSP loop = {}, bool ipc = false) {
    if (!loop) loop = Loop::default_loop();
    RETVAL = create_pipe(loop, ipc);
}

void Pipe::open (Sv fd, int mode) {
    XSRETURN_EXPECTED(THIS->open(sv2fd(fd), mode, Ownership::SHARE));
}

void Pipe::bind (panda::string_view name) {
    XSRETURN_EXPECTED(THIS->bind(name));
}

ConnectRequestSP Pipe::connect (string name, Sub callback = Sub()) {
    Stream::connect_fn fn;
    auto req = make_backref<PipeConnectRequest>(name, fn);
    if (callback) {
        fn = [=](const StreamSP& h, const ErrorCode& err, const ConnectRequestSP& req){
            callback.call<void>(xs::out(h), xs::out(err), xs::out(req));
        };
    }
    THIS->connect(name, fn);
    RETVAL = req;
}

void Pipe::sockname () : ALIAS(peername=1) {
    auto ret = ix == 0 ? THIS->sockname() : THIS->peername();
    XSRETURN_EXPECTED(ret);
}

void Pipe::pending_instances (int count)

void Pipe::chmod (int mode) {
    XSRETURN_EXPECTED(THIS->chmod(mode));
}

#// pair([$loop])
#// pair($reader, $writer)
#// returns ($reader, $writer)
void pair (Sv arg1 = Sv(), Sv arg2 = Sv()) {
    PipeSP reader, writer;
    LoopSP loop;

    if (arg2) {
        reader = xs::in<Pipe*>(arg1);
        writer = xs::in<Pipe*>(arg2);
    }
    else if (arg1) {
        loop = xs::in<Loop*>(arg1);
    }

    if (!loop) loop = Loop::default_loop();
    // although, these don't accept connections, they can be reset(), and used as servers, so we need connection_factory
    if (!reader) reader = create_pipe(loop, false);
    if (!writer) writer = create_pipe(loop, false);
    if (reader->ipc() || writer->ipc()) throw "both reader and writer must be created with ipc = false";
    
    auto ret = Pipe::pair(reader, writer);
    if (!ret) throw Error(ret.error());

    mXPUSHs(xs::out(reader).detach());
    mXPUSHs(xs::out(writer).detach());
    XSRETURN(2);
}