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

#include "str.h"
#include "utils.h"
#include "conf.h"
#include "list.h"
#include "state.h"
#include "mutex.h"
#include "gen_xml.h"
#include "var.h"

extern char tmp[MAXMAXLEN];

boolean_t _stateInited = BOOL_NO;
static ClientState _state;
static SingleList* _userMenu = NULL;
static int         _curForm  = CF;

int _iconPadding   = 0;
int _iconSize      = 64;

void initState()
{
    INFO2("[DS]: initState");
    
    mutexNew(M_STATE);
    

    _state.cf.layout7x1   = BOOL_NO;
    _state.cf.upBtn       = NULL;
    _state.cf.dnBtn       = NULL;
    _state.cf.joystick    = BOOL_YES;
    _state.cf.keypad      = BOOL_YES;
    
    _state.cf.caption     = stringNew("anyRemote");
    _state.cf.title       = stringNew(" ");
    _state.cf.status      = stringNew(" ");
    int i = 0;
    for (;i<ICON_NUM;i++) {
        _state.cf.icons[i] = stringNew("default");
        _state.cf.hints[i] = stringNew("");
    }
    _state.cf.cover          = NULL;
    _state.cf.namedCover  = NULL;
    _state.cf.volume      = NULL;
    _state.cf.useVolume   = BOOL_NO;
    _state.cf.visual.font = stringNew("small");
    _state.cf.visual.fg   = stringNew("FFFFFF"); 
    _state.cf.visual.bg   = stringNew("000000"); 

    _state.lf.caption     = stringNew("");
    _state.lf.items       = NULL;
    _state.lf.selIdx      = 1;
    _state.lf.visual.font = stringNew("medium");
    _state.lf.visual.fg   = stringNew("000000"); 
    _state.lf.visual.bg   = stringNew("FFFFFF"); 

    _state.tf.caption     = stringNew("");
    _state.tf.text        = stringNew("");
    _state.tf.visual.font = stringNew("small");
    _state.tf.visual.fg   = stringNew("000000"); 
    _state.tf.visual.bg   = stringNew("FFFFFF"); 

    _state.wf.window      = NULL;

    _state.ef.caption     = stringNew("");
    _state.ef.text        = stringNew("");
    _state.ef.label       = stringNew("");
    
    _stateInited = BOOL_YES;
}

static void listItemFree(pointer_t data)
{
    ListItem* item = (ListItem*) data;
    if (item->icon) {
        stringFree(item->icon,   (item->icon->str   != NULL));
    }
    if (item->string) {
        stringFree(item->string, (item->string->str != NULL));
    }
    free(item);
}

void freeState()
{
    if (_stateInited == BOOL_NO) {
        return;
    }
    
    INFO2("[DS]: freeState");
    
    mutexLock(M_STATE);
    
    if (_state.cf.caption) stringFree(_state.cf.caption, BOOL_YES);
    if (_state.cf.title)   stringFree(_state.cf.title,   BOOL_YES);
    if (_state.cf.status)  stringFree(_state.cf.status,  BOOL_YES);
    
    if (_state.cf.upBtn)   stringFree(_state.cf.upBtn,   BOOL_YES);
    if (_state.cf.dnBtn)   stringFree(_state.cf.dnBtn,   BOOL_YES);
    
    int i = 0;
    for (;i<ICON_NUM;i++) {
        if (_state.cf.icons[i]) stringFree(_state.cf.icons[i], BOOL_YES);
        if (_state.cf.hints[i]) stringFree(_state.cf.hints[i], BOOL_YES);
    }
    
    freeCfCover();
    
    if (_state.cf.volume)      stringFree(_state.cf.volume,      BOOL_YES);
    if (_state.cf.visual.font) stringFree(_state.cf.visual.font, BOOL_YES);
    if (_state.cf.visual.fg)   stringFree(_state.cf.visual.fg,   BOOL_YES);
    if (_state.cf.visual.bg)   stringFree(_state.cf.visual.bg,   BOOL_YES);

    if (_state.lf.caption)     stringFree(_state.lf.caption,     BOOL_YES);
   
    freeLfList();

    if (_state.lf.visual.font) stringFree(_state.lf.visual.font, BOOL_YES);
    if (_state.lf.visual.fg)   stringFree(_state.lf.visual.fg,   BOOL_YES);
    if (_state.lf.visual.bg)   stringFree(_state.lf.visual.bg,   BOOL_YES);

    if (_state.tf.caption)     stringFree(_state.tf.caption,     BOOL_YES);
    if (_state.tf.text)        stringFree(_state.tf.text,        BOOL_YES);
    if (_state.tf.visual.font) stringFree(_state.tf.visual.font, BOOL_YES);
    if (_state.tf.visual.fg)   stringFree(_state.tf.visual.fg,   BOOL_YES);
    if (_state.tf.visual.bg)   stringFree(_state.tf.visual.bg,   BOOL_YES);

    if (_state.wf.window)      stringFree(_state.wf.window,      BOOL_YES);

    if (_state.ef.caption)     stringFree(_state.ef.caption,     BOOL_YES);
    if (_state.ef.text)        stringFree(_state.ef.text,        BOOL_YES);
    if (_state.ef.label)       stringFree(_state.ef.label,       BOOL_YES);
    
    freeMenu();

    mutexUnlock(M_STATE);
    
    mutexRemove(M_STATE);
    
    _stateInited = BOOL_NO;
}

