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

#include "parse.h"
#include "var.h"
#include "alarm.h"
#include "utils.h"
#include "mode.h"

#define EMPTY_STR   	"EMPTY"

extern char tmp[MAXMAXLEN];

extern CONF       conf;

int initDone  = 0;

boolean_t doLog       = BOOL_NO;
boolean_t doDebug     = BOOL_NO;
boolean_t isIViewer   = BOOL_NO;
boolean_t isBemused   = BOOL_NO;
int       waitTimeout = -1;

static char * _btAddress = NULL;

/* ----------------- Interface functions ------------------------- */

boolean_t boolValue(const char* value)
{
    if (!value) return BOOL_NO;
    return (!value || strncmp(value,"false",5) == 0 || strncmp(value,"no",5) == 0 ? BOOL_NO : BOOL_YES);
}

void setLog(const char* value) 
{
    doLog = boolValue(value);
    
    doDebug = 0;
    if (value && strncmp(value,"debug",5) == 0) {
        doDebug = 1;
    }
}

int getLog()
{
    return doLog;
}

void setBemused(const char* value) 
{
    isBemused = boolValue(value);
}

int getBemused()
{
    return isBemused;
}

void setIViewer(const char* value) 
{
    isIViewer = boolValue(value);
}

int getIViewer()
{
    return isIViewer;
}

int getDebug()
{
    return doDebug;
}

char* getDevice()
{
    return dupVarValue("Device");
}

int getBaudrate()
{
    char* value = dupVarValue("Baudrate");
    if (value != NULL) {

        int rate = atoi(value);
	free(value);

        switch (rate) {
        case 300:
            return B300;
        case 1200:
            return B1200;
        case 2400:
            return B2400;
        case 9600:
            return B9600;
        case 19200:
            return B19200;
        case 38400:
            return 38400;
#ifdef B57600
        case 57600:
            return B57600;
#endif
#ifdef B115200
        case 115200:
            return B115200;
#endif
#ifdef B230400
        case 230400:
            return B230400;
#endif
        default:
            WARNING2("bad baudrate %d, defaulting to 9600", rate);
            return B9600;
        }
    }
    return B19200;
}

int getRetrySecs()
{
    char* value = dupVarValue("RetrySeconds");
    if (value != NULL) {
        int v = atoi(value);
        free(value);
        return v;
    }
    return 10;
}

int getWaitTime()
{
    return waitTimeout;
}

void setWaitTime(const char* value) 
{
    waitTimeout = -1;
    
    if (value != NULL) {
        waitTimeout = atoi(value);
	    if (waitTimeout < 0) {
	        waitTimeout = -1; 
	    }
    }
}

int autoConnect()
{
    int sz = 0;
    const char* v = getVarValue(VAR_AUTOCONN, &sz);
    if (v != NULL && sz > 3 && strncmp(v,"true",4) == 0) {
        return 1;
    }
    return 0;
}

int getAutoRepeat()
{
    int sz = 0;
    const char* v = getVarValue(VAR_AUTOREPEAT, &sz);
    if (v != NULL && sz > 3 && strncmp(v,"true",4) == 0) {
        return 1;
    }
    return 0;
}

static type_key* repeatCMD = NULL;

void setRepeatNow(type_key* repeat)
{
    repeatCMD = repeat;
}

type_key* repeatNow()
{
    return repeatCMD;
}

int getUseScreen()
{
    int sz = 0;
    const char* v = getVarValue("TwoWayComm", &sz);
    if (v != NULL && sz > 3 && strncmp(v,"true",4) == 0) {
        return 1;
    }
    return 0;
}

char* getCharset()
{
    return dupVarValue(VAR_CHARSET);
}

char* getToMainMenu()
{
    return dupVarValue("ToMainMenu");
}

char* getServiceName()
{
    return dupVarValue("ServiceName");
}

