//
// 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
//

//#define _GNU_SOURCE
#include <stdio.h>

#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>

#include "ar_dbus.h"
#include "cmds.h"
#include "common.h"
#include "conf.h"
#include "dispatcher.h"
#include "peer.h"
#include "executor.h"
#include "parse.h"
#include "utils.h"
#include "alarm.h"
#include "timer.h"
#include "var.h"
#include "mode.h"
#include "xemulate.h"
#include "pr_btspp.h"
#include "sys_util.h"

extern char         callerId[MAXLEN];
extern type_key*    repeatCmdPtr;
extern char         tmp[MAXMAXLEN];
extern SingleList*  _peers;

static int processCommands(SingleList* commands, cmdParams* p);

char tmptext[MAXCMDLEN];

int  remoteOn = 1;

static string_t* substParams(const char* in, cmdParams *params)
{
    if (in == NULL || *in == '\0') { // Empty command. Skip it.
        //logger(L_ERR,"[EX]: substParams(): null input");
        return NULL;
    }
    //DEBUG2("[EX]: substParams >%s<", in);

    char *command =  strdup(in);
    char *ptr     = command;
    char *papameterized = NULL;

    int pc = strlen(PARAM_CALLID);
    int pm = strlen(PARAM_MODE);
    int pp = strlen(PARAM_PARAM);
    int pi = strlen(PARAM_INDEX);
    int pb = strlen(PARAM_BTADDR);

    string_t* cmdBuffer = stringNew("");

    while(ptr != NULL) {
        //DEBUG2("[EX]: current cmd >%s<", ptr);

        papameterized = strstr(ptr, PARAM_START);   // $(

        //if (papameterized != NULL) {
        //    DEBUG2("[EX]: papameterized >%s<", papameterized);
        //}

        if (papameterized == NULL) { // Just copy

            //logger(L_DBG, "[EX]: no more parameters");

            stringAppend(cmdBuffer, ptr);
            
            //DEBUG2("[EX]: cur cmd >%s<", cmdBuffer->str);

            break;

        } else if(strncmp(papameterized+2,PARAM_CALLID,pc) == 0) {

            *papameterized = '\0';

            stringAppend(cmdBuffer, ptr);    // First part
            stringAppend(cmdBuffer, callerId);    // Add params

            ptr = papameterized + pc + 2;

        } else if (strncmp(papameterized + 2,PARAM_MODE,pm) == 0) {

            *papameterized = '\0';

            const char* m = getModeName();

            stringAppend(cmdBuffer, ptr);    // First part
            stringAppend(cmdBuffer, m);        // Add params

            ptr = papameterized + pm + 2;

        } else if (strncmp(papameterized + 2,PARAM_PARAM,pp) == 0) {

            // This is a trick to make Bemused commands like VOLM<volume> works
            // also it is used in Java Client list handling

            *papameterized = '\0';

            stringAppend(cmdBuffer, ptr);    // First part
            if (params == NULL) {   // value can be empty
                logger(L_WARN, "[EX]: No parameters supplied!");
                
                // this is useless
                //stringAppend(cmdBuffer, PARAM_START);
                //stringAppend(cmdBuffer, PARAM_PARAM);
           
            } else {
                stringAppend(cmdBuffer, params->value);    // Add params
            }
            ptr = papameterized + pp + 2;

        } else if (strncmp(papameterized + 2,PARAM_INDEX,pi) == 0) {

            // Used in Java Client list handling

            *papameterized = '\0';

            stringAppend(cmdBuffer, ptr);    // First part
            if (params == NULL) {
                logger(L_WARN, "[EX]: No parameters supplied !");

                // this is useless
                //stringAppend(cmdBuffer, PARAM_START);
                //stringAppend(cmdBuffer, PARAM_INDEX);
            } else {
                stringAppend(cmdBuffer, params->index);    // Add params
            }
            ptr = papameterized + pi + 2;

        } else if (strncmp(papameterized + 2,PARAM_BTADDR,pb) == 0) {

            *papameterized = '\0';

            // First part
            stringAppend(cmdBuffer, ptr);

            // Add XX:XX:XX:XX:XX:XX
            char *d;
            if ((d = getBtAddress()) != NULL) {
                stringAppend(cmdBuffer, d);
            }

            ptr = papameterized + pb + 2;

        } else {

            int pLen = (papameterized == NULL ? 0 : 1);
            if (pLen) {
                char * br = index(papameterized+2,')');
                pLen = (br == NULL ? 0 : br - papameterized - 2);
            }

            char* varName = malloc(pLen + 1);
            strncpy(varName, papameterized + 2, pLen);
            varName[pLen] = '\0';

            *papameterized = '\0';
            stringAppend(cmdBuffer, ptr);        // First part

            varData* var = searchVar(varName);

            if (var != NULL) {

                //logger(L_DBG, "[EX]: substitute: searchVar() OK");

                if (var->size > 0) {
                    stringAppendLen(cmdBuffer, var->value, var->size);    // Add params
                //} else {
                //    logger(L_DBG, "[EX]: substitute: empty var");
                }

                //DEBUG2("[EX]: cmd var value >%s<", cmdBuffer->str);

        // seems it is more confusing than helpful, but some sh scripting uses $()
        } else {    // Just copy

                //sprintf(tmp, "[EX]: Just copy >%s<", ptr);
                //logger(L_DBG,tmp);

                stringAppend(cmdBuffer, "$(");
                stringAppend(cmdBuffer, varName);
                stringAppend(cmdBuffer, ")");
            }
            ptr = papameterized + pLen + 3;
            free(varName);
        }
    }
    //DEBUG2("[EX]: substParams: final cmd >%s<", cmdBuffer->str);

    free(command);

    return cmdBuffer;
}

static char* executeSimpleCommand(const char* exec, size_t* sz)
{
    if (exec == NULL || strlen(exec) == 0) {
        logger(L_DBG, "[EX]: executeSimpleCommand null input");
        return NULL;
    }
    if (remoteOn != 1) {
        logger(L_DBG, "[EX]: executeSimpleCommand remote if OFF");
        return NULL;
    }

    DEBUG2("[EX]: executeSimpleCommand >%s<", exec);

    char* gstr = executeCommandPipe(exec, sz);
    //It is OK if gstr == NULL because command can have no output

    DEBUG2("[EX]: executeSimpleCommand RET bytes >%zd<", (*sz));
    return gstr;
}