// rely on previous strtok()
void setFgBg(int form, boolean_t set_fg)
{
    char buf[16];

    char *token = strtok(NULL,",");
    if (!token) {
        return;
    }
    int r = atoi(token);

    token = strtok(NULL,",");
    if (!token) {
        return;
    }
    int g = atoi(token);

    token = strtok(NULL,",");
    if (!token) {
        return;
    }
    int b = atoi(token);

    sprintf(buf,"%0*x%0*x%0*x",2,r,2,g,2,b);

    INFO2("[DS]: setFgBg fg=%d form=%d color=%s",set_fg,form,buf);

    string_t** ptr = NULL;
    
    if (form == CF) {
        ptr = (set_fg ? &_state.cf.visual.fg : &_state.cf.visual.bg);
    } else if (form == TX) {
        ptr = (set_fg ? &_state.tf.visual.fg : &_state.tf.visual.bg);
    } else if (form == LI) {
        ptr = (set_fg ? &_state.lf.visual.fg : &_state.lf.visual.bg);
    }
    
    if (ptr) {
        if ((*ptr)) {
            (*ptr) = stringNew(buf);
        } else {
            stringAssign((*ptr),buf);
        }
    }
}

void setFont(int form)
{
    char *s = strtok(NULL,",");
    INFO2("[DS]: setFont %d %s",form,(s?s:"NULL"));

    if (!s) {
        return;
    }

    while (isspace(*s)) {
        ++s;
    }
    
    string_t** ptr = NULL;
    
    if (form == CF) { 
        ptr = &_state.cf.visual.font;
    } else if (form == TX) {
        ptr = &_state.tf.visual.font;
    } else if (form == LI) {
        ptr = &_state.lf.visual.font;
    }
    
    if (ptr) {
        if ((*ptr)) {
            (*ptr) = stringNew(s);
        } else {
            stringAssign((*ptr),s);
        }
    }
}

void setCurForm(int f)
{
    _curForm = f;
}

int curForm()
{
    return _curForm;
}

////////////////////////////////////////////////////////////////////////////////
//
//  Menu
//
////////////////////////////////////////////////////////////////////////////////

static void addDefMenu()
{
    INFO2("[DS]: addDefMenu %d", _curForm);
    
    switch (_curForm) {

        case TX:
        case LI:
            _userMenu = listSingleAppend(_userMenu, stringNew("Back"));
            break;

        case EF:
            _userMenu = listSingleAppend(_userMenu, stringNew("Back"));
            _userMenu = listSingleAppend(_userMenu, stringNew("Ok"));
            break;

        case CF:
            //_userMenu = listSingleAppend(_userMenu, stringNew("Disconnect"));
            //_userMenu = listSingleAppend(_userMenu, stringNew("Exit"));
            break;

        case WM:
            break;
    }
}

static void stringItemFree(pointer_t data)
{
    stringFree(data,(((string_t*) data)->str != NULL));
}

void freeMenu()
{
    INFO2("[DS]: freeMenu %d", _curForm);
    listSingleFullFree(_userMenu, stringItemFree);
    _userMenu = NULL;
}

void setupDefMenu()
{
    INFO2("[DS]: setupDefMenu %d", _curForm);
    mutexLock(M_STATE);
    
    freeMenu();
    addDefMenu();
    
    mutexUnlock(M_STATE);
}

SingleList* userMenu()
{
    return _userMenu;
}

int menuSize()
{
    return (_userMenu ? listSingleLength(_userMenu) : 0);
}

SingleList* menuNth(int n)
{
    return (_userMenu ? listSingleNth(_userMenu, n) : NULL); 
}

void setMenu()
{
    char *token = strtok(NULL,",");
    if (!token) {
        return;
    }
    while (isspace(*token)) {
        ++token;
    }
    
    if (strncmp(token,"add",3) == 0 || strncmp(token,"replace",7) == 0) {

        logger(L_INF, "[DS]: setMenu add/replace");

        if (strcmp(token,"replace") == 0) {
            setupDefMenu();
        }

        char *token3 = strtok(NULL,",");
        while (token3) {

            while (isspace(*token3)) {
                ++token3;
            }

            INFO2("[DS]: Set(menu, ...) %s", token3);
            
            mutexLock(M_STATE);
            
            string_t* dup = stringNew(token3);
            _userMenu = listSingleAppend(_userMenu, dup);
            token3 = strtok(NULL,",");
            
            mutexUnlock(M_STATE);
        }
    } else if (strncmp(token,"clear",5) == 0) {
        mutexLock(M_STATE);
        freeMenu();
        mutexUnlock(M_STATE);
    } else {
        ERROR2("[DS]: Can to parse Set(menu, ...)");
    }
}