char* getAT_CMER(int what)
{
    if (what == CMER_ON) {
        char *cmer = dupVarValue("CmerOn");

        if  (cmer == NULL) {
            if (conf.model == MODEL_MOTOROLA) {
                return strdup(DEF_AT_CMER_ON_MOTOROLA);
            } else if (conf.model == MODEL_SE) {
                return strdup(DEF_AT_CMER_ON_SE);
            } else if (conf.model == MODEL_SAGEM) {
                return strdup(DEF_AT_CMER_ON_SAGEM);
            } else if (conf.model == MODEL_SIEMENS) {
                return strdup(DEF_AT_CMER_ON_SIEMENS);
            } else {
                return strdup(DEF_AT_CMER_ON_DEFAULT);
            }
        } else {
            return cmer;
        }
    }

    if (what == CMER_OFF) {
        char *cmer = dupVarValue("CmerOff");

        if  (cmer == NULL) {
            if (conf.model == MODEL_MOTOROLA) {
                return strdup(DEF_AT_CMER_OFF_MOTOROLA);
            } else if (conf.model == MODEL_SE) {
                return strdup(DEF_AT_CMER_OFF_SE);
            } else if (conf.model == MODEL_SAGEM) {
                return strdup(DEF_AT_CMER_OFF_SAGEM);
            } else if (conf.model == MODEL_SIEMENS) {
                return strdup(DEF_AT_CMER_OFF_SIEMENS);
            } else {
                return strdup(DEF_AT_CMER_OFF_DEFAULT);
            }
        } else {
            return cmer;
        }
    }
    return NULL;
}

int getFrontEnd()
{
    return conf.frontEnd;
}

void setModel (char *answer)
{
    if (answer == NULL) {
        conf.model = MODEL_DEFAULT;
    } else if (strstr(answer,STR_MOTOROLA)) {
        conf.model = MODEL_MOTOROLA;
    } else if (strstr(answer,STR_SE)) {
        conf.model = MODEL_SE;
    } else if (strstr(answer,STR_SAGEM)) {
        conf.model = MODEL_SAGEM;
    } else if (strstr(answer,STR_SIEMENS)) {
        conf.model = MODEL_SIEMENS;
    } else {
        conf.model = MODEL_DEFAULT;
    }
}

int getModel (void)
{
    return conf.model;
}

void  setBtAddress (char* a)
{
    _btAddress = a;
}

char * getBtAddress()
{
    return _btAddress;
}

void freeBtAddress()
{
    if (_btAddress) {
        free(_btAddress);
    }
    _btAddress = NULL;
}


/////////////////////////////////////////////////////////////////////////////////////////////////

type_key* findExact(mode *mode, const char *key)
{
    //DEBUG2("findExact() %s",key);
    if (mode == NULL || key == NULL) {
        logger(L_DBG, "findExact() input is empty ?");
        return NULL;
    }
    type_key* It = mode->keys;

    // Search exact command
    while (It != NULL && It->key != NULL && strcmp(It->key,key) != 0) {
        //sprintf(tmp,"findExact search >%s< compare to >%s<", It->key, key);
        //logger(L_DBG,tmp);

        It = (type_key*) It->next;
        //logger(L_DBG, "findExact() next loop");
    }
    //logger(L_DBG, "findExact() exiting");
    return It;
}

static type_key* findStartingWith(mode *mode, const char *key)
{
    if (mode == NULL) {
        return NULL;
    }
    type_key* It = mode->keys;

    while (It && It->key != NULL) {
        char *start = strstr(It->key,key);
	
	     //DEBUG2("[EX]: findStartingWith() %s %s %s",It->key,key,(start?"FOUND":"NOTFOUND"));

        if (start && start == It->key) {    // We got a part of multi-key or parametrized command

            int lk = strlen(key);
            int l2 = strlen(It->key);
	        int ok = 1;
	    
            if (*(key+lk-1) != '$' && // not parametrized
	            l2 > lk) {            // part of multi-key should be followed by space
	    
	        char next = *(It->key+lk);
		    if (next != ' ') {
		        ok = 0;
		    }
	    }
	    
	    if (ok == 1)  {
        	logger(L_DBG,"[EX]: Found part of multi-key or parametrized command");
        	return It;
	    }
        }
        It = (type_key*) It->next;
    }

    return It;
}

