#include <sys/types.h>
#include <stddef.h>
#include <string.h>
#include <stdio.h>
#include <math.h>

#include <ladspa.h>

namespace xxx
{

    class StaticInitializer
    {
        public:
            typedef void (*func_t)();

        private:
            func_t init;
            func_t fini;

        public:
            StaticInitializer(func_t f_init, func_t f_fini)
            {
                init = f_init;
                fini = f_fini;
                if (init != NULL)
                    init();
            }

            ~StaticInitializer()
            {
                if (fini != NULL)
                    fini();
            }
    };

    enum flags_t
    {
        F_NONE          = 0,        // Input
        F_UPPER         = (1 << 0), // Upper-limit defined
        F_LOWER         = (1 << 1), // Lower-llmit defined
        F_STEP          = (1 << 2), // Step defined
        F_LOG           = (1 << 3)  // Logarithmic scale
    };

    enum unit_t
    {
        U_TOGGLE,
        U_VALUE,
        U_SECONDS,
        U_MILLIS,
        U_GAIN,
        U_PERCENT,
        U_METERS,
        U_SAMPLES,
        U_HZ,
        U_INDEX
    };

    enum plugin_class_t
    {
        C_DELAY,
            C_REVERB,
        C_DISTORTION,
            C_WAVESHAPER,
        C_DYNAMICS,
            C_AMPLIFIER,
            C_COMPRESSOR,
            C_ENVELOPE,
            C_EXPANDER,
            C_GATE,
            C_LIMITER,
        C_FILTER,
            C_ALLPASS,
            C_BANDPASS,
            C_COMB,
            C_EQ,
                C_MULTI_EQ,
                C_PARA_EQ,
            C_HIGHPASS,
            C_LOWPASS,
        C_GENERATOR,
            C_CONSTANT,
            C_INSTRUMENT,
            C_OSCILLATOR,
        C_MODULATOR,
            C_CHORUS,
            C_FLANGER,
            C_PHASER,
        C_SIMULATOR,
        C_SPATIAL,
        C_SPECTRAL,
            C_PITCH,
        C_UTILITY,
            C_ANALYSER,
            C_CONVERTER,
            C_FUNCTION,
            C_MIXER
    };

    typedef struct control_t
    {
        const char     *id;         // Control ID
        const char     *name;       // Control name
        unit_t          unit;       // Units
        int             flags;      // Flags
        float           min;        // Minimum value
        float           max;        // Maximum value
        float           start;      // Initial value
        float           step;       // Change step
        const char    **items;      // Items for list
    } parameter_t;

    typedef struct port_t
    {
        const char     *id;         // Port ID
        const char     *name;       // Port name
    } port_t;

    typedef struct plugin_metadata_t
    {
        const char             *name;           // Plugin description
        const char             *author;         // Author
        const int              *classes;        // List of plugin classes terminated by negative value
        const port_t           *in_buffers;     // List of data input ports
        const port_t           *out_buffers;    // List of data output ports
        const control_t        *in_controls;    // List of input controls
        const control_t        *out_controls;   // List of output controls
    } plugin_metadata_t;

    class plugin
    {
        public:
            plugin() {};

            virtual ~plugin() {};

        public:
            virtual void init(int sample_rate) {};
            virtual void destroy() {};

            virtual void activate() {};
            virtual void deactivate() {};
            virtual void bind(size_t port, void *data) {};
            virtual void update_settings() {};

            virtual void run(size_t samples) {};
            virtual void process(size_t samples) {};
    };

    class test_plugin: public plugin
    {
        public:
            static const plugin_metadata_t metadata;

            static const float DETECT_TIME_MIN          =   1.0f;
            static const float DETECT_TIME_MAX          =   100.0f;
            static const float DETECT_TIME_DFL          =   10.0f;
            static const float DETECT_TIME_STEP         =   1.0f;

        public:
            test_plugin() {};

            virtual ~test_plugin() {};
    };

    static const port_t input_ports[] =
    {
        { "in_a", "Input A" },
        { "in_b", "Input B" },
        { NULL }
    };

    static const port_t output_ports[] =
    {
        { "out_a", "Output A" },
        { "out_b", "Output B" },
        { NULL }
    };

    static const control_t input_controls[] =
    {
        { "sel_time",       "Selected time",    U_MILLIS,   F_UPPER | F_LOWER | F_STEP, - test_plugin::DETECT_TIME_MAX, test_plugin::DETECT_TIME_MAX, 0, 1, NULL },
        { NULL, NULL }
    };

    static const control_t output_controls[] =
    {
        { "best_time",      "Best time",        U_MILLIS,   F_NONE, 0, 0, 0, 0, NULL },
        { NULL, NULL }
    };

    static const int classes[] = { C_UTILITY, C_ANALYSER, -1 };

