#pragma once
#include "common.hpp"
#include "sock.hpp"
#include "pool.hpp"
#include "aio.hpp"
#include "callout.hpp"
#include "test.hpp"
#include <vector>


bool domain_match(const char*, const char*); ///< matches domain against given globbing pattern w/ special subdomain handling


class AclMatch {
    private:
        typedef struct addr_range_s {
            sockaddr_t from, to;
            bool parse(const sockaddr_t& a, unsigned m);
            bool parse(char* s);
            bool match(const sockaddr_t& a) const;
        } addr_range_t;

        addr_range_t src;
        addr_range_t dst;
        char host[MAX_SNI_LEN+1];

    public:
        char* parse(char*); ///< returns end ptr upon success
        INLINE bool match(const char* h, const sockaddr_t& s, const sockaddr_t& d) const {
            return domain_match(h, host) && src.match(s) && dst.match(d);
        }

        static bool parser(char*, bool&); ///< helper for use with @see Acl::parser_t
        static bool parser(char*, tristate_t&); ///< helper for use with @see Acl::parser_t
};


template <class T> class Acl {
    public:
        typedef bool (*parser_t)(char*, T&);

    private:
        typedef struct {
            AclMatch match;
            T val;
        } match_t;
        static TPool<match_t> pool;
        std::vector<match_t*> acls;

        AioFileIn::Result* aio;
        tristate_t parsed;
        const parser_t parser;

        static bool poller(void*); ///< poll for aio to be finished using @see Poller
        static void parse(void*); ///< #aio data available now
        static bool parse_line(char*, size_t, intptr_t);

    public:
        Acl(const char* fn, parser_t p); ///< parse filename w/ acl syntax
        Acl(parser_t p);
        ~Acl();

        bool parse_line(char*); ///< parse a single line and add it to #acls
        tristate_t match(const char*, const sockaddr_t&, const sockaddr_t&, T&) const; ///< TRI_FALSE: no acls parsed (yet), TRI_TRUE: match found, TRI_NONE: no match found
};


//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//\\//


template <class T> TPool<typename Acl<T>::match_t> Acl<T>::pool;


template <class T> Acl<T>::Acl(const char* fn, parser_t p): parser(p) {
    aio = AioFileIn::enqueue(fn);
    if (!aio) {
        parsed = TRI_FALSE;
    } else if (aio->len > 0) {
        parsed = TRI_NONE;
        parse(this);
    } else if (aio->len == 0) {
        parsed = TRI_NONE;
        (void)Poller::getInst()->add(poller, this);
    } else if (aio->len < 0) {
        parsed = TRI_FALSE;
        aio->delInst();
        aio = NULL;
    }
}


template <class T> Acl<T>::Acl(parser_t p): aio(NULL), parsed(TRI_TRUE), parser(p) {
}


template <class T> Acl<T>::~Acl() {
    for (typename std::vector<match_t*>::iterator it=acls.begin(); it!=acls.end(); ++it) {
        pool.push(*it);
    }
    if (aio) {
        if (parsed == TRI_NONE) {
            (void)Poller::getInst()->del(poller, this);
        }
        aio->delInst();
    }
}


template <class T> bool Acl<T>::poller(void* i) {
    Acl* inst = (Acl*)i;
    if (inst->aio->len > 0) {
        inst->parse(i);
        return false;
    } else if (inst->aio->len == 0) {
        return true;
    } else { // inst->aio->len < 0
        inst->parsed = TRI_FALSE;
        inst->aio->delInst();
        inst->aio = NULL;
        return false;
    }
}


template <class T> void Acl<T>::parse(void* i) {
    Acl* inst = (Acl*)i;
    assert(inst->aio && inst->aio->len > 0);
    assert(inst->parsed == TRI_NONE);
    if (readline_r(inst->aio->buf, inst->aio->len, '\n', &parse_line, (intptr_t)i)) {
        inst->parsed = TRI_TRUE;
    } else {
        inst->parsed = TRI_FALSE;
        log(notice, "acl parsing failed");
    }
}


template <class T> bool Acl<T>::parse_line(char* p, size_t, intptr_t i) {
    return ((Acl*)i)->parse_line(p);
}


template <class T> bool Acl<T>::parse_line(char* p) {
    while (*p == ' ' || *p == '\t') ++p;
    if (*p == '#') return true;

    match_t* m = pool.pop();
    p = m->match.parse(p);
    if (!p) {
        pool.push(m);
        return false;
    }

    if (!parser(p, m->val)) {
        pool.push(m);
        return false;
    }

    acls.push_back(m);
    return true;
}


template <class T> tristate_t Acl<T>::match(const char* host, const sockaddr_t& src, const sockaddr_t& dst, T& rv) const {
    if (parsed != TRI_TRUE) {
        return TRI_FALSE;
    }
    for (typename std::vector<match_t*>::const_iterator it=acls.begin(); it!=acls.end(); ++it) {
        if ((*it)->match.match(host, src, dst)) {
            rv = (*it)->val;
            return TRI_TRUE;
        }
    }
    return TRI_NONE;
}