static int processSendCmd(const char* sDescr)
{
    int ret = EXIT_OK;

    char all[MAXMAXLEN];
    memset(all,0,MAXMAXLEN);

    strncpy(all, sDescr, MAXMAXLEN-1);

    char *tag  = strtok(all, ",");
    if (tag == NULL) {
        logger(L_DBG, "[EX]: processSendCmd(): incorrect input (1)");
        return EXIT_NOK;
    }
    if (strlen(sDescr) <= strlen(tag) + 1) {   // Set(tag,) or Set(tag) without any value
        logger(L_DBG, "[EX]: processSendCmd(): incorrect input (2)");
        return EXIT_NOK;
    }
    char *ptr   = tag + strlen(tag) - 1;
    // strip spaces from tail
    while ( *ptr == '\0' || *ptr == '\n' || *ptr == '\t' || *ptr == ' ') {
        *ptr = '\0';
        ptr--;
    }
    ptr = tag + strlen(tag) + 1;
    // strip spaces from head of value
    while ( *ptr != '\0' && (*ptr == '\t' || *ptr == ' ')) {
        ptr++;
    }

    DEBUG2("[EX]: processSendCmd() is >%s< >%s<",tag,ptr);

    dMessage* dm = allocDMessage();
    dm->size     = 1;
    dm->subtype  = ID_SET_MAX;
    
    if (strcmp(tag, SEND_STRING) == 0) {

        dm->type  = DM_SENDS;
        dm->size  = strlen(ptr);

    } else if (strcmp(tag, SEND_BYTES) == 0) {

        dm->type  = DM_SENDB;

    } else {
        logger(L_DBG, "[EX]: processSendCmd(): incorrect input (3)");
        free(dm);
        return EXIT_NOK;
    }

    dm->value = (void*) strdup(ptr);
    sendToDispatcher(dm);

    return ret;
}

static void sendData(int type, int flag, char *data, int count)
{
    DEBUG2("[EX]: sendData() >%d<",count);

    dMessage* dm = allocDMessage();
    dm->size     = count;
    dm->subtype  = ID_SET_MAX;

    if (type == ID_SEND && flag) {
        logger(L_DBG,"[EX]: sendData() send as bytes");
        dm->type  = DM_SENDB;
    } else if (count > 0) { // just check count to be sure we goes here from right place
        dm->type  = DM_SENDS;
    }

    dm->value = (data ? (void*) strdup(data) : NULL);
    sendToDispatcher(dm);
}

int execDynamically(char *command)
{
    logger(L_DBG,"[EX]: execDynamically");
    SingleList* tmpCmd = NULL;
    storeCmds(&tmpCmd, command); // parse command on-the-fly

    int ret = processCommands(tmpCmd,NULL);
    freeCmds(tmpCmd);

    return ret;
}

static int loadCmds(int subtype, const char *file, const char* exec, cmdParams* params)
{
    INFO2("[EX]: Command: Load %s", (file ? file : "NULL"));

    if (file == NULL) {
        return EXIT_NOK;
    }

    FILE *fp=fopen(file,"r");
    if (fp == NULL) {
        return EXIT_NOK;
    }

    struct stat file_status;
    stat(file, &file_status);

    if (file_status.st_size <= 0) {
        logger(L_DBG,"[EX]: loadCmds - file is empty");
        fclose(fp);
        return EXIT_NOK;
    }

    char *command = (char*) malloc(file_status.st_size+1);
    *command = '\0';

    int ret = EXIT_OK;
    while (fgets(command, file_status.st_size+1, fp) != NULL) {
        DEBUG2("[EX]: Retrieved line %s of length %d ", command, (int) strlen(command));
        ret = execDynamically(command);
    }
    free(command);
    fclose(fp);
    return ret;
}


int handleHook(int hook)
{
    if (hook < 0 || hook >= ID_EVT_MAX) {
        return EXIT_NOK;
    }

    const char* _hookNames[] = {
        "hook_init",      // ID_EVT_INIT,
        "hook_exit",      // ID_EVT_EXIT,
        "hook_connect",   // ID_EVT_CONNECT,
        "hook_disconnect" // ID_EVT_DISCONNECT
    };

    DEBUG2("handleHook() >%d<",hook);

    char hookFile[MAXLEN];
    char *t = getenv("HOME");
    if (t) {
        strcpy(hookFile, t);
    } else {
        strcpy(hookFile, ".");
    }
    
    strcat(hookFile, "/.anyRemote/");
    strcat(hookFile, _hookNames[hook]);

    return loadCmds(-1, hookFile, NULL, NULL);
}