////////////////////////////////////////////////////////////////////////////////
//
//  Control form
//
////////////////////////////////////////////////////////////////////////////////

void setCfCaption(const char * s)
{
    mutexLock(M_STATE);
    
    if (_state.cf.caption == NULL) {
        _state.cf.caption = stringNew(s);
    } else {
        stringAssign(_state.cf.caption,s);
    }
    
    mutexUnlock(M_STATE);
}

const char* cfCaption()
{
    return (_state.cf.caption ? _state.cf.caption->str : NULL); 
}

void setCfTitle(const char * s)
{
    boolean_t setupMenu = (_curForm != CF);

    mutexLock(M_STATE);
    
    if (_state.cf.title == NULL) {
        _state.cf.title =  stringNew(s);
    } else {
        stringAssign(_state.cf.title,s);
    }
    mutexUnlock(M_STATE);

    setCurForm(CF);

    if (setupMenu) {
        setupDefMenu();
    }
}

const char* cfTitle()
{
    return (_state.cf.title ? _state.cf.title->str : NULL); 
}

void setCfStatus(const char * s)
{
    boolean_t setupMenu = (_curForm != CF);

    mutexLock(M_STATE);
    
    if (_state.cf.status == NULL) {
        _state.cf.status =  stringNew(s);
    } else {
        stringAssign(_state.cf.status,s);
    }

    mutexUnlock(M_STATE);
    
    setCurForm(CF);

    if (setupMenu) {
        setupDefMenu();
    }
}

const char* cfStatus()
{
    return (_state.cf.status ? _state.cf.status->str : NULL); 
}

void setIcons()
{
    boolean_t setupMenu = (curForm() != CF);

    char* token = strtok(NULL,",");

    if (token && strcmp(token,"SAME") != 0) {
        setCfCaption(token);
    }

    token = strtok(NULL,",");
    
    mutexLock(M_STATE);
    
    while (token) {

        while (isspace(*token)) {
            ++token;
        }

        int ic = -1;
        if (strncmp(token,"*",1) == 0) {
            ic = 9;
        } else if (strncmp(token,"#",1) == 0) {
            ic = 11;
        } else if (strncmp(token,"0",1) == 0) {
            ic = 10;
        } else {
            ic = atoi(token)-1;
        }
        if (ic >=0 && ic < 12) {
            token = strtok(NULL,",");
            while (token && isspace(*token)) {
                ++token;
            }

             if (_state.cf.icons[ic] == NULL) {
                _state.cf.icons[ic] =  stringNew((token?token:"none"));
            } else {
                stringAssign(_state.cf.icons[ic],(token?token:"none"));
            }
            INFO2("[DS]: setIcons %d %s",ic,_state.cf.icons[ic]->str);
        }
        token = strtok(NULL,",");
    }
    
    mutexUnlock(M_STATE);
    
    setCurForm(CF);

    if (setupMenu) {
        setupDefMenu();
    }

}

void setHints()
{
    char* token = strtok(NULL,",");
    
    mutexLock(M_STATE);
    
    while (token) {

        while (isspace(*token)) {
            ++token;
        }
        
        //DEBUG2("[DS]: setHints %s",token);

        int ic = -1;
        if (strncmp(token,"*",1) == 0) {
            ic = 9;
        } else if (strncmp(token,"#",1) == 0) {
            ic = 11;
        } else if (strncmp(token,"0",1) == 0) {
            ic = 10;
        } else {
            ic = atoi(token)-1;
        }

        if (ic >=0 && ic < 12) {
            token = strtok(NULL,",");
            while (token && isspace(*token)) {
                ++token;
            }

             if (_state.cf.hints[ic] == NULL) {
                _state.cf.hints[ic] =  stringNew((token?token:""));
            } else {
                stringAssign(_state.cf.hints[ic],(token?token:""));
            }
            INFO2("[DS]: setHints %d %s",ic,_state.cf.hints[ic]->str);
        }
        token = strtok(NULL,",");
    }
    
    mutexUnlock(M_STATE);
}

static void setUseVolume(boolean_t use)
{
    _state.cf.useVolume = use;
}

