/************************************************************************
 ************************************************************************
 FAUST compiler
 Copyright (C) 2003-2015 GRAME, Centre National de Creation Musicale
 ---------------------------------------------------------------------
 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; if not, write to the Free Software
 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ************************************************************************
 ************************************************************************/

#ifndef _SERVER_h
#define _SERVER_h

#include <sstream>
#include <iostream>
#include <fstream>
#include <fcntl.h>
#include <stdlib.h>
#include <list>
#include <map>
#include <vector>
#include <set>
#include <microhttpd.h>
#include <netdb.h>
#include <arpa/inet.h>

#define LLVM_DSP 1
/*
 #define OSCCTRL 1
 #define HTTPCTRL 1
 #define MIDICTRL 1
 #define NETJACK 1
 */

#ifdef LLVM_DSP_FACTORY
#include "faust/dsp/llvm-dsp.h"
#else
#include "faust/dsp/interpreter-dsp.h"
#endif

#include "faust/audio/audio.h"

#ifdef OSCCTRL
#include "faust/gui/OSCUI.h"
#endif

#ifdef HTTPCTRL
#include "faust/gui/httpdUI.h"
#endif

#ifdef MIDICTRL
#include "faust/gui/MidiUI.h"
#endif

#include "TMutex.h"

#define POSTBUFFERSIZE 512
#define GET 0
#define POST 1

#ifdef WIN32
#define    EXPORT __declspec(dllexport)
#else
#define    EXPORT __attribute__ ((visibility("default")))
#endif

using namespace std;

typedef bool (*createFactoryDSPCallback) (dsp_factory* factory, void* arg);
typedef bool (*createInstanceDSPCallback) (dsp* dsp, void* arg);
typedef bool (*deleteFactoryDSPCallback) (dsp_factory* factory, void* arg);
typedef bool (*deleteInstanceDSPCallback) (dsp* dsp, void* arg);

/*
 TODO :
 
 - possibly return 'machine code' to client side?
 - better define where the DSP instance and the audio driver is going to run (server or client side)
 
 */

// Structure wrapping llvm_dsp with all its needed elements (audio/interface/...)

class audio_dsp {
    
    protected:
    
        string fInstanceKey;
        string fName;
    
        dsp* fDSP;          // DSP Instance
        audio* fAudio;      // Audio driver
    
    #ifdef OSCCTRL
        OSCUI* fOSCUI;      // OSC controler
    #endif
    
    #ifdef HTTPCTRL
        httpdUI* fHttpdUI;  // Httpd controler
    #endif
    
    #ifdef MIDICTRL
        MidiUI* fMidiUI;    // MIDIcontroler
    #endif
    
        createInstanceDSPCallback fCreateDSPInstanceCb;
        void* fCreateDSPInstanceCb_arg;
    
        deleteInstanceDSPCallback fDeleteDSPInstanceCb;
        void* fDeleteDSPInstanceCb_arg;
    
    public:
    
        audio_dsp(dsp_factory* factory,
                  bool poly, int voices, bool group,
                  bool osc, bool httpd, bool midi,
                  const string& name, const string& key,
                  createInstanceDSPCallback cb1, void* cb1_arg,
                  deleteInstanceDSPCallback cb2, void* cb2_arg);
        virtual ~audio_dsp();
    
        virtual bool init(int sr, int bs);
    
        virtual bool start()
        {
            return fAudio->start();
        }
        void stop()
        {
            fAudio->stop();
        }
    
        virtual bool isActive() { return true; }
    
        string  getKey() { return fInstanceKey; }
        void    setKey(const string& key) { fInstanceKey = key; }
        string  getName() { return fName; }
        void    setName(string name) { fName = name; }
    
};

// Structure handled by libmicrohttp related to a connection

enum {
    ERROR_FACTORY_NOTFOUND,
    ERROR_INSTANCE_NOTCREATED,
    ERROR_INSTANCE_NOTFOUND
};


typedef set<dsp_factory*> FactoryTable;  // TO CHECK
#define FactoryTableIt FactoryTable::iterator

class DSPServer;

