/*
 * Copyright (c) 2012 Mark McCurry
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.  IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */
#ifndef RTOSC_MIDITABLE_H
#define RTOSC_MIDITABLE_H

#include <rtosc/ports.h>
#include <string.h>
#include <algorithm>
#include <map>
#include <sstream>
#include <deque>
#include <utility>
#include <cassert>

namespace rtosc {
/**
 * Module Overview
 *
 * Actions:
 *  - Add a mapping {coarse/fine}    [nRT]
 *  - Delete a mapping {coarse/fine} [nRT]
 *  - Transform mapping value based on passive observation [nRT]
 *  - Find unused CC numbers  [RT]
 *  - Transform CC into event {coarse/fine} [RT]
 */

class MidiMapperStorage
{
    public:
        //Almost immutable short vector class
        template<class T>
        class TinyVector {
            int n;
            T  *t;
            public:
            TinyVector(void):n(0),t(0){}
            TinyVector(int i):n(i),t(new T[i]){}
            T&operator[](int i)       {assert(i>=0 && i<n);return t[i];}
            T operator[](int i) const {assert(i>=0 && i<n);return t[i];}

            TinyVector insert(const T &t_)
            {TinyVector next(n+1); for(int i=0;i<n; ++i) next.t[i]=t[i]; next.t[n] = t_;return next;}
            TinyVector one_larger(void)
            {TinyVector next(n+1); for(int i=0;i<n + 1; ++i) next.t[i]=0; return next;}
            TinyVector sized_clone(void)
            {TinyVector next(n); for(int i=0;i<n; ++i) next.t[i]=0; return next;}
            TinyVector clone(void)
            {TinyVector next(n); for(int i=0;i<n; ++i) next.t[i]=t[i]; return next;}
            int size(void) const{return n;}
        };

        typedef std::function<void(const char*)> write_cb;
        typedef std::function<void(int16_t,write_cb)> callback_t;
        //RT Read Only
        TinyVector<std::tuple<int, bool, int>> mapping;//CC->{coarse, val-cb offset}
        TinyVector<callback_t> callbacks;
        //RT RW
        TinyVector<int> values;

        bool handleCC(int ID, int val, write_cb write);

        //TODO try to change O(n^2) algorithm to O(n)
        void cloneValues(const MidiMapperStorage &storage);

        MidiMapperStorage *clone(void);
};

struct MidiBijection
{
    int mode;//0:linear,1:log
    float min;
    float max;
    int operator()(float x) const;
    float operator()(int x) const;
};

class MidiMappernRT
{
    public:
        MidiMappernRT(void);
        void map(const char *addr, bool coarse = true);

        MidiMapperStorage *generateNewBijection(const Port &port, std::string);

        void addNewMapper(int ID, const Port &port, std::string addr);

        void addFineMapper(int ID, const Port &port, std::string addr);

        void useFreeID(int ID);

        void unMap(const char *addr, bool coarse);

        void delMapping(int ID, bool coarse, const char *addr);
        void replaceMapping(int, bool, const char *);

        void clear(void);

        std::map<std::string, std::string> getMidiMappingStrings(void);

        //unclear if this should be be here as a helper or not
        std::string getMappedString(std::string addr);

        MidiBijection getBijection(std::string s);

        void snoop(const char *msg);

        void apply_high(int v, int ID);
        void apply_low(int v, int ID);
        void apply_midi(int val, int ID);

        void setBounds(const char *str, float low, float high);

        std::tuple<float,float,float,float> getBounds(const char *str);

        bool has(std::string addr);
        bool hasPending(std::string addr);
        bool hasCoarse(std::string addr);
        bool hasFine(std::string addr);
        bool hasCoarsePending(std::string addr);
        bool hasFinePending(std::string addr);
        int getCoarse(std::string addr);
        int getFine(std::string addr);

        //(Location, Coarse, Fine, Bijection)
        std::map<std::string, std::tuple<int, int, int, MidiBijection>> inv_map;
        std::deque<std::pair<std::string,bool>> learnQueue;
        std::function<void(const char *)> rt_cb;
        MidiMapperStorage *storage;
        const Ports *base_ports;
};

class MidiMapperRT
{
    public:
        MidiMapperRT(void);
        void setBackendCb(std::function<void(const char*)> cb);
        void setFrontendCb(std::function<void(const char*)> cb);
        void handleCC(int par, int val, char chan=1, bool nrpn=false);
        void addWatch(void);
        void remWatch(void);

        //Depricated
        Port addWatchPort(void);
        Port removeWatchPort(void);
        Port bindPort(void);

        static const Ports ports;

        //Fixed upper bounded size set of integer IDs
        class PendingQueue
        {
            public:
                PendingQueue()
                    :pos_r(0), pos_w(0), size(0)
                {
                    for(int i=0; i<32; ++i)
                        vals[i] = -1;
                }
                void insert(int x)
                {
                    if(has(x) || size > 31)
                        return;
                    vals[pos_w] = x;
                    size++;
                    pos_w = (pos_w+1)%32;
                }
                void pop(void)
                {
                    if(size == 0)
                        return;
                    size--;
                    vals[pos_r] = -1;
                    pos_r = (1+pos_r)%32;
                }
                bool has(int x)
                {
                    for(int i=0; i<32; ++i)
                        if(vals[i] == x)
                            return true;
                    return false;
                }
                int vals[32];
                int pos_r;
                int pos_w;
                int size;

        };


        /***************
         * Member Data *
         ***************/
        PendingQueue pending;
        MidiMapperStorage *storage;
        unsigned watchSize;
        std::function<void(const char*)> backend;
        std::function<void(const char*)> frontend;
};

struct MidiAddr
{
    //The midi values that map to the specified action
    uint8_t ch, ctl;

    //The type of the event 'f', 'i', 'T', 'c'
    char  type;
    //The path of the event
    char *path;
    //The conversion function for 'f' types
    const char *conversion;
};


/**
 * Table of midi mappings - Deprecated
 *
 */
class MidiTable
{
    public:

        const Ports  &dispatch_root;
        short  unhandled_ch;
        short  unhandled_ctl;
        char  *unhandled_path;

        void (*error_cb)(const char *, const char *);
        void (*event_cb)(const char *);
        void (*modify_cb)(const char *, const char *, const char *, int, int);

        MidiTable(const Ports &_dispatch_root);
        ~MidiTable();

        bool has(uint8_t ch, uint8_t ctl) const;

        MidiAddr *get(uint8_t ch, uint8_t ctl);

        const MidiAddr *get(uint8_t ch, uint8_t ctl) const;

        bool mash_port(MidiAddr &e, const Port &port);

        void addElm(uint8_t ch, uint8_t ctl, const char *path);

        void check_learn(void);

        void learn(const char *s);

        void clear_entry(const char *s);

        void process(uint8_t ch, uint8_t ctl, uint8_t val);

        Port learnPort(void);
        Port unlearnPort(void);
        Port registerPort(void);

        //TODO generalize to an addScalingFunction() system
        static float translate(uint8_t val, const char *meta);

    private:
        class MidiTable_Impl *impl;
};

};
#endif