//
// Set(skin,default|bottomline|3x4|7x1
//    [,keypad_only|joystick_only]
//    [,ticker|noticker]   <-- TODO
//    [,volume]
//    [,choose,_button_]   <-- TODO
//    [,up,_button_] [,down,_button_])
//
void setSkin()
{
    
    setUseVolume(BOOL_NO);  // reset volume usage flag
    _state.cf.layout7x1 = BOOL_NO;
    
    char* token;

    boolean_t setupMenu = (curForm() != CF);
    
    mutexLock(M_STATE);
    
    if (_state.cf.upBtn) {
        stringFree(_state.cf.upBtn, BOOL_YES);
        _state.cf.upBtn = NULL;
    }
    if (_state.cf.dnBtn) {
        stringFree(_state.cf.dnBtn, BOOL_YES);
        _state.cf.dnBtn = NULL;
    }

    while((token = strtok(NULL,","))) {

        if (strcmp(token,"bottomline") == 0 || 
            strcmp(token,"7x1") == 0 || 
            strcmp(token,"6x1") == 0 || 
            strcmp(token,"5x1") == 0) {
            _state.cf.layout7x1 = BOOL_YES;
        } else if (strcmp(token,"volume") == 0) {
            setUseVolume(BOOL_YES);
        } else if (strcmp(token,"keypad_only") == 0) {
            _state.cf.keypad   = BOOL_YES;
            _state.cf.joystick = BOOL_NO;
        } else if (strcmp(token,"joystick_only") == 0) {
            _state.cf.keypad   = BOOL_NO;
            _state.cf.joystick = BOOL_YES;
        } else if (strcmp(token,"up") == 0) {
            token = strtok(NULL,",");
            if (token) {
                if (_state.cf.upBtn == NULL) {
                    _state.cf.upBtn =  stringNew(token);
                } else {
                    stringAssign(_state.cf.upBtn,token);
                }
            }
        } else if (strcmp(token,"down") == 0) {
            token = strtok(NULL,",");
            if (token) {
                if (_state.cf.dnBtn == NULL) {
                    _state.cf.dnBtn =  stringNew(token);
                } else {
                    stringAssign(_state.cf.dnBtn,token);
                }
            }
        }
    }

    //if (_state.cf.layout7x1) {
    //    freeCfCover();
    //}

    mutexUnlock(M_STATE);
    
    setCurForm(CF);

    if (setupMenu) {
        setupDefMenu();
    }
}

const char* cfIcon(int i)
{
    if (i < 0 || i >= ICON_NUM) return NULL;
    
    return (_state.cf.icons[i] ? _state.cf.icons[i]->str : NULL);
}

const char* cfHint(int i)
{
    if (i < 0 || i >= ICON_NUM) return NULL;
    
    return (_state.cf.hints[i] ? _state.cf.hints[i]->str : NULL);
}

void setCfCover(const char * s)
{
    //printf("setCfCover %s\n", s);
    mutexLock(M_STATE);

    if (s) {
        if (_state.cf.cover == NULL) {
            _state.cf.cover =  stringNew(s);
        } else {
            stringAssign(_state.cf.cover,s);
        }
        if (_state.cf.namedCover) {
            stringFree(_state.cf.namedCover, BOOL_YES);
            _state.cf.namedCover = NULL;
        }
    } else {
        freeCfCover();
    }
    mutexUnlock(M_STATE);
}

void setCfNamedCover(char* s) 
{
    //printf("setCfNamedCover %s\n", s);
    mutexLock(M_STATE);

    if (s) {
        if (_state.cf.namedCover == NULL) {
            _state.cf.namedCover =  stringNew(s);
        } else {
            stringAssign(_state.cf.namedCover,s);
        }
        if (_state.cf.cover) {
            stringFree(_state.cf.cover, BOOL_YES);
            _state.cf.cover = NULL;
        }
    } else {
        freeCfCover();
    }
    mutexUnlock(M_STATE);
}

const char* cfCover()
{
    return (_state.cf.cover ? _state.cf.cover->str : NULL); 
}

const char* cfNamedCover()
{
    return (_state.cf.namedCover ? _state.cf.namedCover->str : NULL); 
}

void freeCfCover()
{
    //printf("freeCfCover\n");
    if (_state.cf.cover)      stringFree(_state.cf.cover,      BOOL_YES);
    if (_state.cf.namedCover) stringFree(_state.cf.namedCover, BOOL_YES);
    _state.cf.cover = NULL; 
    _state.cf.namedCover = NULL;
}

// cover-audio -> /usr/share/..../cover-audio.png
string_t* findNamedCover(const char *name)
{
    char *confDir = dupVarValue(VAR_CFGDIR);
    string_t* fpath = confDir ? stringNew(confDir) : stringNew("");
    if (confDir) free(confDir);
    
    stringAppend(fpath, "/Icons/common/");
    stringAppend(fpath, name);
    stringAppend(fpath, ".png");
    
    struct stat statbuf;
    
    if (stat(fpath->str, &statbuf) < 0) {  // not found

        char *h = getenv("HOME");
        if (h) {
            stringAssign(fpath, h);
            stringAppend(fpath, "/.anyRemote/Covers/");
            stringAppend(fpath, name);
            stringAppend(fpath, ".png");
            if (stat(fpath->str, &statbuf) < 0) {
                stringFree(fpath,  BOOL_YES);
                return NULL;
            }
        } else {
            stringFree(fpath,  BOOL_YES);
            return NULL;
        }
    }
    
    if (S_ISREG(statbuf.st_mode) || S_ISLNK(statbuf.st_mode)) {
        return fpath;
    }
    
    stringFree(fpath,  BOOL_YES);
    return NULL;
}

