//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2016 Mikhail Fedotov <anyremote@mail.ru>
//
// 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 3 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., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
//

#include <stdbool.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#ifdef USE_DBUS
#include <dbus/dbus.h>
#include <dbus/dbus-glib.h>
#endif

#include "ar_dbus.h"
#include "cmds.h"
#include "common.h"
#include "utils.h"
#include "var.h"

extern int macroCmdCallback(char *descr, char *exec, cmdParams* params);  // from cmds.c

extern char tmp[MAXMAXLEN];

typedef struct Dp {
    char        *id;

    char        *service;
    char        *path;
    char        *interface;

#ifdef USE_DBUS
    DBusGProxy  *proxy;
#endif
    struct Dp   *next;
} type_dp;

typedef struct Cb {
    char        *id;
    struct Cb   *next;
} type_cb;

int getParamType(char* tc)
{
    if (!tc) {
        return TYPE_ERROR;
    }
    if (strcmp(tc,"int")==0) {
        return TYPE_INT;
    }
    if (strcmp(tc,"uint")==0) {
        return TYPE_UINT;
    }
    if (strcmp(tc,"double")==0) {
        return TYPE_DOUBLE;
    }
    if (strcmp(tc,"bool")==0) {
        return TYPE_BOOL;
    }
    if (strcmp(tc,"string")==0) {
        return TYPE_STRING;
    }
    return TYPE_ERROR;
}

#ifdef USE_DBUS

DBusGConnection *connection = NULL;
type_dp         *proxies    = NULL;
type_cb         *callbacks  = NULL;

void intCb (DBusGProxy *proxy, int data, gpointer user_data)
{
    //printf("intCb %s\n", (char*) user_data);fflush(stdout);
    //printf("data %d\n",data);

    cmdParams p;
    strcpy(p.index,"-1");
    sprintf(p.value,"%d",data);

    macroCmdCallback((char*) user_data, NULL, &p);
}

void uintCb (DBusGProxy *proxy, uint data, gpointer user_data)
{
    //printf("uintCb %s\n", (char*) user_data);fflush(stdout);
    //printf("data %u\n",data);

    cmdParams p;
    strcpy(p.index,"-1");
    sprintf(p.value,"%u",data);

    macroCmdCallback((char*) user_data, NULL, &p);
}

void doubleCb (DBusGProxy *proxy, double data, gpointer user_data)
{
    //printf("doubleCb %s\n", (char*) user_data);fflush(stdout);
    //printf("data %f\n",data);

    cmdParams p;
    strcpy(p.index,"-1");
    sprintf(p.value,"%f",data);

    macroCmdCallback((char*) user_data, NULL, &p);
}

void boolCb (DBusGProxy *proxy, gboolean data, gpointer user_data)
{
    //printf("boolCb %s\n", (char*) user_data);fflush(stdout);
    //printf("data %s\n",(data?"true":"false"));

    cmdParams p;
    strcpy(p.index,"-1");
    sprintf(p.value,"%s",(data ? "true" : "false"));

    macroCmdCallback((char*) user_data, NULL, &p);
}

void stringCb (DBusGProxy *proxy, const char *message, gpointer user_data)
{
    //printf("stringCb %s\n", (char*) user_data);fflush(stdout);
    //printf("msg %s\n",message);

    cmdParams p;
    strcpy(p.index,"-1");
    strncpy(p.value,message,MAXARGLEN);

    macroCmdCallback((char*) user_data, NULL, &p);
}

static type_dp* findProxy(char *id, type_dp** prev)
{
    if (id == NULL) {
        return NULL;
    }

    type_dp* ptr = proxies;

    while (ptr != NULL && strncmp(ptr->id,id,MTEXTLEN) != 0) {
        if (prev != NULL) {
            *prev = ptr;
        }
        ptr  = ptr->next;
    }
    return ptr;
}