struct dsp_server_connection_info {
    
    int fAnswercode;    // internally used by libmicrohttpd to see where things went wrong or right
    
    string fAnswer;     // the answer sent to the user after upload
    
    string fAudioType;  // audio driver type
    
    //-----DATAS RECEIVED TO CREATE NEW DSP FACTORY---------
    string fNameApp;
    string fFaustCode;
    string fTarget;
    vector<string> fCompilationOptions;
    string fOptLevel;
    
    //------DATAS RECEIVED TO CREATE NEW NetJack DSP INSTANCE-------
    string fIP;
    string fPort;
    string fCompression;
    string fMTU;
    string fLatency;
    string fSHAKey;
    string fInstanceKey;
    
    //------DATAS RECEIVED TO CREATE POLYPOHONIC -------
    string fPoly;
    string fVoices;
    string fGroup;
    
    //------DATAS RECEIVED TO CREATE NEW local Audio INSTANCE-------
    string fSampleRate;
    string fBufferSize;
    string fOSC;
    string fHTTPD;
    string fMIDI;
    
    dsp_server_connection_info();
    virtual ~dsp_server_connection_info() {}
    
    void getJson(dsp_factory* factory);
    bool getFactoryFromSHAKey(DSPServer* server);
    
    int iteratePost(const char* key, const char* data, size_t size);
    
    dsp_factory* createFactory(DSPServer* server, string& error);
    dsp_factory* crossCompileFactory(DSPServer* server, string& error);
    
    virtual int postProcess(const char* upload_data, size_t* upload_data_size)
    {
        return MHD_NO;
    }
    
};

struct dsp_server_connection_info_post : public dsp_server_connection_info {
    
    MHD_PostProcessor* fPostprocessor;     // the POST processor used internally by libmicrohttpd
    
    dsp_server_connection_info_post(MHD_Connection* connection);
    
    virtual ~dsp_server_connection_info_post()
    {
        MHD_destroy_post_processor(fPostprocessor);
    }
    
    int postProcess(const char* upload_data, size_t* upload_data_size)
    {
        MHD_post_process(fPostprocessor, upload_data, *upload_data_size);
        *upload_data_size = 0;
        return MHD_YES;
    }
};

struct dsp_server_connection_info_get : public dsp_server_connection_info  {
    
    dsp_server_connection_info_get():dsp_server_connection_info()
    {}
};

// Same prototype LLVM/REMOTE DSP are using for allocation/desallocation

class DSPServer {
    
        friend struct dsp_server_connection_info_post;
        friend struct dsp_server_connection_info;
    
    private:
    
        createFactoryDSPCallback fCreateDSPFactoryCb;
        void* fCreateDSPFactoryCb_arg;
    
        deleteFactoryDSPCallback fDeleteDSPFactoryCb;
        void* fDeleteDSPFactoryCb_arg;
    
        createInstanceDSPCallback fCreateDSPInstanceCb;
        void* fCreateDSPInstanceCb_arg;
    
        deleteInstanceDSPCallback fDeleteDSPInstanceCb;
        void* fDeleteDSPInstanceCb_arg;
    
        TMutex fLocker;
        pthread_t fThread;
        int fPort;
    
        // Factories that can be instanciated.
        // The remote client asking for a new DSP Instance has to send an SHA key corresponding to an existing factory
        FactoryTable fFactories;
    
        // List of currently running DSP. Use to keep track of Audio that would have lost their connection
        list<audio_dsp*> fRunningDsp;
        MHD_Daemon* fDaemon;    // Running http daemon
    
        void open(audio_dsp* dsp);
    
        // Creates the html to send back
        int sendPage(MHD_Connection* connection, const string& page, int status_code, const string& type);
    
        void stopNotActiveDSP();
    
        // Reaction to a GET request
        int answerGet(MHD_Connection* connection, const char* url);
    
        // Reaction to a POST request
        int answerPost(MHD_Connection* connection, const char* url,
                       const char* upload_data, size_t* upload_data_size,
                       void** con_cls);
    
        // Reaction to a /CreateInstance request --> Creates llvm_dsp_instance & NetJack slave
        bool createInstance(dsp_server_connection_info* con_info);
    