static type_key* findItemInMode(mode *mode, const char *key, int *flag, cmdParams *params)
{
    if (mode == NULL) {
        logger(L_DBG,"[EX]: findItemInMode: mode is null");
        return NULL;
    }
    int canLog = (strcmp(mode->name->str,"_INTERNAL_") != 0);

    if(canLog) {
        sprintf(tmp,"[EX]: findItemInMode >%s,%s<", mode->name->str, key);
        logger(L_DBG,tmp);
    }

    // Prepare to search as parametrized command, control presence of both ( and )
    const char *start  = index(key,'(');
    const char *finish = rindex(key,')');

    if ((start != NULL && finish == NULL) ||  // Command was read partially?
        (start == NULL && finish != NULL)) {  // Command was incorectrly formed ?
        if(canLog) {
            logger(L_DBG,"[EX]: findItemInMode: Incorrectly formed parametrized command. One brace is absent");
        }
        return NULL;
    }

    // Clean-up
    if (params != NULL) {
        params->index[0] = '\0';
        params->value[0] = '\0';
    }

    type_key* It = findExact(mode,key);	// Search exact command
    if (It) {
        if(canLog) {
            logger(L_DBG,"[EX]: findItemInMode: found exact command");
        }
	    *flag = FLAG_EXACT;
        return It;
    }

    It = findStartingWith(mode,key);	// Search as part of multikey sequence
    if (It) {
        if(canLog) {
            logger(L_DBG,"[EX]: findItemInMode: found part of multi-key command");
        }
        *flag = FLAG_MULTIKEY;
        return It;
    }

    if (params == NULL) {
        //if(canLog) {
        //    logger(L_DBG,"[EX]: findItemInMode: No parameters suspected. Item does not found.");
        //}
        return NULL;
    }

    // Search as parametrized command

    if (start != NULL && finish != NULL && start != key) {        // Do not match "(...)=..."
        It = mode->keys;

        char tag  [MAXARGLEN];
        memset(tag,  0,MAXARGLEN);
        char index[6];
        memset(index,0,6);
        char value[MAXARGLEN];
        memset(value,0,MAXARGLEN);

        strncpy(tag, key, start-key);
        *(tag+(start-key)) = '\0';

        const char *comma = strstr(start+1,",");
        if (comma == NULL || comma > finish) { // Parametrized command from Java Client should be in a form like List(1,String1)
            if(canLog) {
                logger(L_DBG,"[EX]: findItemInMode: Incorrectly formed parametrized command");
            }
            return NULL;
        }

        if (comma-start > 6) { // 65535 = max value in Comman Fusion iViewer
            if(canLog) {
                logger(L_ERR,"[EX]: findItemInMode: Received incorrect index!");
            }
            return NULL;
        }
        strncpy(index,start+1,comma-start-1);
        *(index+(comma-start-1)) = '\0';

        strncpy(value,comma+1,finish-comma-1);
        *(value+(finish-comma-1)) = '\0';

        if(canLog) {
            sprintf(tmp,"[EX]: Parametrized command parsed as >%s< >%s< >%s< ", tag,index,value);
            logger(L_DBG,tmp);
        }
	
	    char * decodedVal = value;
	    int  needFree = 0;

	    #ifdef USE_ICONV
	    if (needConvert() == 1) {
	        decodedVal = convCharsetSimple(value, CNV_TO);
	        needFree = 1;
	        DEBUG2("[EX]: Decoded string >%s< ", (decodedVal ? decodedVal : "NULL"));
	    }
	    #endif

        // Try to search explicitly specified parametrized command (for ex List(1) or List(commandX))
        // By index
        strcat(tag,"(");
        strcat(tag,index);
        strcat(tag,")");
        It = findExact(mode,tag);

        if (It) {
            if(canLog) {
                logger(L_DBG,"[EX]: Found exact (explicitly specified by index) parametrized command");
            }
            *flag = FLAG_EXACT;

	        #ifdef USE_ICONV
	        if (needFree) {
	            free(decodedVal);
	        }
	        #endif
	    
            return It;
        }

        // Then by value
        *(tag+(start-key+1)) = '\0';
        strcat(tag,(decodedVal ? decodedVal : ""));
        strcat(tag,")");
        It = findExact(mode,tag);

        if (It) {
            if(canLog) {
                logger(L_DBG,"[EX]: Found exact (specified by value) parametrized command");
            }
            *flag = FLAG_EXACT;
	    
	        #ifdef USE_ICONV
	        if (needFree) {
	            free(decodedVal);
	        }
	        #endif
	    
            return It;
        }

        // Finally... Search command like ListItem($$)
        *(tag+(start-key)) = '\0';

        char* keyAndBrace = (char*) calloc(1,strlen(tag)+3);
        strcpy(keyAndBrace,tag);
        strcat(keyAndBrace,"($");

        if(canLog) {
            DEBUG2("[EX]: Search findStartingWith() >%s< ", keyAndBrace);
        }
        It = findStartingWith(mode,keyAndBrace);
        free(keyAndBrace);
        if (It) {
            if(canLog) {
                DEBUG2("EX]: Found parametrized command >%s,%s< ", index,decodedVal);
            }
            strcpy(params->index, index);
            strcpy(params->value, decodedVal);

            *flag = FLAG_PARAMETR;

	        #ifdef USE_ICONV
	        if (needFree) {
	            free(decodedVal);
	        }
	        #endif
	    
            return It;
        }
	
	    #ifdef USE_ICONV
	    if (needFree) {
	        free(decodedVal);
	    }
	    #endif
    }
    //if(canLog) {
    //    logger(L_DBG,"[EX]: findItemInMode: not found");
    //}
    return NULL;
}