static void freeProxy(type_dp *proxy)
{
    g_object_unref(proxy->proxy);
    free(proxy->id);
    free(proxy->service);
    free(proxy->path);
    free(proxy->interface);
    free(proxy);
}

static void freeProxies()
{
    logger(L_DBG, "freeProxies()");
    type_dp *tm;

    while (proxies != NULL) {
        tm     = proxies;
        proxies = proxies->next;
        freeProxy(tm);
    }
}

static void freeProxyByName(char *id)
{
    type_dp *prev = NULL;
    type_dp *prox = findProxy(id, (type_dp**) &prev);
    if (prox != NULL) {
        if (prox == proxies) { 	// remove in head
            proxies = prox->next;
        } else {
            if (prev != NULL) {
                prev->next = prox->next;
            } else {
                logger(L_ERR, "[EX]: freeProxyByName(): Previous item absent for non-first element");
                return;
            }
        }
        freeProxy(prox);
    } else {
        logger(L_ERR, "[EX]: freeProxyByName(): no such dbus connection");
    }
}

static void addProxy(char *id,char *s, char *p, char *i, DBusGProxy *proxy)
{
    logger(L_DBG, "addProxy()");
    if (id == NULL || proxy == NULL) {
        return;
    }

    if (findProxy(id,NULL) != NULL) {
        logger(L_DBG, "addProxy(): proxy already exists");
        return;
    }
    type_dp *tm = (type_dp *) calloc(sizeof(type_dp),1);

    tm->id        = strdup(id);
    tm->service   = strdup(s);
    tm->path      = strdup(p);
    tm->interface = strdup(i);

    tm->proxy     = proxy;
    if (proxy == NULL) {
        logger(L_DBG, "addProxy(): proxy is NULL");
        return;
    }

    // Insert in head
    if (proxies == NULL) {  // Insert first
        tm->next = NULL;
    } else {
        tm->next = proxies;
    }
    proxies = tm;
}

static char* addCb(char *id)
{
    logger(L_DBG, "addCb()");
    if (id == NULL) {
        return NULL;
    }

    type_cb *tmp = (type_cb *) calloc(sizeof(type_cb),1);
    tmp->id      = strdup(id);

    // Insert in head
    if (callbacks == NULL) {  // Insert first
        tmp->next = NULL;
    } else {
        tmp->next = callbacks;
    }
    callbacks = tmp;

    return tmp->id;
}

static void freeCbs()
{
    logger(L_DBG, "freeCbs()");
    type_cb *tmp;

    while (callbacks != NULL) {
        tmp       = callbacks;
        callbacks = callbacks->next;

        free(tmp->id);
    }
}

int dbusInit()
{
    //logger(L_DBG, "dbusInit");
    GError *error = NULL;

    #if (GLIB_MAJOR_VERSION <= 2 && GLIB_MINOR_VERSION < 36)
    g_type_init ();
    #endif

    dbus_threads_init_default();

    connection = dbus_g_bus_get (DBUS_BUS_SESSION, &error);
    if (connection == NULL) {
        ERROR2("[EX]: Failed to open connection to bus: %s", error->message)
        g_error_free (error);
        return EXIT_NOK;
    }
    return EXIT_OK;
}

void dbusFinish()
{
    freeProxies();
    freeCbs();
    dbus_g_connection_unref(connection);
}