/*static void uploadPixOld(char *cmdLine)
{
    DEBUG2("uploadPix >%s<", cmdLine);
    int prefixSz = 0;                  // image,icon,_name_,_file_|image,window,_file_|cover, _file_

    if (strstr(cmdLine,UPLOAD_PIX)   != NULL) {
        prefixSz = strlen(UPLOAD_PIX) + 1;
    } else if (strstr(cmdLine,UPLOAD_COVER_DATA) != NULL) {
        prefixSz = strlen(UPLOAD_COVER_DATA) + 1;
    } else {
        int nCh = strlen(UPLOAD_ICON) + 1;
        prefixSz = nCh + strstr(cmdLine+nCh,",") - (cmdLine+nCh) + 1;
    } else {
        int nCh = strlen(UPLOAD_COVER) + 1;
        prefixSz = nCh + strstr(cmdLine+nCh,",") - (cmdLine+nCh) + 1;
    }

    char* fname = cmdLine + prefixSz;
    char* p = fname;
    while (*p != '\0') {    // remove trailing \n if exists
        if (*p == '\n') {
            *p = '\0';
            break;
        }
        p++;
    }
    //sprintf(tmp, "uploadPix file >%s<", fname);
    //logger(L_INF,tmp);

    FILE *fp;
    struct stat buf;

    long fLen = 0;
    if(stat(fname, &buf) == -1) {
        logger(L_ERR,"can't get file size!");
        if (strstr(cmdLine,UPLOAD_COVER_DATA) == NULL) {    // in case UPLOAD_COVER_DATA we will send empty image to clear previous one
            return;
        } else {
            logger(L_DBG,"uploadPix(): send empty cover");

            dMessage* dm = allocDMessage();
            dm->type     = DM_SET;
            dm->subtype  = ID_SET_MAX;
            dm->size  = 11;
            dm->value = (void*) strdup("Set(cover);");

            sendToDispatcher(dm);

            return;
        }
    } else {
        fLen = buf.st_size;
    }

    if (!S_ISREG (buf.st_mode)) {
        logger(L_ERR,"not regular file");
        return;
    }

    //sprintf(tmp, "uploadPix >%ld<", fLen);
    //logger(L_DBG,tmp);

    fp=fopen(fname,"r");
    if (fp == NULL) {
        logger(L_ERR,"can't open file!");
        return;
    }

    uint32_t szh32 = (uint32_t) fLen;
    uint32_t szi32 = htonl(szh32);

    char * fBuffer = (char*) calloc(fLen+prefixSz+12,1);    // 11 =  "Set("     ,<size of bin data=4bytes>    ");\0"
    if (fBuffer == NULL) {
        logger(L_ERR,"no more memory!");
        return;
    }

    strcpy(fBuffer, "Set(");
    strncat(fBuffer, cmdLine, prefixSz+1);
    memcpy((void*)fBuffer+prefixSz+4, (const void *) &szi32, 4);    // length on binary data

    if (fp) {
        #ifdef __cplusplus
        size_t dummy =
        #endif
        fread(fBuffer+prefixSz+8, sizeof(char), fLen, fp);

        fclose(fp);
    }
    strcpy(fBuffer+prefixSz+fLen+8,");");

    dMessage* dm = allocDMessage();
    dm->type     = DM_SET;
    dm->subtype  = ID_SET_MAX;
    dm->size  = prefixSz+fLen+10;
    dm->value = (void*) fBuffer;    // transfer ovnership to dispatcher

    sendToDispatcher(dm);

    return;
}*/


static void uploadPix(int subtype, char *cmdLine)
{
    DEBUG2("[EX]: uploadPix >%s<", cmdLine);
    int prefixSz = 0;                  // image,icon,_name_,_file_|image,window,_file_|cover,noname, _file_

    if (strstr(cmdLine,UPLOAD_PIX)   != NULL) {
        prefixSz = strlen(UPLOAD_PIX) + 1;
    } else if (strstr(cmdLine,UPLOAD_COVER_DATA) != NULL) {
        prefixSz = strlen(UPLOAD_COVER_DATA) + 1;
    } else if (strstr(cmdLine,UPLOAD_COVER) != NULL) {
        int nCh = strlen(UPLOAD_COVER) + 1;
        prefixSz = nCh + strstr(cmdLine+nCh,",") - (cmdLine+nCh) + 1;
    } else {
        int nCh = strlen(UPLOAD_ICON) + 1;
        prefixSz = nCh + strstr(cmdLine+nCh,",") - (cmdLine+nCh) + 1;
    }

    char* fname = cmdLine + prefixSz;
    char* p = fname;
    while (*p != '\0') {    // remove trailing \n if exists
        if (*p == '\n') {
            *p = '\0';
            break;
        }
        p++;
    }
    sprintf(tmp, "[EX]: uploadPix file >%s<", fname);
    logger(L_INF,tmp);

    char * fBuffer = (char*) calloc(prefixSz+6,1); // "Set(" + "," + "\0"
    if (fBuffer == NULL) {
        logger(L_ERR,"[EX]: no more memory!");
        return;
    }
    strcpy(fBuffer, "Set(");
    strncat(fBuffer, cmdLine, prefixSz);

    char * fName = (char*) calloc(strlen(fname)+1,1);
    if (fName == NULL) {
        logger(L_ERR,"[EX]: no more memory!");
        return;
    }
    strcpy(fName,fname);

    dMessage* dm = allocDMessage();
    dm->peer     = PEER_ANY;    // TODO - use specific peer
    dm->type     = DM_SETFILE;
    dm->subtype  = subtype;
    dm->size     = -1;
    dm->value    = (void*) fBuffer;    // transfer ovnership to dispatcher
    dm->file     = (void*) fName;
    dm->scaled   = (void*) fName;

    sendToDispatcher(dm);

    return;
}

static void uploadCoverToPeers(const char* command, char* file, boolean_t isExec)
{
    if (!file) {
        return;
    }
    int sz = strlen(file);
    if (sz > 1 && *(file+sz-1) == '\n') {
        *(file+sz-1) = '\0';
    }
    
    DEBUG2("[EX]: uploadCoverToPeers %s<>%s", command, file);
    
    SingleList* list = _peers;
    
    char * fBuffer = (char*) calloc(strlen(command)+5,1);    // 4 = "Set(" + "\0"
    if (fBuffer == NULL) {
        logger(L_ERR,"no more memory!");
        return;
    }

    strcpy(fBuffer, "Set(");
    strcat(fBuffer, command);
    if (isExec) {
        strcat(fBuffer, ",");
    }
   
    while (list) {
    
        PeerDef* peer = (PeerDef*) list->data;
        DEBUG2("[EX]: Prepare cover for peer %d", peer->id);
        
        char buf[16];
        sprintf(buf,"%d",peer->coverSz);
         
        char* dup = strdup(file);
        int ssz = strlen(dup);
        int c = ssz;
        while (*(dup+c) != '/' && c >= 0) {
            c--;
        }
        if (c != ssz) {
            c++;
        }
        
        int dot = ssz;
        while (*(dup+dot) != '.' && dot >= 0) {
           dot--;
        }
        if (dot > 0) {
            *(dup+dot) = '\0';   
        } 
        DEBUG2("[EX]: Parsed file name %s", (dup+c));
        
        string_t* scaledFile = stringNew("/tmp/");
        
        stringAppend(scaledFile,dup+c);
        stringAppend(scaledFile, "_");
        stringAppend(scaledFile, buf);
        stringAppend(scaledFile, ".png");
        free(dup);
        
        DEBUG2("[EX]: Scaled file name %s", scaledFile->str);
        
        string_t* cmd = stringNew("convert -resize ");

        stringAppend(cmd, buf);
        stringAppend(cmd, "x");
        stringAppend(cmd, buf);
        stringAppend(cmd, " -depth 8 -background transparent ");
        stringAppend(cmd, file);
        stringAppend(cmd, " ");
        stringAppend(cmd, scaledFile->str);
        stringAppend(cmd, " 2> /dev/null");
        
        DEBUG2("[EX]: Scaled command %s", cmd->str);
        
        size_t sz = 0;
        char* dummy = executeCommandPipe(cmd->str, &sz);
        if (dummy) {  // can be NULL
            free(dummy);
        }
        stringFree(cmd, BOOL_YES);
        
        DEBUG2("[EX]: uploadCoverToPeers Final command %s %s", fBuffer, scaledFile->str);

        dMessage* dm = allocDMessage();
        dm->peer     = peer->id;
        dm->type     = DM_SETFILE;
        dm->subtype  = ID_SET_COVER;
        dm->size     = -1;
        dm->value    = (void*) strdup(fBuffer);        // transfer ovnership to dispatcher
        dm->file     = (void*) strdup(file);
        dm->scaled   = (void*) strdup(scaledFile->str);
        
        stringFree(scaledFile, BOOL_YES);

        sendToDispatcher(dm);

        list = listSingleNext(list);
    }
    
    free(fBuffer);
}

