#pragma once
#include "common.hpp"
#include "files.hpp"
#include "hooks.hpp"
#include <vector>


#define CONFIG_MAX_STR_LEN 100


/** When inherited from, this is a factory for use with CONF_KEY_INST(), passing the actual config value to the constructor. */
template <class A> class ConfigInst {
    protected:
        ConfigInst() {}
    public:
        virtual ~ConfigInst() {}
        virtual bool is_valid() const = 0;
        template <class C> static ConfigInst<A>* getConfigInst(A a) {
            ConfigInst<A>* rv = new C(a);
            if (rv->is_valid()) return rv;
            delete rv;
            return NULL;
        }
};


class ConfigKey {
    public:
        typedef bool (*handler_t)(void*&, char*); ///< parser, false rv rejects whole config
        typedef void (*unhandler_t)(void*&); ///< upon shutdown or config rollback/switch (for cleanup)
        typedef ConfigInst<char*>* (*getinst_str_t)(char*);
        typedef ConfigInst<size_t>* (*getinst_size_t)(size_t); ///< 0 means disabled
        typedef enum {CONFIG_TYPE_STR, CONFIG_TYPE_INT, CONFIG_TYPE_BOOL, CONFIG_TYPE_STR_CLASS, CONFIG_TYPE_SIZE_CLASS, CONFIG_TYPE_HANDLER} type_t;

        typedef union { // ConfigKey must be iterable/same size, so no templated sub-class here (w/ partial specialization)
            struct {
                char str[CONFIG_MAX_STR_LEN+1];
                size_t len;
            } str;
            int num;
            bool bin;
            struct {
                handler_t handler;
                unhandler_t unhandler;
                void* ctx; ///< opaque, for module's usage w/ handlers
            } handler;
            struct {
                union {
                    getinst_str_t str_handler;
                    getinst_size_t num_handler;
                };
                void* ctx; // ConfigInst* actually
            } inst;
        } val_t;

    private:
        char key[CONFIG_MAX_STR_LEN+1];
        const bool reconfigurable; ///< can be reconfigured between config instances?
        const bool multi; ///< can be stated multiple times within the same config instance? also false if not reconfigurable lateron.
        bool parsed; ///< some value has been set (non-default). true also after dup() if not reconfigurable.

        ConfigKey* child; ///< refcount for #val destruction, @see dup()
        ConfigKey* parent; ///< refcount for #val destruction, @see dup()

        const type_t type;
        val_t default_val;

        ConfigKey(ConfigKey*); ///< @see dup()

    public:
        ConfigKey(const char*, bool, const char*);
        ConfigKey(const char*, bool, int);
        ConfigKey(const char*, bool, bool);
        ConfigKey(const char*, bool, bool, handler_t, unhandler_t=NULL); ///< multiple calls only for custom handler type
        ConfigKey(const char*, bool, getinst_str_t);
        ConfigKey(const char*, bool, getinst_size_t);
        ~ConfigKey();

        val_t val;
        template <class T> INLINE T* ctx_as() { ///< convenience wrapper for omitting cast for val.[handler|inst].ctx
            return (type == CONFIG_TYPE_HANDLER)? (T*)val.handler.ctx: ((type == CONFIG_TYPE_STR_CLASS || type == CONFIG_TYPE_SIZE_CLASS)? (T*)val.inst.ctx: NULL);
        }

        INLINE const char* get_key() const { return key; }

        bool parse(char*); ///< config value parsing
        bool parse() const { return !parsed || reconfigurable || multi; } ///< could parse again w/o expected error?
        void dup(ConfigKey*); ///< creates a child from a parent for new config instance

        void info() const;
};


class Config {
    private:
        typedef struct config_s {
            ConfigKey** conf; ///< pointer to the module's local storage. NOTE: configs must be iterable, i.e. castable to ConfigKey[]
            ConfigKey* next_conf; ///< the next config, currently being processed
            size_t len; ///< # of entries in #conf
            void(*init)(ConfigKey*); ///< user-defined initialization func
        } config_t;
        static std::vector<config_t> configs; ///< all statically registered configs

        static bool load(char*, size_t, intptr_t); // line handler
        bool load(char*); // line
        bool load(const char*, char*); // k/v
        bool loaded;

    public:
        Config(const char*); ///< finalizes #configs from NULL to default values
        ~Config();
        const char* const fn;

        bool load(); ///< (re-)loads the config file (blocking read).
        bool load(char*, size_t); ///< (re-)loads the config from given buffer (possibly read async).
        void info() const;

        static void add(ConfigKey**, size_t, void(*)(ConfigKey*)); ///< exclusive use by CONF_KEY_INIT()
};


#define CONF_DEF(cname) \
    struct CONCAT(cname, _s); \
    static CONCAT(cname, _s)* cname = NULL; \
    struct CONCAT(cname, _s)

#define CONF_INIT(cname) \
    static void CONCAT(cname, _i)(CONCAT(cname, _s)*); \
    UNUSED static const int CONCAT(cname, _dummy) = (Config::add((ConfigKey**)&cname, sizeof(CONCAT(cname, _s))/sizeof(ConfigKey), (void(*)(ConfigKey*))&CONCAT(cname, _i)), 42); \
    static void CONCAT(cname, _i)(CONCAT(cname, _s)* c)

#define CONF_KEY_INIT(k, r, ...) new (&c->k)ConfigKey(STRINGIFY(k), r, __VA_ARGS__)
#define CONF_KEY_INST(k, r, t, a) new (&c->k)ConfigKey(STRINGIFY(k), r, &ConfigInst<a>::getConfigInst<t>)