//
// 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 <stdlib.h>
#include <stdio.h>
#include <string.h>

#include "utils.h"
#include "conf.h"
#include "hash.h"
#include "var.h"
#include "str.h"
#include "security.h"

extern char tmp[MAXMAXLEN];

static HashTable* _variableHash = NULL;

//////////////////////////////////////////////////////////////////////////////////
//
// Functions related to internal "variables" handling
//
//////////////////////////////////////////////////////////////////////////////////

varData* searchVar(const char *id)
{
    if (id == NULL) {
        return NULL;
    }

    //strcpy(tmp, "searchVar() >");
    //strcat(tmp,id);
    //strcat(tmp,"<");
    //logger(L_DBG, tmp);
    
    varData* recalledObject = hashFind(_variableHash, id);
    
    return recalledObject;
}

// caller function must free data
// need to dup it because var can contain binary data (so size field matters)
char* dupVarValue(const char *name)
{
    varData* v = searchVar(name);
    if (v != NULL && 
        v->value != NULL && 
        v->value[0] != '\0') {
	
        char *d = (char*) malloc(v->size+1);
        strncpy(d,v->value,v->size);
        d[v->size] = '\0';
        return d;
    }
    return NULL;
}

const char* getVarValue(const char *id, int *sz)
{
    if (id == NULL) {
        return NULL;
    }
    varData* ptr = searchVar(id);
    
    (*sz) = (ptr ? ptr->size : 0);
    
    return (ptr ? ptr->value : NULL);
}

static void variable_destroyed(void* data) 
{
   varData * vd = (varData*) data;
    
   if (vd->value != NULL) {
       free(vd->value);
       vd->value = NULL;
   }
   free(vd);
}

static varData* initVarData(const char *name, const char *val, int sz)
{ 
    //DEBUG2("initVarData()  >%s< >%s< %d", name, (val ? val : "NULL"), sz);
    
    varData * v = (varData *) malloc(sizeof(varData));
 
    if (sz > 0 && val != NULL) {
        v->value = (char*) malloc(sz);
        memcpy((void*)v->value, (const void *) val, sz); // can not use strdup() since val can contains binary data
    } else {
        v->value = NULL;
    }
    v->size = sz;
    
    return v;
}

static void arepeatHook() 
{
    // clean auto-repeat flag in any case
    setRepeatNow(NULL);
}

static void loggingHook() 
{
    char* vl = dupVarValue(VAR_LOGGING);
    setLog(vl);
    free(vl);
}

static void iviewerHook() 
{
    char* vl = dupVarValue(VAR_IVIEWER);
    setIViewer(vl);
    free(vl);
}

static void bemusedHook() 
{
    char* vl = dupVarValue(VAR_BEMUSED);
    setBemused(vl);
    free(vl);
}

static void waitTimeoutHook() 
{
    char* vl = dupVarValue(VAR_WAITEXEC);
    setWaitTime(vl);
    free(vl);
}

static void allowedOnlyHook() 
{
    char* vl = dupVarValue(VAR_ALLOWED_ONLY);
    setAllowedOnly(vl);
    free(vl);
}

static void checkVarHooks(const char* name)
{   
    //DEBUG2("checkVarHooks  >%s<", name);
    static struct 
        { 
	    const char* name; 
	    void (*hook)(void);
	} 
    varHooks[] = {
    	#ifdef USE_ICONV
	{ VAR_TO_ENCODING,   encodingHook},
    	{ VAR_FROM_ENCODING, encodingHook},
	#endif
    	{ VAR_LOGGING,       loggingHook},
    	{ VAR_AUTOREPEAT,    arepeatHook},
    	{ VAR_IVIEWER,       iviewerHook},
    	{ VAR_BEMUSED,       bemusedHook},
    	{ VAR_WAITEXEC,      waitTimeoutHook},
    	{ VAR_ALLOWED_ONLY,  allowedOnlyHook},
    	{ NULL,              0}
    };
   
    size_t idx;
    for (idx = 0; varHooks[idx].name; ++idx) {
    	if (strcmp(name, varHooks[idx].name) == 0) {
    	    if (varHooks[idx].hook != NULL) {
	        (*(varHooks[idx].hook))();
	    }
	    break;
    	}
    }
}