//
// Input can be something like:
// connect,_id_,_service_,_path_,_interface_
// set,_id_,_method_,[int|uint|double|bool|string,_value_]
// get,_id_,_method_,int|double|bool|string,_variable_name_
// signal,_id_,_method_,int|double|bool|string,_callback_id_
//
int dbusCommand(int subtype, const char *descr, const char* cmdString, cmdParams* params)
{
    logger(L_INF, "Command: Dbus");
    
    GError   *error  = NULL;
    gboolean retCode = TRUE;

    if (connection == NULL) {
        logger(L_ERR, "[EX]: dbusCommand(): no connection");
        return EXIT_NOK;
    }
    if (descr == NULL) {
        logger(L_ERR, "[EX]: dbusCommand(): no command");
        return EXIT_NOK;
    }
    DEBUG2("[EX]: dbusCommand(): %s", descr)

    char * cmd = strdup(descr);

    char* token = strtok(cmd,",");
    while (token) {

        char * id   = strtok(NULL,",");
        if (strcmp(token,"connect") == 0) {
            logger(L_DBG, "[EX]: dbusCommand(): connect");

            char * serv = strtok(NULL,",");
            char * path = strtok(NULL,",");
            char * intf = strtok(NULL,",");

            if (id != NULL && serv != NULL && path != NULL && intf != NULL) {

                //DEBUG2("[EX]: dbusCommand(): dbus_g_proxy_new_for_name %s %s %s", serv,path,intf)
                DBusGProxy  *proxy = dbus_g_proxy_new_for_name (connection,
                                     serv, 	// ex.: "org.freedesktop.DBus",
                                     path,   	// ex.: "/org/freedesktop/DBus",
                                     intf); 	// ex.: "org.freedesktop.DBus");
				     
                addProxy(id,serv,path,intf,proxy);	// proxy can be NULL here
                if (proxy == NULL)  {
                    logger(L_DBG, "[EX]: dbusCommand(): get NULL proxy");
                }
            } else {
                logger(L_ERR, "[EX]: dbusCommand(): improper input id-service-path-interface");
            }

        } else if (strcmp(token,"close") == 0) {
            logger(L_DBG, "[EX]: dbusCommand(): close");

            freeProxyByName(id);

        } else if (strcmp(token,"set") == 0) {
            logger(L_DBG, "[EX]: dbusCommand(): set");

            type_dp *prox = findProxy(id,NULL);

            if (prox == NULL) {
                ERROR2("[EX]: proxy %s not registered",id);
                return EXIT_NOK;
            }

            if (prox->proxy == NULL) {
                logger(L_DBG, "[EX]: dbusCommand(): recreate proxy");
                DBusGProxy  *proxy = dbus_g_proxy_new_for_name (connection,
                                     prox->service,prox->path,prox->interface);
                if (proxy == NULL)  {
                    logger(L_DBG, "[EX]: dbusCommand(): get NULL proxy");
                }
                prox->proxy = proxy;
            }

            if (prox->proxy != NULL) {
                char * method = strtok(NULL,",");
                char * type   = strtok(NULL,",");
                char * value  = strtok(NULL,",");

                if (method != NULL && type != NULL && value != NULL) {
                    int sendType = getParamType(type);
                    DEBUG2("[EX]: dbusCommand(): method %s with 1 param of type %s",method,type);

                    if (sendType == TYPE_INT) {
                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_INT, atoi(value),
                                                     G_TYPE_INVALID, G_TYPE_INVALID);

                    } else if (sendType == TYPE_UINT) {

                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_UINT, atoi(value),
                                                     G_TYPE_INVALID, G_TYPE_INVALID);

                    } else if (sendType == TYPE_DOUBLE) {

                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_DOUBLE, (double) atof(value),
                                                     G_TYPE_INVALID, G_TYPE_INVALID);

                    } else if (sendType == TYPE_BOOL) {

                        gboolean sb = (strcmp(value,"true") == 0);
                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_BOOLEAN, sb,
                                                     G_TYPE_INVALID, G_TYPE_INVALID);

                    } else if (sendType == TYPE_STRING) {

                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_STRING, value,
                                                     G_TYPE_INVALID, G_TYPE_INVALID);
                    } else {
                        ERROR2("[EX]: dbusCommand(): unsupported param type %s",type);
                    }

                    if (!retCode) {
                        ERROR2("[EX]: dbusCommand(): failed in d-bus proxy call %s",method);
                        g_object_unref(prox->proxy);
                        prox->proxy = NULL;
                    }
                } else {
                    DEBUG2("[EX]: dbusCommand(): method %s with no params",method);
                    retCode = dbus_g_proxy_call (prox->proxy, method, &error, G_TYPE_INVALID, G_TYPE_INVALID);
                }
            }

        } else if (strcmp(token,"get") == 0) {
            logger(L_DBG, "[EX]: dbusCommand(): get");

            type_dp *prox = findProxy(id,NULL);

            if (prox == NULL) {
                ERROR2("[EX]: proxy %s not registered",id);
                return EXIT_NOK;
            }

            char retVals[512];

            if (prox->proxy == NULL) {
                logger(L_DBG, "[EX]: dbusCommand(): recreate proxy");
                DBusGProxy  *proxy = dbus_g_proxy_new_for_name (connection,
                                     prox->service,prox->path,prox->interface);
                if (proxy == NULL)  {
                    logger(L_DBG, "[EX]: dbusCommand(): get NULL proxy");
                }
                prox->proxy = proxy;
            }

            if (prox->proxy != NULL) {

                char * method = strtok(NULL,",");
                char * type   = strtok(NULL,",");
                char * var    = strtok(NULL,",");

                if (method != NULL && type != NULL && var != NULL) {
                    int retType = getParamType(type);
                    int ret;
                    DEBUG2("[EX]: dbusCommand(): method %s with 1 param of type %s",method,type);

                    if (retType == TYPE_INT) {

                        gint ri;
                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_INVALID, G_TYPE_INT, &ri, G_TYPE_INVALID);

                        sprintf(retVals,"%d",ri);
                        DEBUG2("[EX]: dbusCommand(): got int %d",ri);

                        ret = setVar(var,retVals,strlen(retVals));
                        if (ret != EXIT_OK) {
                            logger(L_DBG,"[EX]: dbusCommand(): Fails in setVar() int");
                        }

                    } else if (retType == TYPE_UINT) {

                        guint ri;
                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_INVALID, G_TYPE_UINT, &ri, G_TYPE_INVALID);

                        sprintf(retVals,"%u",ri);
                        DEBUG2("[EX]: dbusCommand(): got uint %u",ri);

                        ret = setVar(var,retVals,strlen(retVals));
                        if (ret != EXIT_OK) {
                            logger(L_DBG,"[EX]: dbusCommand(): Fails in setVar() uint");
                        }

                    } else if (retType == TYPE_DOUBLE) {

                        gdouble rd;
                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_INVALID, G_TYPE_DOUBLE, &rd, G_TYPE_INVALID);
                        sprintf(retVals,"%f",rd);
                        DEBUG2("[EX]: dbusCommand(): got double %f",rd);

                        ret = setVar(var,retVals,strlen(retVals));
                        if (ret != EXIT_OK) {
                            logger(L_DBG,"[EX]: dbusCommand(): Fails in setVar() double");
                        }

                    } else if (retType == TYPE_BOOL) {

                        gboolean rb;
                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_INVALID, G_TYPE_BOOLEAN, &rb, G_TYPE_INVALID);
                        sprintf(retVals,"%s", (rb?"true":"false"));

                        ret = setVar(var,retVals,strlen(retVals));
                        if (ret != EXIT_OK) {
                            logger(L_DBG,"[EX]: dbusCommand(): Fails in setVar() boolean");
                        }

                    } else if (retType == TYPE_STRING) {

                        char *rc = NULL;
                        retCode = dbus_g_proxy_call (prox->proxy, method, &error,
                                                     G_TYPE_INVALID, G_TYPE_STRING, rc, G_TYPE_INVALID);

                        memset(retVals,0,512);
                        if (rc != NULL) {
                            strncpy(retVals,rc,511);
                        }
                        ret = setVar(var,retVals,strlen(retVals));
                        if (ret != EXIT_OK) {
                            logger(L_DBG,"[EX]: dbusCommand(): Fails in setVar() string");
                        }

                        g_free(rc);
                    } else {
                        ERROR2("[EX]: dbusCommand(): unsupported param type %s",type);
                    }

                    if (!retCode) {
                        ERROR2("[EX]: dbusCommand(): failed in d-bus proxy call %s",method);
                        g_object_unref(prox->proxy);
                        prox->proxy = NULL;
                    }
                } else {
                    logger(L_DBG, "[EX]: dbusCommand(): set with no output params");
                }
            }

        } else if (strcmp(token,"signal") == 0) {
            logger(L_DBG, "[EX]: dbusCommand(): signal");

            type_dp *prox = findProxy(id,NULL);

            if (prox == NULL) {
                ERROR2("[EX]: proxy %s not registered",id);
                return EXIT_NOK;
            }

            if (prox->proxy == NULL) {
                logger(L_DBG, "[EX]: dbusCommand(): recreate proxy");
                DBusGProxy  *proxy = dbus_g_proxy_new_for_name (connection,
                                     prox->service,prox->path,prox->interface);
                if (proxy == NULL)  {
                    logger(L_DBG, "[EX]: dbusCommand(): get NULL proxy");
                }
                prox->proxy = proxy;
            }
            if (prox->proxy != NULL) {

                char * signal = strtok(NULL,",");
                char * type   = strtok(NULL,",");
                char * cback  = strtok(NULL,",");

                if (type != NULL && cback != NULL) {
                    int stype = getParamType(type);

                    DEBUG2("[EX]: dbusCommand(): signal %s with 1 param of type %s",signal,type);

                    char* cb = addCb(cback);

                    if (stype == TYPE_INT) {

                        dbus_g_proxy_add_signal(prox->proxy, signal ,G_TYPE_INT, G_TYPE_INVALID);
                        dbus_g_proxy_connect_signal(prox->proxy, signal,
                                                    G_CALLBACK(intCb), cb, NULL);

                    } else if (stype == TYPE_UINT) {

                        dbus_g_proxy_add_signal(prox->proxy, signal ,G_TYPE_UINT, G_TYPE_INVALID);
                        dbus_g_proxy_connect_signal(prox->proxy, signal,
                                                    G_CALLBACK(uintCb), cb, NULL);

                    } else if (stype == TYPE_DOUBLE) {

                        dbus_g_proxy_add_signal(prox->proxy, signal ,G_TYPE_DOUBLE, G_TYPE_INVALID);
                        dbus_g_proxy_connect_signal(prox->proxy, signal,
                                                    G_CALLBACK(doubleCb), cb, NULL);

                    } else if (stype == TYPE_BOOL) {

                        dbus_g_proxy_add_signal(prox->proxy, signal, G_TYPE_BOOLEAN, G_TYPE_INVALID);
                        dbus_g_proxy_connect_signal(prox->proxy, signal,
                                                    G_CALLBACK(boolCb), cb, NULL);

                    } else if (stype == TYPE_STRING) {

                        dbus_g_proxy_add_signal(prox->proxy, signal ,G_TYPE_STRING, G_TYPE_INVALID);
                        dbus_g_proxy_connect_signal(prox->proxy, signal,
                                                    G_CALLBACK(stringCb), cb, NULL);

                    } else {
                        ERROR2("[EX]: dbusCommand(): unsupported param type %s",type);
                    }
                } else {
                    logger(L_DBG, "[EX]: dbusCommand(): set with no output params");
                }
            }

        } else {
            ERROR2("[EX]: dbusCommand(): improper input - command is %s",token);
            free(cmd);
            return EXIT_NOK;
        }

        token = strtok(NULL,",");
    }

    free(cmd);

    return EXIT_OK;
}

#else

// Just simple stubs to make compilation possible

int dbusInit()
{
    return EXIT_NOK;
}

void dbusFinish()
{
}

int dbusCommand(int subtype, const char *descr, const char* cmdString, cmdParams* params)
{    
    return EXIT_OK;
}


#endif