const char* cfBg()
{
    return (_state.cf.visual.bg ? _state.cf.visual.bg->str : NULL);
}

const char* cfFg()
{
    return (_state.cf.visual.fg ? _state.cf.visual.fg->str : NULL);
}

const char* cfFont()
{
    return (_state.cf.visual.font ? _state.cf.visual.font->str : NULL);
}

void setCfVolume(const char * s)
{
    if (s) {
        if (_state.cf.volume == NULL) {
            _state.cf.volume =  stringNew(s);
        } else {
            stringAssign(_state.cf.volume,s);
        }
    } else {
        setCfVolume("");
    }
}

const char* cfVolume()
{
    return (_state.cf.volume ? _state.cf.volume->str : NULL); 
}

boolean_t useVolume()
{
    return _state.cf.useVolume;
}

boolean_t bottomlineSkin()
{
    return _state.cf.layout7x1;
}

const char* cfUpButton()
{
    return (_state.cf.upBtn ? _state.cf.upBtn->str : NULL); 
}

const char* cfDownButton()
{
    return (_state.cf.dnBtn ? _state.cf.dnBtn->str : NULL); 
}

boolean_t useKeypad()
{
    return _state.cf.keypad;
}

boolean_t useJoystick()
{
    return _state.cf.joystick;
}

////////////////////////////////////////////////////////////////////////////////
//
//  List form
//
////////////////////////////////////////////////////////////////////////////////

static void setLfCaption(const char * s)
{
    if (_state.lf.caption == NULL) {
        _state.lf.caption =  stringNew(s);
    } else {
        stringAssign(_state.lf.caption,s);
    }
}

const char* lfCaption()
{
    return (_state.lf.caption ? _state.lf.caption->str : NULL); 
}

// rely on previous strtok()
static void setLfList(boolean_t useIcons) 
{
    char *token3 = strtok(NULL,",\n");
    while (token3) {
        
        if (strlen(token3) > 0) {   // avoid empty list item
            
            ListItem * item = malloc(sizeof(ListItem));
            
            char * semicolon = index(token3,':');
            if (useIcons && semicolon) {
                *semicolon = '\0';
                semicolon++;
                item->icon   = stringNew(token3);
                item->string = stringNew(semicolon);
            } else {
                item->icon   = NULL;
                item->string = stringNew(token3);
            }

            _state.lf.items = listSingleAppend(_state.lf.items, item);
        }
        token3 = strtok(NULL,",\n");
    }
}

static void setLfIndex(const char * s)
{
    _state.lf.selIdx = (s ? atoi(s) : 1);
}

void setList(boolean_t useIcons)
{
    logger(L_INF, "[DS]: setList");
    boolean_t setupMenu = (_curForm != LI);

    char *token = strtok(NULL,",");
    if (!token) {
        return;
    }
    while (isspace(*token)) {
        ++token;
    }

    
    if (strncmp(token,"add",3) == 0 || strncmp(token,"replace",7) == 0) {

        mutexLock(M_STATE);
        _curForm = LI;

        char *token2 = strtok(NULL,","); // can be NULL
        if (!token2) {
            return;
        }

        logger(L_INF, "[DS]: setList add/replace");

        if (strcmp(token,"replace") == 0) {
            freeLfList();
        }

        if (strcmp(token2,"SAME") != 0) {
            setLfCaption(token2);
        }
        
        setLfList(useIcons);  // rely on previous strtok()
        mutexUnlock(M_STATE);

    } else if (strncmp(token,"close",5) == 0) {
       
        char *token2 = strtok(NULL,","); // can be NULL
        if (token2 && strcmp(token,"clear") == 0) {
            mutexLock(M_STATE);
            freeLfList();
            mutexUnlock(M_STATE);
        }
        _curForm = CF;
        setupDefMenu();
        return;
    } else if (strncmp(token,"clear",5) == 0) {
        mutexLock(M_STATE);
        freeLfList();
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"fg",2) == 0) {
        mutexLock(M_STATE);
        setFgBg(LI,BOOL_YES);
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"bg",2) == 0) {
        mutexLock(M_STATE);
        setFgBg(LI,BOOL_NO);
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"font",4) == 0) {
        mutexLock(M_STATE);
        setFont(LI);
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"select",6) == 0) {
        mutexLock(M_STATE);
        _curForm = LI;
        setLfIndex(strtok(NULL,","));
        mutexUnlock(M_STATE);
    } else if (strncmp(token,"show",4) == 0) {
        _curForm = LI;
    } else {
        ERROR2("[DS]: Can to parse Set([icon]list, ...)");
        return;
    }
    
    if (setupMenu) {
        setupDefMenu();
    }
}

