#include "unievent.h"
#include "unievent/Listener.h"
#include <typeinfo>
#include <cxxabi.h>

using namespace panda::unievent;
using panda::string;
using panda::string_view;

namespace xs { namespace unievent {

Sv::payload_marker_t event_listener_marker;

static bool _init () {
    event_listener_marker.svt_free = [](pTHX_ SV*, MAGIC* mg) -> int {
        delete (XSListener*)mg->mg_ptr;
        return 0;
    };
    return true;
}
static bool __init = _init();

IoInfo sv_io_info (const Sv& sv) {
    IoInfo ret;
    ret.is_sock = false;

    if (sv.is_ref() || sv.type() > SVt_PVMG) { // if we have a perl IO, get its type for free
        Io io(sv);
        ret.is_sock = io.iotype() == IoTYPE_SOCKET;
        ret.fd      = io.fileno();
    }
    else if (!SvOK(sv)) throw std::invalid_argument("fd must be defined");
    else { // otherwise we have to check the type
        ret.fd      = SvIV(sv);
        ret.is_sock = Fs::stat(ret.fd).value().type() == Fs::FileType::SOCKET;
    }

    if (ret.is_sock) ret.sock = fd2sock(ret.fd);
    return ret;
}

string sv2buf (const Sv& sv) {
    string buf;
    if (sv.is_array_ref()) { // [$str1, $str2, ...]
        Array wlist(sv);
        STRLEN sum = 0;
        for (auto it = wlist.cbegin(); it != wlist.cend(); ++it) {
            STRLEN len;
            SvPV(*it, len);
            sum += len;
        }
        if (!sum) return string();

        char* ptr = buf.reserve(sum);
        for (auto it = wlist.cbegin(); it != wlist.cend(); ++it) {
            STRLEN len;
            const char* data = SvPV(*it, len);
            memcpy(ptr, data, len);
            ptr += len;
        }
        buf.length(sum);
    } else { // $str
        STRLEN len;
        const char* data = SvPV(sv, len);
        if (!len) return string();
        buf.assign(data, len);
    }
    return buf;
}

void XSListener::_throw_noobj (const Simple& evname) {
    auto err = std::string("Handle needs event listener for event '") + evname.c_str() + "' but listener was weak and went out of scope";
    throw std::logic_error(err);
}

}}

namespace xs {

static inline void throw_bad_hints () { throw "argument is not a valid AddrInfoHints"; }

AddrInfoHints Typemap<AddrInfoHints>::in (SV* arg) {
    if (!sv_defined(arg)) return AddrInfoHints();

    if (SvPOK(arg)) {
        if (SvCUR(arg) < sizeof(AddrInfoHints)) throw_bad_hints();
        return *reinterpret_cast<AddrInfoHints*>(SvPVX(arg));
    }

    if (!Sv(arg).is_hash_ref()) throw_bad_hints();
    AddrInfoHints ret;
    Hash h = arg;
    for (auto& row : h) {
        auto k = row.key();
        auto val = row.value().number();
        if      (k == "family"  ) ret.family   = val;
        else if (k == "socktype") ret.socktype = val;
        else if (k == "protocol") ret.protocol = val;
        else if (k == "flags"   ) ret.flags    = val;
    }
    return ret;
}

Sv Typemap<Fs::FStat>::out (const Fs::FStat& s, const Sv&) {
    return Ref::create(Array::create({
        Simple(s.dev),
        Simple(s.ino),
        Simple(s.mode),
        Simple(s.nlink),
        Simple(s.uid),
        Simple(s.gid),
        Simple(s.rdev),
        Simple(s.size),
        Simple(s.atime.get()),
        Simple(s.mtime.get()),
        Simple(s.ctime.get()),
        Simple(s.blksize),
        Simple(s.blocks),
        Simple(s.flags),
        Simple(s.gen),
        Simple(s.birthtime.get()),
        Simple((int)s.type()),
        Simple(s.perms()),
    }));
}

Fs::FStat Typemap<Fs::FStat>::in (const Array& a) {
    Fs::FStat ret;
    ret.dev       = a.fetch(0).number();
    ret.ino       = a.fetch(1).number();
    ret.mode      = a.fetch(2).number();
    ret.nlink     = a.fetch(3).number();
    ret.uid       = a.fetch(4).number();
    ret.gid       = a.fetch(5).number();
    ret.rdev      = a.fetch(6).number();
    ret.size      = a.fetch(7).number();
    ret.atime     = a.fetch(8).number();
    ret.mtime     = a.fetch(9).number();
    ret.ctime     = a.fetch(10).number();
    ret.blksize   = a.fetch(11).number();
    ret.blocks    = a.fetch(12).number();
    ret.flags     = a.fetch(13).number();
    ret.gen       = a.fetch(14).number();
    ret.birthtime = a.fetch(15).number();
    return ret;
}

uint64_t type;
uint64_t bsize;
uint64_t blocks;
uint64_t bfree;
uint64_t bavail;
uint64_t files;
uint64_t ffree;
uint64_t spare[4];

Sv Typemap<Fs::FsInfo>::out (const Fs::FsInfo& i, const Sv&) {
    return Ref::create(Array::create({
        Simple(i.type),
        Simple(i.bsize),
        Simple(i.blocks),
        Simple(i.bfree),
        Simple(i.bavail),
        Simple(i.files),
        Simple(i.ffree),
        Ref::create(Array::create({
            Simple(i.spare[0]),
            Simple(i.spare[1]),
            Simple(i.spare[2]),
            Simple(i.spare[3]),
        })),
    }));
}

Fs::FsInfo Typemap<Fs::FsInfo>::in (const Array& a) {
    Fs::FsInfo ret;
    ret.type   = a.fetch(0).number();
    ret.bsize  = a.fetch(1).number();
    ret.blocks = a.fetch(2).number();
    ret.bfree  = a.fetch(3).number();
    ret.bavail = a.fetch(4).number();
    ret.files  = a.fetch(5).number();
    ret.ffree  = a.fetch(6).number();
    Array spare = a.fetch(7);
    ret.spare[0] = spare.fetch(0).number();
    ret.spare[1] = spare.fetch(1).number();
    ret.spare[2] = spare.fetch(2).number();
    ret.spare[3] = spare.fetch(3).number();
    return ret;
}

Sv Typemap<Fs::DirEntry>::out (const Fs::DirEntry& de, const Sv&) {
    return Ref::create(Array::create({
        Simple(de.name()),
        Simple((int)de.type())
    }));
}

Fs::DirEntry Typemap<Fs::DirEntry>::in (const Array& a) {
    return Fs::DirEntry(a[0].as_string(), (Fs::FileType)a[1].as_number<int>());
}

Sv Typemap<Fs::path_fd_t>::out (const Fs::path_fd_t& val, const Sv&) {
    return Ref::create(Array::create({
        Simple(val.path),
        Simple(val.fd)
    }));
}

Fs::path_fd_t Typemap<Fs::path_fd_t>::in (const Array& a) {
    return Fs::path_fd_t{ a[0].as_string(), (fd_t)a[1].as_number<int>() };
}

}