type_key* findItemInModeAndParents(mode* curMode, const char *key, int *flag, cmdParams *params)
{
    DEBUG2("[EX]: findItemInModeAndParents() search in mode %s (parents %s)", curMode->name->str, (curMode->parent ? curMode->parent->str : "no parents"));
    type_key* tk = findItemInMode(curMode, key, flag, params);
    
    if (tk == NULL) {
    	
    	if (curMode != getDefaultMode() && curMode->parent) {
    	
	
    	    string_t* parent = stringNew(curMode->parent->str);
    	    char* ptrptr = NULL;
    	    char* pmode  = strtok_r(parent->str,",", &ptrptr); 
    	    
    	    while (pmode) {
    	    
    		    // firstly strip spaces from start and tail

    		    while (*pmode == ' ' || *pmode == '\t') {
    		        pmode++;
    		        if (*pmode == '\0') {
    			        break;
    		        }
    		    }

    		    if (*pmode != '\0') {

    		        char *p2 = pmode + strlen(pmode) - 1;

    		        while (*p2 == ' ' || *p2 == '\t') {
    			        *p2 = '\0';
    			        p2--;
    			        if (pmode == p2) {
    			            break;
    			        }
    		        }

    		        if (*pmode != '\0') {
    			        mode* tryMode = findMode(pmode);
    			        if (tryMode) {
    			            tk = findItemInModeAndParents(tryMode, key, flag, params);
    			            if (tk) {
    				            break;
    			            }
    			        } else {
                            ERROR2("[EX]: findItemInModeAndParents() parent mode %s not found",pmode);
                        }
    		        }
    		    }
    		    pmode  = strtok_r(NULL,",",&ptrptr);
    	    }
    	    stringFree(parent,BOOL_YES);
    	}
    }
    
    return tk;
}