int lfIndex()
{
    return _state.lf.selIdx;
}

const char* lfBg()
{
    return (_state.lf.visual.bg ? _state.lf.visual.bg->str : NULL);
}

const char* lfFg()
{
    return (_state.lf.visual.fg ? _state.lf.visual.fg->str : NULL);
}

const char* lfFont()
{
    return (_state.lf.visual.font ? _state.lf.visual.font->str : NULL);
}

void freeLfList()
{
    if (_state.lf.items) {
        listSingleFullFree(_state.lf.items, listItemFree);
        _state.lf.items = NULL;
        _state.lf.selIdx = 1;
    }
}

SingleList* lfList()
{
    return (_state.lf.items ? _state.lf.items : NULL); 
}

SingleList* lfListNth(int n)
{
    return (_state.lf.items ? listSingleNth(_state.lf.items, n) : NULL); 
}

int lfSize()
{
    return (_state.lf.items ? listSingleLength(_state.lf.items) : 0); 
}

////////////////////////////////////////////////////////////////////////////////
//
//  Text form
//
////////////////////////////////////////////////////////////////////////////////

static void setTfCaption(const char * s)
{
    if (_state.tf.caption == NULL) {
        _state.tf.caption =  stringNew(s);
    } else {
        stringAssign(_state.tf.caption,s);
    }
}

const char* tfCaption()
{
    return (_state.tf.caption ? _state.tf.caption->str : NULL); 
}

static void addTfText(const char * s)
{
    //INFO2("[DS]: addTfText %s", s); can be too long
    if (_state.tf.text == NULL) {
        _state.tf.text =  stringNew(s);
    } else {
        stringAppend(_state.tf.text,s);
    }
}

static void resetTfText()
{
    if (_state.tf.text) {
        stringTruncate(_state.tf.text,0);
    }
}

const char* tfText()
{
    return (_state.tf.text ? _state.tf.text->str : NULL); 
}

const char* tfBg()
{
    return (_state.tf.visual.bg ? _state.tf.visual.bg->str : NULL);
}

const char* tfFg()
{
    return (_state.tf.visual.fg ? _state.tf.visual.fg->str : NULL);
}

const char* tfFont()
{
    return (_state.tf.visual.font ? _state.tf.visual.font->str : NULL);
}

void setText(boolean_t split)
{
    //logger(L_INF, "[DS]: setText");
    boolean_t setupMenu = (_curForm != TX);

    char *token = strtok(NULL,",");
    if (!token) {
        return;
    }
    while (isspace(*token)) {
        ++token;
    }


    if (strncmp(token,"add",3) == 0 || strncmp(token,"replace",7) == 0) {

        mutexLock(M_STATE);
        _curForm = TX;

        char *token2 = strtok(NULL,","); // can be NULL
        if (!token2) {
            return;
        }

        //logger(L_INF, "[DS]: setText add/replace");

        if (strncmp(token,"replace",7) == 0) {
            resetTfText();
        }

        if (strcmp(token2,"SAME") != 0) {
            setTfCaption(token2);
        }
        
        if (split) {
            char *token3 = strtok(NULL,"\n");
            while (token3) {

                //INFO2("[DS]: Set(text, ...) %s", token3);
                addTfText(token3);
                token3 = strtok(NULL,"\n");
            }
        } else {
            addTfText(token2 + strlen(token2) + 1);
        }
        mutexUnlock(M_STATE);
    
    } else if (strcmp(token,"close") == 0) {
    
        char *token2 = strtok(NULL,","); // can be NULL
        if (token2 && strcmp(token,"clear") == 0) {
            mutexLock(M_STATE);
            resetTfText();
            mutexUnlock(M_STATE);
        }
    
        _curForm = CF;
        setupDefMenu();
        return;
    
    } else if (strncmp(token,"clear",5) == 0) {
        mutexLock(M_STATE);
        resetTfText();
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"fg",2) == 0) {
        mutexLock(M_STATE);
        setFgBg(TX,BOOL_YES);
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"bg",2) == 0) {
        mutexLock(M_STATE);
        setFgBg(TX,BOOL_NO);
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"font",4) == 0) {
        mutexLock(M_STATE);
        setFont(TX);
        mutexUnlock(M_STATE);
        return;
    } else if (strncmp(token,"show",4) == 0) {
        _curForm = TX;
    } else {
        ERROR2("[DS]: Can to parse Set(text, ...)");
        return;
    }
    
    if (setupMenu) {
        setupDefMenu();
    }
}

////////////////////////////////////////////////////////////////////////////////
//
//  Edit form
//
////////////////////////////////////////////////////////////////////////////////

static void setEfCaption(const char * s)
{
    if (_state.ef.caption == NULL) {
        _state.ef.caption =  stringNew(s);
    } else {
        stringAssign(_state.ef.caption,s);
    }
}

const char* efCaption()
{
    return (_state.ef.caption ? _state.ef.caption->str : NULL); 
}