static void uploadCoverCmd(const char* descr, boolean_t isExec)
{ 
    char* dup = strdup(descr);
    char* ptr = strstr(dup,",cover,");
    ptr += 7;
    while (*ptr != ',') {
        ptr++;
    }
    ptr++;
    char* f = strdup(ptr);
    *ptr = '\0';
    
    uploadCoverToPeers(dup, f, isExec);
    
    free(dup);
    free(f);
}

static void uploadWindowToPeers(const char* command, char* file)
{
    if (!file) {
        return;
    }
    int sz = strlen(file);
    if (sz > 1 && *(file+sz-1) == '\n') {
        *(file+sz-1) = '\0';
    }
    
    DEBUG2("[EX]: uploadWindowToPeers %s<>%s", command, file);
    
    SingleList* list = _peers;
    
    char * fBuffer = (char*) calloc(strlen(command)+5,1);    // 4 = "Set(" + "\0"
    if (fBuffer == NULL) {
        logger(L_ERR,"no more memory!");
        return;
    }

    strcpy(fBuffer, "Set(");
    strcat(fBuffer, command);
   
    while (list) {
    
        PeerDef* peer = (PeerDef*) list->data;
        DEBUG2("[EX]: Prepare image for peer %d", peer->id);
        
        char bufx[16];
        char bufy[16];
        sprintf(bufx,"%d",peer->xSz);
        sprintf(bufy,"%d",peer->ySz);
         
        char* dup = strdup(file);
        int ssz = strlen(dup);
        int c = ssz;
        while (*(dup+c) != '/' && c >= 0) {
            c--;
        }
        if (c != ssz) {
            c++;
        }
        
        int dot = ssz;
        while (*(dup+dot) != '.' && dot >= 0) {
           dot--;
        }
        if (dot > 0) {
            *(dup+dot) = '\0';   
        } 
        DEBUG2("[EX]: Parsed file name %s", (dup+c));
        
        string_t* scaledFile = stringNew("/tmp/");
        
        stringAppend(scaledFile,dup+c);
        stringAppend(scaledFile, "_");
        stringAppend(scaledFile, bufx);
        stringAppend(scaledFile, "x");
        stringAppend(scaledFile, bufy);
        stringAppend(scaledFile, ".png");
        free(dup);
        
        DEBUG2("[EX]: Scaled file name %s", scaledFile->str);
        
        string_t* cmd = stringNew("convert -resize ");

        stringAppend(cmd, bufx);
        stringAppend(cmd, "x");
        stringAppend(cmd, bufy);
        stringAppend(cmd, " -depth 8 -background transparent ");
        stringAppend(cmd, file);
        stringAppend(cmd, " ");
        stringAppend(cmd, scaledFile->str);
        stringAppend(cmd, " 2> /dev/null");
        
        DEBUG2("[EX]: Scaled command %s", cmd->str);
        
        size_t sz = 0;
        char* dummy = executeCommandPipe(cmd->str, &sz);
        if (dummy) {  // can be NULL
            free(dummy);
        }
        stringFree(cmd, BOOL_YES);
        
        DEBUG2("[EX]: uploadWindowToPeers Final command %d %s %s", peer->id, fBuffer, scaledFile->str);

        dMessage* dm = allocDMessage();
        dm->peer     = peer->id;
        dm->type     = DM_SETFILE;
        dm->subtype  = ID_SET_IMAGE;
        dm->size     = -1;
        dm->value    = (void*) strdup(fBuffer);        // transfer ovnership to dispatcher
        dm->file     = (void*) strdup(file);
        dm->scaled   = (void*) strdup(scaledFile->str);
        
        stringFree(scaledFile, BOOL_YES);

        sendToDispatcher(dm);

        list = listSingleNext(list);
    }
    
    free(fBuffer);
}

static void uploadWindowCmd(const char* descr)
{ 
    DEBUG2("[EX]: uploadWindowCmd %s",descr);
    char* dup = strdup(descr);
    char* ptr = strstr(dup,",window,");
    ptr += 8;
    char* f = strdup(ptr);
    *ptr = '\0';
    
    uploadWindowToPeers(dup, f);
    
    free(dup);
    free(f);
}

static int isConvertable(int what)
{
    if (what == ID_SET_LIST || what == ID_SET_ILIST || what == ID_SET_FMGR) {
        return 1;
    } else {
        return 0;
    }
}

static int exitCmd(int subtype, const char *oper, const char *exec, cmdParams* params)
{
    logger(L_INF, "[EX]: Exit command processed");
    sendAbort();
    return EXIT_OK;
}

