//  prototype of a supercollider-synthdef-based synth prototype
//  Copyright (C) 2009 Tim Blechmann
//
//  This program is free software; you can redistribute it and/or modify
//  it under the terms of the GNU General Public License as published by
//  the Free Software Foundation; either version 2 of the License, or
//  (at your option) any later version.
//
//  This program is distributed in the hope that it will be useful,
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  GNU General Public License for more details.
//
//  You should have received a copy of the GNU General Public License
//  along with this program; see the file COPYING.  If not, write to
//  the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
//  Boston, MA 02111-1307, USA.

#pragma once

#include <boost/intrusive/set.hpp>
#include <boost/intrusive/unordered_set.hpp>
#include <boost/checked_delete.hpp>

#include "sc_synthdef.hpp"
#include "sc_synth.hpp"
#include "sc_plugin_interface.hpp"

#include "SC_InterfaceTable.h"
#include "SC_Unit.h"

#include "utilities/named_hash_entry.hpp"

namespace nova {
namespace bi = boost::intrusive;

struct sc_unitcmd_def : public named_hash_entry {
    const UnitCmdFunc func;

    sc_unitcmd_def(const char* cmd_name, UnitCmdFunc func): named_hash_entry(cmd_name), func(func) {}

    void run(Unit* unit, struct sc_msg_iter* args) { (func)(unit, args); }
};

class sc_ugen_def : public aligned_class, public named_hash_entry {
private:
    const size_t alloc_size;
    const UnitCtorFunc ctor;
    const UnitDtorFunc dtor;
    const uint32_t flags;

    static const std::size_t unitcmd_set_bucket_count = 4;
    typedef bi::unordered_set<sc_unitcmd_def, bi::constant_time_size<false>, bi::power_2_buckets<true>,
                              bi::store_hash<true>>
        unitcmd_set_type;

    unitcmd_set_type::bucket_type unitcmd_set_buckets[unitcmd_set_bucket_count];
    unitcmd_set_type unitcmd_set;

public:
    sc_ugen_def(const char* inUnitClassName, size_t inAllocSize, UnitCtorFunc inCtor, UnitDtorFunc inDtor,
                uint32 inFlags):
        named_hash_entry(inUnitClassName),
        alloc_size(inAllocSize),
        ctor(inCtor),
        dtor(inDtor),
        flags(inFlags),
        unitcmd_set(unitcmd_set_type::bucket_traits(unitcmd_set_buckets, unitcmd_set_bucket_count)) {}

    Unit* construct(sc_synthdef::unit_spec_t const& unit_spec, sc_synth* parent, int parentIndex, World* world,
                    linear_allocator& allocator);

    void initialize(Unit* unit) { (*ctor)(unit); }

    void destruct(Unit* unit) {
        if (dtor)
            (*dtor)(unit);
    }

    bool cant_alias(void) const { return flags & kUnitDef_CantAliasInputsToOutputs; }

    std::size_t memory_requirement(void) const {
        return alloc_size + 64; // overallocate to allow alignment
    }

    bool add_command(const char* cmd_name, UnitCmdFunc func);
    void run_unit_command(const char* cmd_name, Unit* unit, struct sc_msg_iter* args);
};

struct sc_bufgen_def : public named_hash_entry {
    const BufGenFunc func;

    sc_bufgen_def(const char* name, BufGenFunc func): named_hash_entry(name), func(func) {}

    sample* run(World* world, uint32_t buffer_index, struct sc_msg_iter* args);
};

struct sc_cmdplugin_def : public named_hash_entry {
    const PlugInCmdFunc func;
    void* user_data;

    sc_cmdplugin_def(const char* name, PlugInCmdFunc func, void* user_data):
        named_hash_entry(name),
        func(func),
        user_data(user_data) {}

    void run(World* world, struct sc_msg_iter* args, void* replyAddr) { (func)(world, user_data, args, replyAddr); }
};

class sc_plugin_container {
    static const std::size_t ugen_set_bucket_count = 512;
    typedef bi::unordered_set<sc_ugen_def, bi::constant_time_size<false>, bi::power_2_buckets<true>,
                              bi::store_hash<true>>
        ugen_set_type;

    static const std::size_t bufgen_set_bucket_count = 64;
    typedef bi::unordered_set<sc_bufgen_def, bi::constant_time_size<false>, bi::power_2_buckets<true>,
                              bi::store_hash<true>>
        bufgen_set_type;

    static const std::size_t cmdplugin_set_bucket_count = 8;
    typedef bi::unordered_set<sc_cmdplugin_def, bi::constant_time_size<false>, bi::power_2_buckets<true>,
                              bi::store_hash<true>>
        cmdplugin_set_type;

    ugen_set_type::bucket_type ugen_set_buckets[ugen_set_bucket_count];
    ugen_set_type ugen_set;

    bufgen_set_type::bucket_type bufgen_set_buckets[bufgen_set_bucket_count];
    bufgen_set_type bufgen_set;

    cmdplugin_set_type::bucket_type cmdplugin_set_buckets[cmdplugin_set_bucket_count];
    cmdplugin_set_type cmdplugin_set;

protected:
    sc_plugin_container(void):
        ugen_set(ugen_set_type::bucket_traits(ugen_set_buckets, ugen_set_bucket_count)),
        bufgen_set(bufgen_set_type::bucket_traits(bufgen_set_buckets, bufgen_set_bucket_count)),
        cmdplugin_set(cmdplugin_set_type::bucket_traits(cmdplugin_set_buckets, cmdplugin_set_bucket_count)) {}

    ~sc_plugin_container(void) {
        ugen_set.clear_and_dispose(boost::checked_deleter<sc_ugen_def>());
        bufgen_set.clear_and_dispose(boost::checked_deleter<sc_bufgen_def>());
        cmdplugin_set.clear_and_dispose(boost::checked_deleter<sc_cmdplugin_def>());
    }

public:
    void register_ugen(const char* inUnitClassName, size_t inAllocSize, UnitCtorFunc inCtor, UnitDtorFunc inDtor,
                       uint32 inFlags);

    void register_bufgen(const char* name, BufGenFunc func);

    sc_ugen_def* find_ugen(symbol const& name);

    bool register_ugen_command_function(const char* ugen_name, const char* cmd_name, UnitCmdFunc);
    bool register_cmd_plugin(const char* cmd_name, PlugInCmdFunc func, void* user_data);

    sample* run_bufgen(World* world, const char* name, uint32_t buffer_index, struct sc_msg_iter* args);
    bool run_cmd_plugin(World* world, const char* name, struct sc_msg_iter* args, void* replyAddr);
};

/** factory class for supercollider ugens
 *
 *  \todo do we need to take care of thread safety? */
class sc_ugen_factory : public sc_plugin_interface, public sc_plugin_container {
public:
    sc_ugen_factory() = default;

    ~sc_ugen_factory(void) { close_handles(); }

    /* @{ */
    /** ugen count handling */
    void allocate_ugens(uint32_t count) { ugen_count_ += count; }

    void free_ugens(uint32_t count) { ugen_count_ -= count; }

    uint32_t ugen_count(void) const { return ugen_count_; }
    /* @} */

    void load_plugin_folder(std::filesystem::path const& dir);
    void load_plugin(std::filesystem::path const& path);

private:
    void load_plugin_folder(std::filesystem::path const& dir, bool found_scx_file);

    void close_handles(void);

    uint32_t ugen_count_ = 0;
    std::vector<void*> open_handles;
};

extern std::unique_ptr<sc_ugen_factory> sc_factory;

} /* namespace nova */