static void setEfLabel(const char * s)
{
    if (_state.ef.label == NULL) {
        _state.ef.label =  stringNew(s);
    } else {
        stringAssign(_state.ef.label,s);
    }
}

const char* efLabel()
{
    return (_state.ef.label ? _state.ef.label->str : NULL); 
}

static void setEfText(const char * s)
{
    if (_state.ef.text == NULL) {
        _state.ef.text =  stringNew(s);
    } else {
        stringAssign(_state.ef.text,s);
    }
}

const char* efText()
{
    return (_state.ef.text ? _state.ef.text->str : NULL); 
}

void setEfPassword(boolean_t use)
{
    _state.ef.pass = use;
}

boolean_t efPassword()
{
    return _state.ef.pass; 
}

// rely on previous strtok()
void setEditfield()
{
    logger(L_INF, "[DS]: setEditfield");

    boolean_t setupMenu = (_curForm != EF);

    char *token1 = strtok(NULL,",");
    if (!token1) {
        return;
    }

    char *token2 = strtok(NULL,",");
    if (!token2) {
        return;
    }

    char *token3 = strtok(NULL,",");  // can be NULL
    
    mutexLock(M_STATE);
    
    setEfCaption(token1);
    setEfLabel  (token2);
    setEfText   (token3 ? token3 : "");
    setEfPassword(BOOL_NO);

    mutexUnlock(M_STATE);
    
    setCurForm(EF);
    
    if (setupMenu) {
        setupDefMenu();
    }
    INFO2("[DS]: setEditfield done %d", _curForm);
}

void setPassField()
{
    logger(L_INF, "[DS]: setPassField");
    
    boolean_t setupMenu = (curForm() != EF);

    mutexLock(M_STATE);
    
    setEfCaption("Enter password");
    setEfLabel  ("Enter password");
    setEfText   ("");
    setEfText   ("");
    setEfPassword(BOOL_YES);
    
    mutexUnlock(M_STATE);

    setCurForm(EF);

    if (setupMenu) {
        setupDefMenu();
    }
}


////////////////////////////////////////////////////////////////////////////////
//
//  Window Form
//
////////////////////////////////////////////////////////////////////////////////

//
// Set(image,window,_image_file_name_)
// Set(image,icon,_icon_name_,_image_file_name_) -- not supported
// Set(image,show|close|cursor|nocursor|remove_all) -- only 1,2 are supported
//
void setImage(const char* cmd, const char* file)
{
    INFO2("[DS]: setImage %s %s",cmd,file);
    boolean_t setupMenu = (curForm() != WM);

    if (!cmd) {
        return;
    }
    while (isspace(*cmd)) {
        ++cmd;
    }

    
    if (strncmp(cmd,"window",6) == 0) {

        logger(L_INF, "[DS]: setImage: window");

        setCurForm(WM);

        if (!file) {
            return;
        }
        while (isspace(*file)) {
            ++file;
        }
        
        mutexLock(M_STATE);
        if (_state.wf.window) {
             stringAssign(_state.wf.window, file);
        } else {
             _state.wf.window = stringNew(file);
        }
        mutexUnlock(M_STATE);
    
    } else if (strncmp(cmd,"show",4) == 0) {
        setCurForm(WM);
    } else if (strncmp(cmd,"close",5) == 0) {
        setCurForm(CF);
        setupDefMenu();
        return;
    } else {
        logger(L_INF, "[DS]: setImage: skip command");
        return;
    }

    
    if (setupMenu) {
        setupDefMenu();
    }
}

const char* wfImage()
{
    return (_state.wf.window ? _state.wf.window->str : NULL);
}

////////////////////////////////////////////////////////////////////////////////
//
//  Params
//
////////////////////////////////////////////////////////////////////////////////