static int includeCmd(int subtype, const char *file, const char *exec, cmdParams* params)
{
    logger(L_INF, "[EX]: Command: Include");
    if (file == NULL) {
        ERROR2("[EX]: Can not Include() file with empty name");
        return EXIT_NOK;
    }

    int ret = EXIT_OK;

    if (load_cfg(file, 0) == EXIT_NOK) {
        logger(L_ERR,"[EX]: Can not include file !");
    }

    printConf();

    return ret;
}
 
static int getCmd(int subtype, const char *descr, const char *exec, cmdParams* params)
{
    if (descr == NULL) {
        return EXIT_NOK;
    }
    INFO2("[EX]: Command: Get %d %s", subtype, descr);

    strcpy(tmptext, CMD_GET);
    strcat(tmptext, "(");
    strcat(tmptext, descr);
    strcat(tmptext, ");");

    dMessage* dm = allocDMessage();
    dm->size  = strlen(tmptext);
    dm->value = strdup(tmptext);

    if (isServerMode()) {
        dm->type    = DM_GET;
        dm->subtype = subtype;
    } else {
        free(dm->value);
        free(dm);
        return  EXIT_NOK;
    }
    
    if (subtype == ID_GET_PING && strlen(descr) > 5) {  // Get(ping,_timeout_);
        addKeepalive(descr+5);
    }

    sendToDispatcher(dm);
    return EXIT_OK;
}

static int execAndSetCmd(int subtype, const char *descr, const char *exec, cmdParams* params)
{
    INFO2("[EX]: Command: ExecAndSet %d", subtype);

    if (!isServerMode() || remoteOn != 1) {
        return EXIT_OK;
    }

    if (descr == NULL || exec == NULL) {
        logger(L_DBG, "[EX]: execAndSetCmd(): Empty command or description");
        return EXIT_NOK;
    }
    //printf("TRACE S %s->%s\n",descr,exec);
    
    size_t resLen = 0;
    char *res = executeSimpleCommand(exec, &resLen);
    
    if (res == NULL) {
        // just replace NULL to empty string
        res = malloc(1);
        *res = '\0';
    }

    int dlen = strlen(descr);

    if (subtype == ID_SET_IMAGE) {
        
        INFO2("[EX]: ExecAndSet upload icon/image/cover of size %d",(int)resLen);

        char *chunk = (char*) calloc(dlen + resLen + 2, 1);

        strcpy(chunk,descr);
        strcat(chunk,",");
        memcpy(chunk + dlen + 1, res, resLen);
        
        DEBUG2("[EX]: ExecAndSet upload icon/image/cover %s", descr);
        
        if (strstr(descr,",cover") != NULL) {
            uploadCoverCmd(chunk, BOOL_NO);
        } else if (strstr(descr,",window") != NULL) {
            uploadWindowCmd(chunk);
        } else {
            if (strstr(descr,",icon") != NULL) {
                uploadPix(subtype, chunk);
            }
            // Set(image,show|close|cursor|nocursor|remove_all) processed below
        }
        free(chunk);

    } else if (subtype == ID_SET_COVER && strstr(descr,",noname") != NULL) {
        
        logger(L_INF, "[EX]: ExecAndSet upload cover");
        uploadCoverToPeers(descr, res, BOOL_YES);
   
    } else if (subtype == ID_SET_COVER && strstr(descr,",by_name") != NULL) {
         
        string_t* command = stringNew(CMD_SET);
        stringAppend(command,"(");
        stringAppend(command,descr);
        stringAppend(command,",");
        stringAppendLen(command,res,resLen);
        stringAppend(command,");");

        dMessage* dm = allocDMessage();
        dm->type     = DM_SET;
        dm->subtype  = ID_SET_COVER;
        dm->size     = command->len;
        dm->value    = (void*) strdup(command->str);
        
        stringFree(command,BOOL_YES);

        sendToDispatcher(dm);

    } else {

        // Replace all ");" inside buffer to "@@" since Java Client will separate commands by ");"
        char *p;
        while ((p = strstr(res,");")) != NULL) {        // binary data ???
            *p = '@';
            p++;
            *p = '@';
        }

        boolean_t converted = BOOL_NO;

        #ifdef USE_ICONV
        char* decodedVal = res;
        if (isConvertable(subtype) && needConvert() == 1) {
            decodedVal = convCharset(res, resLen, CNV_TO);
            converted = BOOL_YES;
            DEBUG2("[EX]: Decoded string >%s< ", (decodedVal ? decodedVal : "NULL"));
        }
        int sz = (decodedVal ? strlen(decodedVal) : 0);

        int memSize = sz + dlen + 7;

        #else

        int memSize = resLen + dlen + 7;

        #endif

        char *chunk = (char*) calloc(1,memSize+1);
        strcpy(chunk,CMD_SET);

        strcat(chunk,"(");  // For example, Set(title
        strcat(chunk,descr);

        strcat(chunk,",");

        #ifdef USE_ICONV

        memcpy(chunk+5+dlen,decodedVal,sz);      // could be binary data here ????
        strcpy(chunk+5+dlen+sz,");");
        
        if (converted == BOOL_YES) {
            free(decodedVal);
        }

        #else

        memcpy(chunk+5+dlen,res,resLen); // could be binary data here
        strcpy(chunk+5+dlen+resLen,");");

        #endif

        //printf("CHUNK: %s\n", chunk);

        dMessage* dm = allocDMessage();
        dm->type     = DM_SET;
        dm->subtype  = subtype;
        dm->size     = strlen(chunk);
        dm->value    = (void*) strdup(chunk);

        sendToDispatcher(dm);

        free(chunk);
    }

    free(res);
    
    //logger(L_INF,"[EX]: execCmdAndGetResults finished");
    return EXIT_OK;
}

static int execAndSendCmd(int subtype, const char *descr, const char *exec, cmdParams* params)
{
    logger(L_INF, "[EX]: Command: ExecAndSend");

    if (!isServerMode() || remoteOn != 1) {
        return EXIT_OK;
    }

    if (descr == NULL || exec == NULL) {
        logger(L_DBG, "[EX]: execAndSendCmd(): Empty command or description");
        return EXIT_NOK;
    }
    //printf("TRACE S %s->%s\n",descr,exec);

    size_t resLen = 0;
    char *res = executeSimpleCommand(exec, &resLen);

    if (res == NULL) {
        logger(L_ERR, "[EX]: execAndSendCmd: executeSimpleCommand() returns NULL");
        return EXIT_NOK;
    }

    // Just send string
    int bytesFlag = (strstr(descr, SEND_BYTES) ? 1 : 0);
    sendData(ID_SEND, bytesFlag, res, resLen);

    free(res);
    return EXIT_OK;
}