// can be used only to _string_ data
// (binary data can contain \0 inside)
int setVarSimple(const char *name, const char* val)
{
    return setVar(name, val, strlen(val));
}

int setVar(const char *name, const char *val, int sz)
{
    if (name == NULL) {
        return EXIT_NOK;
    }
    DEBUG2("setVar() >%s< -> >%s< %d", name, (val == NULL ? "NULL" : val), sz);
    
    if (!_variableHash) {
        _variableHash = hashNew((DestroyCallback) variable_destroyed);
    }

    varData * vd = initVarData(name, val, sz);
    hashReplace(_variableHash, name, vd);
    checkVarHooks(name);

    return EXIT_OK;
}

void addInternalVars()
{
    char *d = getenv("HOME");
    setVar("Home", d, d ? strlen(d) : 0);
    
    string_t* tmp = stringNew(d ? d : "");
    stringAppend(tmp,"/.anyRemote");
    setVarSimple("TmpDir", tmp->str);
    stringFree(tmp, BOOL_YES);

    setVar(VAR_CFGDIR, NULL, 0);

    struct { 
	    const char* name; 
	    const char* val; 
	} 
    internalVars[] = {
	#ifdef USE_XTEST
     	{ "Xtest", "yes"},
	#else
    	{ "Xtest", "no"},
	#endif
	#ifdef USE_BLUEZ
    	{ "Bluez", "yes"},
	#else
    	{ "Bluez", "no"},
	#endif
	#ifdef USE_DBUS
    	{ "Dbus", "yes"},
	#else
    	{ "Dbus", "no"},
	#endif
    	{ "MixerCard",    "0"},
    	{ "MixerChannel", "Master"},
    	{ VAR_AUTOCONN,   "false"},
    	{ VAR_AUTOREPEAT, "false"},
    	{ "Baudrate",     "19200"},
    	{ VAR_CHARSET,    "8859-1"},		// AT mode only
    	{ "CmerOff",      ""},	   		// AT mode only
    	{ "CmerOn",       ""},			// AT mode only
    	{ "Device",       DEFAULT_DEVICE},
	#ifdef USE_ICONV
    	{ VAR_TO_ENCODING,   "UTF-8"},
    	{ VAR_FROM_ENCODING, ""},
	#endif
    	{ VAR_LOGGING,    "false"},
    	{ VAR_WAITEXEC,   "-1"},
    	{ "RetrySeconds", "60"},
    	{ "ServiceName",  "anyRemote"},
    	{ "TwoWayComm",   "false"},		// Send something to device ?
    	{ "ToMainMenu",   "E"},			// AnyMenu  -> MainMenu - AT mode only
    	{ VAR_IVIEWER,    "false"},		// Use iViewer tricks ?
    	{ VAR_BEMUSED,    "false"},		// Use Bemused tricks ?
    	{ "UpdateTimeout", "5"},		// Update player state timeout
    	{ NULL,           0}
    };
    
    size_t idx;
    for (idx = 0; internalVars[idx].name; ++idx) {
       setVarSimple(internalVars[idx].name, internalVars[idx].val);
    }
}

void varPrinter(void* key, void* value, void* user_data)
{
    const char *name   = (const char *)    key;
    const varData* ptr = (const varData *) value;
    
    // value of variable can contain \0 inside
    memset(tmp, 0, MAXMAXLEN);
    sprintf(tmp, "\t%-16s -> ", name);
    strncat(tmp, ptr->value != NULL ? ptr->value : "(empty)",ptr->value != NULL ? ptr->size : 7);
    logger(L_CFG, tmp);
}

void printVars()
{
    sprintf(tmp, "[Variables]");
    logger(L_CFG, tmp);
    
    hashForeach(_variableHash, varPrinter, NULL);
    
    sprintf(tmp, "[End]");
    logger(L_CFG, tmp);
}

void freeVars()
{
    logger(L_DBG, "freeVars()");
    hashDestroy(_variableHash);
    _variableHash = NULL;
}