    const plugin_metadata_t test_plugin::metadata =
    {
        // Name
        "test_plugin",
        // Author
        "Vladimir Sadovnikov",
        // Classes
        classes,
        // Inputs
        input_ports,
        // Outputs
        output_ports,
        // Input controls
        input_controls,
        // Output controls
        output_controls
    };

    LADSPA_Handle ladspa_instantiate(
        const struct _LADSPA_Descriptor * Descriptor,
        unsigned long                     SampleRate)
    {
        plugin *p = NULL;
        size_t id = 0x10000;

        #define PLUGIN_DEF(plugin) \
            if ((!p) && (Descriptor->UniqueID == id)) \
                p = new plugin(); \
            id++;

        PLUGIN_DEF(test_plugin);
        #undef PLUGIN_DEF

        if (p)
            p->init(SampleRate);

        return reinterpret_cast<LADSPA_Handle>(p);
    }

    void ladspa_connect_port(
        LADSPA_Handle Instance,
        unsigned long Port,
        LADSPA_Data * DataLocation)
    {
        printf("ladspa_connect_port %p, %d, %p\n", Instance, (int)Port, DataLocation);
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->bind(Port, DataLocation);
    }

    void ladspa_activate(LADSPA_Handle Instance)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->activate();
    }

    void ladspa_run(LADSPA_Handle Instance, unsigned long SampleCount)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->run(SampleCount);
    }

    void ladspa_deactivate(LADSPA_Handle Instance)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->deactivate();
    }

    void ladspa_cleanup(LADSPA_Handle Instance)
    {
        plugin *p = reinterpret_cast<plugin *>(Instance);
        p->destroy();
        delete p;
    }

    LADSPA_Descriptor *ladspa_descriptors = NULL;
    size_t ladspa_descriptors_count    = 0;

    const char *decode_unit(size_t unit)
    {
        switch (unit)
        {
            case U_SECONDS: return "s";
            case U_MILLIS:  return "ms";
            case U_PERCENT: return "%";
            case U_METERS:  return "m";
            case U_SAMPLES: return "samples";
            case U_HZ:      return "Hz";
        }

        return NULL;
    }

    const char *ladspa_add_units(const char *s, size_t units)
    {
        char buf[256];
        const char *unit = decode_unit(units);
        if (unit == NULL)
            return strdup(s);

        snprintf(buf, sizeof(buf) - 1, "%s (%s)", s, unit);
        return strdup(buf);
    }

    void ladspa_make_descriptor(LADSPA_Descriptor *d, unsigned long id, const char *label, const plugin_metadata_t &m)
    {
        d->UniqueID     = id;
        d->Label        = label;
        d->Properties   = LADSPA_PROPERTY_HARD_RT_CAPABLE;
        d->Name         = m.name;
        d->Maker        = m.author;
        d->Copyright    = "(C)";
        d->PortCount    = 0;

        // Calculate number of ports
        for (const port_t *p = m.in_buffers; p->name != NULL; ++p)
            d->PortCount ++;
        for (const port_t *p = m.out_buffers; p->name != NULL; ++p)
            d->PortCount ++;
        for (const control_t *p = m.in_controls; (p->id != NULL) && (p->name != NULL); ++p)
            d->PortCount ++;
        for (const control_t *p = m.out_controls; (p->id != NULL) && (p->name != NULL); ++p)
            d->PortCount ++;

        LADSPA_PortDescriptor *p_descr      = new LADSPA_PortDescriptor[d->PortCount];
        const char **p_name                 = new const char *[d->PortCount];
        LADSPA_PortRangeHint *p_hint        = new LADSPA_PortRangeHint[d->PortCount];

        d->PortDescriptors                  = p_descr;
        d->PortNames                        = p_name;
        d->PortRangeHints                   = p_hint;
        d->ImplementationData               = NULL;

        for (const port_t *p = m.in_buffers; p->name != NULL; ++p)
        {
            *p_descr                = LADSPA_PORT_INPUT | LADSPA_PORT_AUDIO;
            *p_name                 = p->name;
            p_hint->HintDescriptor  = 0;
            p_hint->LowerBound      = 0.0f;
            p_hint->UpperBound      = 0.0f;

            p_descr++;
            p_name++;
            p_hint++;
        }

        for (const port_t *p = m.out_buffers; p->name != NULL; ++p)
        {
            *p_descr                = LADSPA_PORT_OUTPUT | LADSPA_PORT_AUDIO;
            *p_name                 = p->name;
            p_hint->HintDescriptor  = 0;
            p_hint->LowerBound      = 0.0f;
            p_hint->UpperBound      = 0.0f;

            p_descr++;
            p_name++;
            p_hint++;
        }

        for (size_t i=0; i<2; ++i)
        {
            const control_t *p = (i == 0) ? m.in_controls : m.out_controls;

            while ((p->id != NULL) && (p->name != NULL))
            {
                *p_descr                = (i == 0) ? LADSPA_PORT_INPUT | LADSPA_PORT_CONTROL : LADSPA_PORT_OUTPUT | LADSPA_PORT_CONTROL;
                *p_name                 = ladspa_add_units(p->name, p->unit);
                p_hint->HintDescriptor  = 0;

                if (p->unit == U_TOGGLE)
                {
                    p_hint->HintDescriptor |= LADSPA_HINT_TOGGLED | LADSPA_HINT_BOUNDED_BELOW | LADSPA_HINT_BOUNDED_BELOW;
                    p_hint->HintDescriptor |= (p->start > 0) ? LADSPA_HINT_DEFAULT_1 : LADSPA_HINT_DEFAULT_0;
                    p_hint->LowerBound      = -1.0f;
                    p_hint->UpperBound      = 1.0f;
                }
                else
                {
                    printf("port name=%s\n", p->name);
                    if (p->flags & F_LOWER)
                    {
                        p_hint->HintDescriptor |= LADSPA_HINT_BOUNDED_BELOW;
                        if (p->min == p->start)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MINIMUM;
                        p_hint->LowerBound      = p->min;
                        printf("lower=%.3f\n", p_hint->LowerBound);
                    }
                    if (p->flags & F_UPPER)
                    {
                        p_hint->HintDescriptor |= LADSPA_HINT_BOUNDED_ABOVE;
                        if (p->max == p->start)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MAXIMUM;
                        p_hint->UpperBound      = p->max;
                        printf("upper=%.3f\n", p_hint->UpperBound);
                    }
                    if (p->flags & F_LOG)
                        p_hint->HintDescriptor |= LADSPA_HINT_LOGARITHMIC;
                    if (p->unit == U_INDEX)
                        p_hint->HintDescriptor  |= LADSPA_HINT_INTEGER;

                    if ((p_hint->HintDescriptor & LADSPA_HINT_DEFAULT_MASK) == LADSPA_HINT_DEFAULT_NONE)
                    {
                        if (p->start == 1.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_1;
                        else if (p->start == 0.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_0;
                        else if (p->start == 100.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_100;
                        else if (p->start == 440.0f)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_440;
                        else if ((p->flags & (F_LOWER | F_UPPER))  == (F_LOWER | F_UPPER))
                        {
                            float factor = (p->flags & F_LOG) ?
                                (logf(p->start) - logf(p->min)) / (logf(p->max) - logf(p->min)) :
                                (p->start - p->min) / (p->max - p->min);

                            if (factor <= 0.33)
                                p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_LOW;
                            else if (factor >= 0.66)
                                p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_HIGH;
                            else
                                p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MIDDLE;
                        }
                        else if (p->flags & F_LOWER)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MINIMUM;
                        else if (p->flags & F_UPPER)
                            p_hint->HintDescriptor |= LADSPA_HINT_DEFAULT_MAXIMUM;
                    }
                }

                p_descr++;
                p_name++;
                p_hint++;
                p++;
            }
        }

        d->instantiate          = ladspa_instantiate;
        d->connect_port         = ladspa_connect_port;
        d->activate             = ladspa_activate;
        d->run                  = ladspa_run;
        d->run_adding           = NULL;
        d->set_run_adding_gain  = NULL;
        d->deactivate           = ladspa_deactivate;
        d->cleanup              = ladspa_cleanup;
    }

    void ladspa_gen_descriptors()
    {
        printf("ladspa_gen_descriptors\n");
        if (ladspa_descriptors != NULL)
            return;

        // Calculate number of plugins
        ladspa_descriptors_count    = 0;
        #define PLUGIN_DEF(plugin) ladspa_descriptors_count++;
        PLUGIN_DEF(test_plugin);
        #undef PLUGIN_DEF

        // Now allocate descriptors
        ladspa_descriptors          = new LADSPA_Descriptor[ladspa_descriptors_count];
        LADSPA_Descriptor *d        = ladspa_descriptors;
        size_t id                   = 0;

        #define PLUGIN_DEF(plugin) ladspa_make_descriptor(&d[id], 0x10000 + id, "http://plugin.org/plugins/ladspa/test_plugin", plugin::metadata); id++;
        PLUGIN_DEF(test_plugin);
        #undef PLUGIN_DEF
    };

    void ladspa_drop_descriptors()
    {
        printf("ladspa_drop_descriptors\n");
        if (ladspa_descriptors == NULL)
            return;

        LADSPA_Descriptor *d = ladspa_descriptors;
        while (ladspa_descriptors_count--)
        {
            delete [] d->PortNames;
            delete [] d->PortDescriptors;
            delete [] d->PortRangeHints;
        }

        delete [] ladspa_descriptors;
        ladspa_descriptors = NULL;
    };

    StaticInitializer ladspa_init(ladspa_gen_descriptors, ladspa_drop_descriptors);
}

const LADSPA_Descriptor * ladspa_descriptor(unsigned long index)
{
    using namespace xxx;
    return (index < ladspa_descriptors_count) ? &ladspa_descriptors[index] : NULL;
}