static int sendCmd(int subtype, const char *descr, const char *exec, cmdParams* p)
{
    logger(L_INF, "[EX]: Command: Send");

    int ret = EXIT_OK;

    if (descr!= NULL && isServerMode() == EXIT_OK) { // WEB/CMXML will skip this message inside
        ret = processSendCmd(descr);
    }
    return ret;
}

static int setCmd(int subtype, const char *descr, const char *exec, cmdParams* p)
{
    INFO2("[EX]: Command: Set %s", (descr ? descr : "NULL"));
    if (descr != NULL && isServerMode() == EXIT_OK) {

        if (subtype == ID_SET_IMAGE) {
            // handle Set(cover|image,icon|window|cover,[...]_image_file_name_); separately.

            if (strstr(descr,",cover,") != NULL) {
                uploadCoverCmd(descr, BOOL_NO);
            } else if (strstr(descr,",window,") != NULL) {
                uploadWindowCmd(descr);
            } else {
                if (strstr(descr,",icon,") != NULL) {
                    uploadPix(subtype, (char*)descr);
                } 
                // Set(image,show|close|cursor|nocursor|remove_all) processed below
            }
            
        } else if (subtype == ID_SET_COVER) {
        
            if (strstr(descr,",noname,") != NULL) {
            
                char* ptr = strstr(descr,",noname,");
                uploadCoverToPeers("cover,noname", (ptr+8), BOOL_NO);
                
            } else if (strstr(descr,",by_name,") != NULL ||
                       strstr(descr,",clear") != NULL) {
            
                string_t* command = stringNew(CMD_SET);
                stringAppend(command,"(");
                stringAppend(command,descr);
                stringAppend(command,");");

                dMessage* dm = allocDMessage();
                dm->type     = DM_SET;
                dm->subtype  = subtype;
                dm->size     = command->len;
                dm->value    = (void*) strdup(command->str);

                stringFree(command,BOOL_YES);
                
                sendToDispatcher(dm);
            }
        
        } else if ((subtype == ID_SET_LIST && strncmp(descr,"list,dir,",9) == 0) || 
                   (subtype == ID_SET_ILIST && strncmp(descr,"iconlist,dir,",13) == 0)) {
           
           INFO2("[EX]: Command: Set list from dir %s", (descr ? descr : "NULL"));
           string_t* command = stringNew(CMD_SET);
           
           int shift;
           if (subtype == ID_SET_LIST) {
               stringAppend(command,"(list,replace,");
               shift = 9;
           } else {
               stringAppend(command,"(iconlist,replace,");
               shift = 13;
           }
           const char *p1 = descr + shift;
           const char *p2 = p1;
           while (*p2 != ',') {
               p2++;
           }
           p2++;
           stringAppendLen(command, p1, p2-p1);
           
           
           INFO2("[EX]: Command: Set list from dir2 %s", p2);
           
           string_t* ff = executeDirListCommand(subtype, p2);
           
           if (ff) {
               stringAppend(command, ff->str);
               stringFree(ff,BOOL_YES);
           }
           
           stringAppend(command,");");

           dMessage* dm = allocDMessage();
           dm->type     = DM_SET;
           dm->subtype  = subtype;
           dm->size     = command->len;
           dm->value    = (void*) strdup(command->str);

           stringFree(command,BOOL_YES);
           
           sendToDispatcher(dm);

        } else if (subtype == ID_SET_TEXT && strncmp(descr,"text,file,",10) == 0) {
           
           INFO2("[EX]: Command: Set text from file %s", (descr ? descr : "NULL"));
           string_t* command = stringNew(CMD_SET);
           
           stringAppend(command,"(text,replace,");

           const char *p1 = descr + 10;  // text,file
           const char *p2 = p1;
           while (*p2 != ',') {
               p2++;
           }
           p2++;
           stringAppendLen(command, p1, p2-p1);
           
           
           INFO2("[EX]: Command: Set text from file %s", p2);
           int len;
           char *buf = readFromFile("", p2, &len);
           if (buf) {
               stringAppendLen(command, buf, len);
               free(buf);
           }
           
           stringAppend(command,");");

           dMessage* dm = allocDMessage();
           dm->type     = DM_SET;
           dm->subtype  = subtype;
           dm->size     = command->len;
           dm->value    = (void*) strdup(command->str);

           stringFree(command,BOOL_YES);
           
           sendToDispatcher(dm);

        } else {

            //INFO2("[EX]: Command: Set ELSE %s", (descr ? descr : "NULL"));
            
            const char* data = descr;
        
            string_t* command = stringNew(CMD_SET);
            stringAppend(command,"(");

            // Change \n in input to real '\n'
            const char *p1 = data;
            const char *p2 = data;

            while (p2 != NULL) {
                p2 = strstr(p1, "\\n");

                if (p2 == NULL) {
                     stringAppend(command,p1);
                } else {
                     stringAppendLen(command,p1, p2-p1);
                     stringAppend(command,"\n");
                    p1 = p2 + 2;
                }
            }

            stringAppend(command,");");

            dMessage* dm = allocDMessage();
            dm->type     = DM_SET;
            dm->subtype  = subtype;
            dm->size     = command->len;
            dm->value    = (void*) strdup(command->str);

            sendToDispatcher(dm);
        
            stringFree(command,BOOL_YES);
        }
    }

    return EXIT_OK;
}