type_key* findItem(const char *keyIn, int *flag, cmdParams *params)
{
    //logger(L_DBG,"[EX]: findItem()");
    type_key *tk = NULL;

    if (keyIn == NULL) {
        return NULL;
    }

    char key[MAXARGLEN];
    memset(key,0,MAXARGLEN);

    if (*keyIn == '\0') {
        logger(L_INF,"[EX]: Got empty key");
        strcpy(key, EMPTY_STR);
    } else {
        strncpy(key, keyIn, MAXARGLEN - 1);
    }

    mode* curMode = getCurrentMode();
    
    if (curMode) {

        tk = findItemInModeAndParents(curMode, key, flag, params);

        mode* defMode = getDefaultMode();
        if (tk == NULL && curMode != defMode) {
            logger(L_DBG,"[EX]: findItem() search in default");
            tk = findItemInMode(defMode, key, flag, params);
        }
    }

    //  Search in "internal" commands
    if (tk == NULL) {
        logger(L_DBG,"[EX]: findItem() internal mode");
        tk = findItemInMode(getInternalMode(), key, flag, params);
        if (tk != NULL && *flag == FLAG_MULTIKEY) {
            logger(L_DBG,"[EX]: findItem() multikey in internal command ?");
        }
    }

    /*if (tk && tk->cmd) {
    	printf("TRACE %s->%s:%s \n",keyIn,tk->cmd->descr,(tk->cmd->exec ? tk->cmd->exec : "NULL"));
    } else {
    	printf("TRACE %s->NOT FOUND?\n",keyIn);
    }*/
    return tk;
}

SingleList *getCommand(type_key* item)
{
    if (!item) {
        //logger(L_DBG,"[EX]: getCommand(): Empty type_key was got");
        return NULL;
    }
    if (item->key == NULL) {
        //logger(L_DBG,"[EX]: Empty key was got");
        return NULL;
    }

    //sprintf(tmp,"[EX]: getCommand for >%s<", item->key);
    //logger(L_DBG,tmp);

    return item->commands;
}

//
// Print cfg stuff
//
static void printKey(const char* key, SingleList* commands)
{
    sprintf(tmp, "%s=\\\n\t", key);

    SingleList* list = commands;
    while (list) {

        cmdItem * item = (cmdItem *) list->data;
        if (!item) continue;

        strcat(tmp, id2Cmd(item->type));

        if (item->descr || item->exec) {
            strcat(tmp, "(");
        }

        int pComma = 0;
        if (item->descr) {
            //printf("descr  %s\n",item->descr);
            strcat(tmp, item->descr);
            pComma = 1;
        }
        strcat(tmp, "<|>");
        if (item->exec) {
            if (pComma) {
                strcat(tmp, ",");
            }
            strcat(tmp, item->exec);
        }

        if (item->descr || item->exec) {
            strcat(tmp, ")");
        }
        strcat(tmp, ";");

	    list = listSingleNext(list);

        if (item  && list) {
            strcat(tmp, "\\");
        }
        logger(L_CFG, tmp);
        sprintf(tmp, "\t");

    }
}

void printKeys(type_key* key)
{
    while (key && key->key != NULL) {
    
        SingleList* commands = getCommand(key);
	
        if (commands) {
	    printKey(key->key, commands);
        } else {
            sprintf(tmp, "\t%s = no command", key->key);
            logger(L_CFG, tmp);
        }

        key = (type_key*) key->next;
    }
}

void  setInitDone(void)
{
    initDone = 1;
}

void printConf()
{
    //printf("printConf ENTER\n");fflush(stdout);
    if (initDone == 0) return; // to avoid dumping several times on initialization
    
    printModes();
    printVars ();

    return ;
}

//
// Clean-up stuff
//

static void destroyCommandItem(void* ptr) 
{
    cmdItem* item = (cmdItem *) ptr;
    
    if (item->descr != NULL) {
    	free(item->descr);
    	item->descr = NULL;
    }
    if (item->exec != NULL) {
    	free(item->exec);
    	item->exec = NULL;
    }
    free(item);
}

void freeCmds(SingleList* commands)
{
    if (commands) {
        listSingleFullFree(commands, destroyCommandItem);
    }
}

void freeCfg(void)
{
    logger(L_DBG, "freeCfg()");
    freeModes(NULL); // delete all modes stuff
}