void setParams()
{
    char *token = strtok(NULL,",");
    if (!token) {
        return;
    }

    while (token) {

        while (isspace(*token)) {
            ++token;
        }

        if (strncmp(token,"icon_padding",12) == 0) {

            char *token2 = strtok(NULL,",");
            if (!token2) {
                return;
            }

            while (isspace(*token2)) {
                ++token2;
            }

            _iconPadding = atoi(token2);
            if (_iconPadding <= 0) {
                _iconPadding = 0;
            }

            INFO2("[DS]: Use icon padding %d", _iconPadding);

        } else if (strncmp(token,"icon_size",9) == 0) {

            char *token2 = strtok(NULL,",");
            if (!token2) {
                return;
            }

            while (isspace(*token2)) {
                ++token2;
            }

            int iconSizeScale = atoi(token2);
            if (iconSizeScale <= 0) {
                iconSizeScale = 64;
            }

            if (iconSizeScale <= 32) {
                _iconSize = 32;
            } else if (iconSizeScale <= 48) {
                _iconSize = 48;
            } else if (iconSizeScale <= 64) {
                _iconSize = 64;
            } else {
                _iconSize = 128;
            }

            INFO2("[DS]: Use icon size %d", _iconSize);

        } else {
            ERROR2("[DS]: Skip Set(parameter, ...) %s", token);
        }

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

int iconPadding()
{
    return _iconPadding;
}

int iconSize()
{
    return _iconSize;
}

static void updateStateSetFile(dMessage* dm)
{
    DEBUG2("[DS]: updateStateSetFile %s", dm->file);
    if (strncmp(dm->value,"Set(cover,noname",16) == 0) {
        setCfCover(dm->file);
        xmlSetLayoutOk(BOOL_NO);  // Need to regenerate
    } else if (strncmp(dm->value,"Set(image",9) == 0) {
        setImage((dm->value + 10), dm->file);  // 10 = 9 + ","
    } else {
        ERROR2("[DS]: Unknown command file: %s", (const char*) dm->value);
    }
}

static void updateStateSet(dMessage* dm)
{
    const char* value = dm->value;
    
    DEBUG2("[DS]: updateState %d", (value ? (int) strlen(value) : -1));

    char * cmd = strdup(value);

    stripCommandEnding(cmd);

    if (strlen(cmd) < MAXMAXLEN) {
        INFO2("[DS]: parse %d %s", curForm(), cmd);
    } else {
        INFO2("[DS]: parse %d ... command too long ...", curForm());
    }

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

    if (strncmp(token,"Set(status",10) == 0) {
        setCfStatus(cmd+strlen("Set(status,"));
    } else if (strncmp(token,"Set(title",9) == 0) {
        setCfTitle(cmd+strlen("Set(title,"));
    } else if (strncmp(token,"Set(icons",9) == 0) {
        setIcons();
        xmlSetLayoutOk(BOOL_NO);  // Need to regenerate
    } else if (strncmp(token,"Set(hints",9) == 0) {
        setHints();
        xmlSetLayoutOk(BOOL_NO);  // Need to regenerate
    } else if (strncmp(token,"Set(font",8) == 0) {
        mutexLock(M_STATE);
        setFont(CF);
        mutexUnlock(M_STATE);
    } else if (strncmp(token,"Set(fg",6) == 0) {
        mutexLock(M_STATE);
        setFgBg(CF,BOOL_YES);
        mutexUnlock(M_STATE);
    } else if (strncmp(token,"Set(bg",6) == 0) {
        mutexLock(M_STATE);
        setFgBg(CF,BOOL_NO);
        mutexUnlock(M_STATE);
    } else if (strncmp(token,"Set(volume",10) == 0) {
        char* sz = strtok(NULL,",");
        mutexLock(M_STATE);
        setCfVolume(sz);
        mutexUnlock(M_STATE);
    } else if (strncmp(token,"Set(layout",10) == 0 ||
               strncmp(token,"Set(skin",8) == 0) {
        setSkin();
        xmlSetLayoutOk(BOOL_NO);  // Need to regenerate
    } else if (strncmp(token,"Set(cover",9) == 0) {
        char* subcmd = strtok(NULL,",");
        if (strncmp(subcmd,"by_name",7) == 0) {
            setCfNamedCover(subcmd+8);
            xmlSetLayoutOk(BOOL_NO);  // Need to regenerate
        } else if (strncmp(subcmd,"clear",5) == 0) {
            setCfNamedCover(NULL);
            xmlSetLayoutOk(BOOL_NO);  // Need to regenerate
        } else {
            ERROR2("[DS]: Improperly formed command Set(cover,...)");
        }
    } else if (strncmp(token,"Set(list",8) == 0) {
        setList(BOOL_NO);
    } else if (strncmp(token,"Set(iconlist",12) == 0) {
        setList(BOOL_YES);
    } else if (strncmp(token,"Set(text",8) == 0) {
        setText(BOOL_NO);
    } else if (strncmp(token,"Set(menu",8) == 0) {
        setMenu();
    } else if (strncmp(token,"Set(editfield",13) == 0) {
        setEditfield();
    } else if (strncmp(token,"Set(image",9) == 0) {
        ERROR2("[DS]: Improperly formed command Set(image,...)");
    } else if (strncmp(token,"Set(popup",9) == 0) {
        // ignore
    } else if (strncmp(token,"Set(disconnect",14) == 0) {
        // ignore
    } else if (strncmp(token,"Set(fullscreen",16) == 0) {
        // ignore
    } else if (strncmp(token,"Set(vibrate",11) == 0) {
        // ignore
    } else if (strncmp(token,"Set(parameter",13) == 0) {
        setParams();
    } else if (strncmp(token,"End",3) == 0) {
        // ignore
    } else {
        ERROR2("[DS]: Unknown command %s", token);
    }

    free(cmd);
}

void updateState(dMessage* dm)
{
    if (dm->type == DM_SET) {
        updateStateSet(dm);
    }
    if (dm->type == DM_SETFILE) {
        updateStateSetFile(dm);
    }
}