static int makeVarCmd(const char *descr, const char *exec)
{
    logger(L_INF, "[EX]: Command: Make(var,...)");

    int ret = EXIT_OK;

    const char *eValue = exec;
    if (exec && strstr(exec,"by_value") == exec) {
        eValue = (exec + 9);
    }

    size_t resLen = 0;
    char *res  = NULL;

    if (eValue == exec) {
        if(exec == NULL) {
            logger(L_DBG, "[EX]: Empty Make(var,...) command");
            return EXIT_NOK;
        }

        res = executeSimpleCommand(exec, &resLen);
        // if return data is NULL, it is OK, need to clean up var value
        
        if (resLen > 0 && *(res+resLen-1) == '\n') {
            resLen--;
        }
    } else {        // set "by_value"
        if (eValue) {
            resLen = strlen(eValue);
            res    = strdup(eValue);
        } else {
            resLen = 0;
        }
    }

    ret = setVar(descr+4,res,resLen);
    if (ret != EXIT_OK) {
        logger(L_DBG,"[EX]: Fails in setVar() (Make)");
    }

    free(res);

    return ret;
}

static int makeModeCmd(const char *descr, const char *exec)
{
    logger(L_INF, "[EX]: Command: Make(mode,...)");

    int ret = EXIT_OK;

    int i   = 0;
    type_key* k = findItem(EVT_EXITMODE, &i, NULL);
    if (k && i == FLAG_EXACT) {
        logger(L_DBG, "[EX]: Exec cmd on exit from mode");
        ret = handleCmdByKey(PEER_ANY,k,NULL);
        if (ret == EXIT_STOP) {
            return ret;
        }
    //} else {
    //    logger(L_DBG, "[EX]: Leave mode");
    }

    switchMode(exec);

    i = 0;
    k = findItem(EVT_ENTRMODE, &i, NULL);
    if (k && i == FLAG_EXACT) {
        logger(L_DBG, "[EX]: Exec cmd on enter to mode");
        ret = handleCmdByKey(PEER_ANY,k,NULL);
        if (ret == EXIT_STOP) {
            return ret;
        }
    } else {
        logger(L_DBG, "[EX]: Entered to mode");
    }

    return ret;
}

static int makeAlarmCmd(const char *descr, const char *exec)
{
    logger(L_INF, "[EX]: Command: Make(alarm,...)");

    char *sExec = strdup(exec);

    char* file  = strtok(sExec,",");
    char* macro = strtok(NULL,",");

    int ret = EXIT_NOK;

    if (file != NULL && macro != NULL) {
        addAlarm(file, macro);
        ret = EXIT_OK;
    }

    free(sExec);
    return ret;
}

static int makeRemoteCmd(const char *descr, const char *exec)
{
    if (exec && strcmp(exec,"on")==0) {
        logger(L_INF, "[EX]: Command: Make(remote,on)");
        remoteOn = 1;
    } else if (exec && strcmp(exec,"off")==0) {
        logger(L_INF, "[EX]: Command: Make(remote,off)");
        remoteOn = 0;
    }
    return EXIT_OK;
}

static int makeDisconnectCmd(const char *descr, const char *exec)
{
    logger(L_INF, "[EX]: Temporary disconnect command");

    dMessage* dm = allocDMessage();
    dm->type     = DM_EVENT;
    dm->subtype  = ID_EVENT_DISCONNECT;

    sendToDispatcher(dm);

    return EXIT_OK; // But what with the rest of the command line ? (now just skip)
}

static int makeStopCmd(const char *descr, const char *exec)
{
    logger(L_INF, "[EX]: Command: Make(stop)");
    return EXIT_STOP;
}

static int makeFlushCmd(const char *descr, const char *exec)
{
    logger(L_INF, "[EX]: Command: Make(flush)");
    return  flushData();
}

static int makeExitCmd(const char *descr, const char *exec)
{
    return exitCmd(ID_MAKE_EXIT,descr,exec,NULL);
}

static int makeNoneCmd(const char *descr, const char *exec)
{
    return EXIT_OK;
}


static int makeCmd(int subtype, const char *descr, const char *exec, cmdParams* params)
{
    DEBUG2("[EX]: makeCmd %d %s", subtype, descr);

    static struct {
        int id;
        int (*hook) (const char *descr, const char *exec);
    }
    _makeHooks[] = {
        {ID_MAKE_DISCONN, makeDisconnectCmd},
        {ID_MAKE_MODE,    makeModeCmd      },
        {ID_MAKE_ALARM,   makeAlarmCmd     },
        {ID_MAKE_FLUSH,   makeFlushCmd     },
        {ID_MAKE_STOP,    makeStopCmd      },
        {ID_MAKE_REMOTE,  makeRemoteCmd    },
        {ID_MAKE_VAR,     makeVarCmd       },
        {ID_MAKE_EXIT,    makeExitCmd      },
        {ID_MAKE_NONE,    makeNoneCmd      },
        {ID_MAKE_MAX,     makeNoneCmd      }
    };
    return (*_makeHooks[subtype].hook)(descr, exec);
}

static int ckpdCmd(int subtype, const char *descr, const char *exec, cmdParams* params)
{
    logger(L_INF, "[EX]: Command: SendCKPD");

    if (descr == NULL) {
        return EXIT_NOK;
    }

    if (isAtModeDuplex()) {
        dMessage* dm = allocDMessage();
        dm->type     = DM_CKPD;
        dm->subtype  = ID_SET_MAX;
        dm->size     = strlen(descr);
        dm->value    = (void*) strdup(descr);

        sendToDispatcher(dm);
    }
    return  EXIT_OK;
}

int macroCmd(int subtype, const char *descr, const char *exec, cmdParams* params)
{
    logger(L_INF, "[EX]: Command: Macro");

    if (descr == NULL) {
        return EXIT_NOK;
    }

    int runIt = 1;
    int ret = EXIT_OK;

    if (exec != NULL) {

        size_t resLen = 0;

        const char* eValue = exec;
        if (exec && strstr(exec,"by_value") == exec) {
            eValue = (exec + 9);
        }

        char* res  = NULL;

        if (eValue == exec) {
            if(exec == NULL) {
                runIt = 0;
            } else {

                res = executeSimpleCommand(eValue, &resLen);
        
                if (res == NULL) {
                    logger(L_ERR, "[EX]: macroCmd: empty condition value, skip execution");
                    return EXIT_NOK;
                }
                if (*(res+resLen-1) == '\n') {
                    resLen--;
                }
            }
        } else {    // set "by_value"
            if (eValue) {
                resLen = strlen(eValue);
                res    = strdup(eValue);
            } else {
                resLen = 0;
            }
        }


        if (res == NULL) {
            runIt = 0;
        } else {
            if (strcmp(res,"0") != 0 && strcmp(res,"0\n") != 0) {    // test return code $?
                runIt = 0;
            }
        }
        free(res);
    }

    if (runIt == 0) {
        logger(L_DBG,"[EX]: macroCmd condition not met");
        return EXIT_OK;
    }

    int found;
    type_key *k = findItem(descr, &found, NULL);

    if (k && found == FLAG_EXACT) {
        ret = handleCmdByKey(PEER_ANY, k, params);
        DEBUG2("[EX]: Return code from macroCmd -> handleCmdByKey() is %d",ret);
    }

    return ret;
}