        // Reaction to a /Delete request --> stops NetJack slave and delete object
        bool deleteInstance(const string& instance_key);
    
        int createConnection(MHD_Connection* connection, const char* method, void** con_cls);
    
        bool start(const string& instance_key);
        bool stop(const string& instance_key);
    
        bool getAvailableFactories(MHD_Connection* connection);
    
        bool getFactoryFromSHAKey(MHD_Connection* connection, dsp_server_connection_info* info);
        bool createFactory(MHD_Connection* connection, dsp_server_connection_info* info);
    
        bool createInstance(MHD_Connection* connection, dsp_server_connection_info* info);
        bool deleteInstance(MHD_Connection* connection, dsp_server_connection_info* info);
    
        bool start(MHD_Connection* connection, dsp_server_connection_info* info);
        bool stop(MHD_Connection* connection, dsp_server_connection_info* info);
    
        bool crossCompileFactory(MHD_Connection* connection, dsp_server_connection_info* info);
    
        bool deleteFactory(MHD_Connection* connection, dsp_server_connection_info* info);
    
        // Register Service as available
        static void* registration(void* arg);
    
        // Callback of another thread to wait netjack audio connection without blocking the server
        static void* open(void* arg);
    
        // Callback that processes the data send to the server
        static int iteratePost(void* coninfo_cls, MHD_ValueKind kind, const char* key,
                               const char* filename, const char* content_type,
                               const char* transfer_encoding, const char* data,
                               uint64_t off, size_t size);
    
        static void requestCompleted(void* cls, MHD_Connection* connection, void** con_cls, MHD_RequestTerminationCode toe);
    
        // Reaction to any kind of connection to the Server
        static int answerToConnection(void* cls, MHD_Connection* connection,
                                      const char* url, const char* method,
                                      const char* version, const char* upload_data,
                                      size_t* upload_data_size, void** con_cls);
    
    public:
    
        DSPServer(int argc, const char* argv[]);
        virtual ~DSPServer();
    
        // Start server on specified port
        bool start(int port = 7777);
        void stop();
    
        void setCreateDSPFactoryCallback(createFactoryDSPCallback callback, void* callback_arg)
        {
            fCreateDSPFactoryCb = callback;
            fCreateDSPFactoryCb_arg = callback_arg;
        }
        void setDeleteDSPFactoryCallback(deleteFactoryDSPCallback callback, void* callback_arg)
        {
            fDeleteDSPFactoryCb = callback;
            fDeleteDSPFactoryCb_arg = callback_arg;
        }
    
        void setCreateDSPInstanceCallback(createInstanceDSPCallback callback, void* callback_arg)
        {
            fCreateDSPInstanceCb = callback;
            fCreateDSPInstanceCb_arg = callback_arg;
        }
        void setDeleteDSPInstanceCallback(deleteInstanceDSPCallback callback, void* callback_arg)
        {
            fDeleteDSPInstanceCb = callback;
            fDeleteDSPInstanceCb_arg = callback_arg;
        }
    
};

// Helper class

struct AudioStarter {
    
    DSPServer* fServer;
    audio_dsp* fDSP;
    
    AudioStarter(DSPServer* server, audio_dsp* dsp):fServer(server),fDSP(dsp)
    {}
    
};

// Public C++ API

class EXPORT remote_dsp_server {
    
    public:
    
        bool start(int port = 7777);
        void stop();
    
        void setCreateDSPFactoryCallback(createFactoryDSPCallback callback, void* callback_arg);
        void setDeleteDSPFactoryCallback(deleteFactoryDSPCallback callback, void* callback_arg);
    
        void setCreateDSPInstanceCallback(createInstanceDSPCallback callback, void* callback_arg);
        void setDeleteDSPInstanceCallback(deleteInstanceDSPCallback callback, void* callback_arg);
    
};

EXPORT remote_dsp_server* createRemoteDSPServer(int argc, const char* argv[]);

EXPORT void deleteRemoteDSPServer(remote_dsp_server* compiler);

#endif