int macroCmdCallback(const char *descr, const char *exec, cmdParams* params)
{
    if (descr == NULL) {
        return EXIT_NOK;
    }

    string_t* d = substParams(descr, params);
    string_t* e = substParams(exec,  params);
    
    INFO2("[EX]: macroCmdCallback after subst >%s< -> >%s<", (d ? d->str : "NULL"), (e ? e->str : "NULL"));

    int ret = macroCmd(-1, (d ? d->str : NULL), (e ? e->str : NULL), params);

    if (d) {
        stringFree(d, BOOL_YES);
    }
    if (e) {
        stringFree(e, BOOL_YES);
    }

    return ret;
}

static int processOneCommand(cmdItem* ci, cmdParams* p)
{
    int ret = EXIT_OK;

    static struct {
        int id;
        int (*hook) (int subtype, const char* descr, const char* exec, cmdParams* p);
    }
    _cmdHooks[] = {
        {ID_EXIT,     exitCmd        },
        {ID_EXEC,     execCmdNoPipe  },
        {ID_SENDCKPD, ckpdCmd        },
        {ID_SET,      setCmd         },
        {ID_EXECSET,  execAndSetCmd  },
        {ID_TIMER,    timerCommand   },
        {ID_SEND,     sendCmd        },
        {ID_EXECSEND, execAndSendCmd },
        {ID_MACRO,    macroCmd       },
        {ID_LOAD,     loadCmds       },
        {ID_INCLUDE,  includeCmd     },
        {ID_GET,      getCmd         },
        {ID_MAKE,     makeCmd        },
        {ID_EMU,      emulateCommands},
        {ID_DBUS,     dbusCommand    },
        {ID_CMD_MAX,  NULL           }
    };

    INFO2("[EX]: processOneCommand >%s< >%s< >%d<",
          ci->descr == NULL ? "no description" : ci->descr,
          ci->exec  == NULL ? "no exec"        : ci->exec, remoteOn);

    if (ci->type == ID_MAKE && 
        ci->subtype == ID_MAKE_REMOTE &&
        ci->exec && strcmp(ci->exec,"on") == 0) {

        logger(L_INF, "[EX]: Switch remote on");
        remoteOn = 1;
        return EXIT_OK;
    }

    if (remoteOn != 1) {
        logger(L_INF, "[EX]: Remote is off. Skip");
        return EXIT_OK;
    }

    if (ci->type < 0 || ci->type >= ID_CMD_MAX) {
        logger(L_INF, "[EX]: Unknown command");
        return EXIT_NOK;
    }

    string_t* sDescr = substParams(ci->descr, p);
    string_t* sExec  = substParams(ci->exec,  p);

    INFO2("[EX]: processOneCommand after subst >%s< -> >%s<",
          (sDescr ? sDescr->str : "NULL"), (sExec ? sExec->str : "NULL"));

    const char* descr = (sDescr ? sDescr->str : NULL);
    const char* exec  = (sExec  ? sExec->str  : NULL);

    if (_cmdHooks[ci->type].hook) {
        ret = (*_cmdHooks[ci->type].hook)(ci->subtype, descr, exec, p);
    }

    if (sDescr) {
        stringFree(sDescr, BOOL_YES);
    }
    if (sExec) {
        stringFree(sExec,  BOOL_YES);
    }

    return ret;
}

static int processCommands(SingleList* commands, cmdParams* p)
{
    SingleList* list = commands;
    while (list) {

        cmdItem* item = (cmdItem *) list->data;

        if (item != NULL) {
            /*if (item->descr == NULL) {
                sprintf(tmp, "Process next command NULL parameters");
            } else {
                sprintf(tmp, "Process next command %s",item->descr);
            }
            logger(L_DBG, tmp);
            */

            int ret = processOneCommand(item, p);

            if (ret != EXIT_OK) {
                if (ret == EXIT_STOP) {
                    logger(L_DBG, "[EX]: Stop execution on current command sequence");
                    return EXIT_STOP;     // just stop execution of command sequence
                }
            }
        }

        list = listSingleNext(list);
    }

    return EXIT_OK;
}

//
// Top-level command processing function
//
int handleCmdByKey(int peer, type_key* k, cmdParams* p)
{
    return handleCmdByKeyEx(peer, k, p, 1);
}

int handleCmdByKeyEx(int peer, type_key* k, cmdParams* p, int sendToMain)
{
    if (k == NULL) {
        logger(L_DBG, "[EX]: handleCmdByKey() NULL input");
        return EXIT_NOK;
    }
    int ret =  EXIT_OK;

    sprintf(tmp, "[EX]: handleCmdByKey() >%s<",k->key);
    logger(L_DBG, tmp);

    SingleList* commands = getCommand(k);
    if (commands == NULL) {
        logger(L_DBG, "[EX]: No appropriate command was found by getCommand()");
    } else {
        //sprintf(tmp, "[EX]: ready to execute command");
        //logger(L_DBG, tmp);

        ret = processCommands(commands, p);

        // In AT mode: User had pressed some key ...
        // Send ToMainMenu sequence of CKPD's to show main menu again
        // Do we send it even if remote is off ?
        if (remoteOn == 1 && peer != PEER_ANY && needAtMainMenuReturn(peer) && sendToMain == 1) {
            sendToMainMenu(peer);
        }
    }

    return ret;
}

