//
// anyRemote
// a wi-fi or bluetooth remote for your PC.
//
// Copyright (C) 2006-2018 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 <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "atsend.h"
#include "btio.h"
#include "common.h"
#include "conf.h"
#include "dispatcher.h"
#include "executor.h"
#include "gen_ar.h"
#include "peer.h"
#include "pr_btspp.h"
#include "pr_frontend.h"
#include "pr_l2cap.h"
#include "pr_rfcomm.h"
#include "pr_serial.h"
#include "pr_socket.h"
#include "pr_stdin.h"
#include "pr_web.h"
#include "state.h"
#include "str.h"
#include "utils.h"

extern char tmp[MAXMAXLEN];

// executor.c
extern void customizePeer(int id, int szX, int szY, int coverSz);

static int          _peerCounter = 1;        
static SingleList * _connections = NULL;

static int peerWrite     (ConnectInfo* peer, const dMessage* msg);
static int peerWriteBytes(ConnectInfo* peer, const char* command);
static void writeIViewerHeartbeat(int fd);
static void writeBemusedHeartbeat(int fd);


static ConnectInfo* allocPeer(boolean_t allocCache)
{
    ConnectInfo* peer = (ConnectInfo*) malloc(sizeof(ConnectInfo));

    peer->id                   = _peerCounter;
    _peerCounter++;
    peer->mode                 = SERVER_MAX;
    peer->state                = PEER_DISCONNECTED;
    peer->port                 = -1;
    peer->connectionData       = NULL;  // specific to connection
    peer->portStr              = NULL;
    
    if (allocCache) {
        peer->cache            = (DCache*) malloc(sizeof(DCache));    
        int i=0;
        for (; i<ID_SET_MAX; i++) {
            peer->cache->lastValues    [i] = NULL;
            peer->cache->lastValuesSize[i] = -1;
        }
    } else {
        peer->cache = NULL;
    }

    return peer;
}

static void freeCachedData(ConnectInfo* peer)
{
    INFO2("[DS]: freeCachedData() peer %d", peer->id);
    DCache* cache = peer->cache;
    if (cache) {
        int i = 0;
        for (; i<ID_SET_MAX; i++) {
            if (cache->lastValues[i]) {
                free(cache->lastValues[i]);
                cache->lastValues[i] = NULL;
            }
        }
    }
}

static void freePeer(void* data)
{
    ConnectInfo* peer = (ConnectInfo* ) data;
    INFO2("[DS]: freePeer() %d", peer->id);
    if (peer->connectionData) {
        free(peer->connectionData);
    }
    freeCachedData(peer);
    free(peer);
}

// do not take into account connection-less peers (HTML + XML)
static int countConnections()
{
    int cnum = 0;
    SingleList* list = _connections;
    //INFO2("[DS]: countConnections() enter %d", listSingleLength(list));
    
    while (list) {
       ConnectInfo* peer = (ConnectInfo*) list->data;
       if (peer->state == PEER_CONNECTED && peer->mode != FRONT_END) {
           cnum++;
       }
       list = listSingleNext(list);
    }
    return cnum;
}

// do not take into account connection-less peers (HTML + XML)
static int countOtherConnections(ConnectInfo* peer)
{
    int cnum = 0;
    SingleList* list = _connections;
    
    while (list) {
       ConnectInfo* otherPeer = (ConnectInfo*) list->data;
       if (otherPeer->state == PEER_CONNECTED && otherPeer->mode != FRONT_END && otherPeer != peer) {
           cnum++;
       } else if (peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML) {
           cnum++;
       }
       list = listSingleNext(list);
    }
    return cnum;
}

static int dummyOpen(ConnectInfo* connInfo)
{
    DEBUG2("[DS]: dummyOpen");
    return EXIT_OK;
}

static int dummyFD(ConnectInfo* connInfo)
{
    return -1;
}

static int setupNope(ConnectInfo* p)
{
    return 1;
}

static int acceptNope(ConnectInfo* p)
{
    switch (p->mode) {
        case SERVER_WEB:
            logger(L_INF, "[DS]: Built-in web server: init OK");
            break;
        case SERVER_CMXML:
            logger(L_INF, "[DS]: Built-in XML server: init OK");
            break;
        case CLIENT_ILIRC:
        case CLIENT_NOAT:
        case SERVER_STDIN:
            logger(L_INF, "[DS]: Unix client: init OK");
            break;
    }
    return 1;
}

static struct {
  int  id;
  int  (*descriptor)           (ConnectInfo* p);
  int  (*openConnection)       (ConnectInfo* p);
  int  (*setupPre)             (ConnectInfo* p);
  int  (*listenConnection)     (ConnectInfo* p);
  int  (*acceptConnection)     (ConnectInfo* p);
  int  (*setupPost)            (ConnectInfo* p);  // unused ?
  int  (*writeConnection)      (ConnectInfo* p, const dMessage* msg);
  int  (*writeBytesConnection) (ConnectInfo* p, const char* value);
  void (*resetConnection)      (ConnectInfo* p);
  void (*closeConnection)      (ConnectInfo* p, int final);
} _peerHandlers[] = {

  // type         get FD    open          setup_pre    listen        accept        setup_post   write        write bytes     reset        close
  //              (EXIT_OK/ (1/-1)        (1/-1)       (1/-1)        (EXIT_OK/     (1/-1)       (EXIT_OK/    (void)          (void)       (void)
  //                   NOK)                                               NOK)                       NOK)
  {CLIENT_RFCOMM, rfcommFD, rfcommConnect,rfcommSetup, setupNope,    acceptNope,   setupNope,   peerWrite,   NULL,           rfcommReset, rfcommClose},
  {CLIENT_AT,     serialFD, serialOpen,   serialSetup, setupNope,    acceptNope,   setupNope,   peerWrite,   NULL,           serialReset, serialClose},
  {SERVER_TCP,    socketFD, socketOpen,   setupNope,   socketListen, socketAccept, setupNope,   socketWrite, peerWriteBytes, socketReset, socketClose},
  {SERVER_BT,     btsppFD,  btsppOpen,    setupNope,   setupNope,    btsppAccept,  setupNope,   peerWrite,   peerWriteBytes, btsppReset,  btsppClose },
                                                       /*call btsppListen from  btsppOpen */
  {CLIENT_ILIRC,  uxsFD,    uxsOpen,      setupNope,   setupNope,    acceptNope,   setupNope,   NULL,        NULL,           uxsReset,    uxsClose   },
  {CLIENT_NOAT,   serialFD, serialOpen,   setupNope,   setupNope,    acceptNope,   setupNope,   peerWrite,   NULL,           serialReset, serialClose},
  {SERVER_STDIN,  dummyFD,  stdinOpen,    setupNope,   setupNope,    acceptNope,   setupNope,   NULL,        NULL,           stdinReset,  stdinClose },
  {SERVER_WEB,    webFD,    openWeb,      setupNope,   listenWeb,    acceptWeb,    setupNope,   writeWeb,    NULL,           webReset,    webClose   },
  {SERVER_CMXML,  webFD,    openWeb,      setupNope,   listenWeb,    acceptWeb,    setupNope,   writeWeb,    NULL,           webReset,    webClose   },
  #ifdef USE_L2CAP
  {SERVER_L2CAP,  l2capFD,  l2capOpen,    setupNope,   l2capSetup,   l2capAccept,  setupNope,   peerWrite,   peerWriteBytes, l2capReset,  l2capClose },
  #endif
  {SERVER_UX ,    socketFD, dummyOpen,    setupNope,   socketListen, socketAccept, setupNope,   peerWrite,   peerWriteBytes, socketReset, socketClose}, // not used
  {FRONT_END ,    feFD,     feOpen,       setupNope,   setupNope,    acceptNope,   setupNope,   NULL,        NULL,           feReset,     feClose    }
};

void freePeers()
{
    INFO2("[DS]: freePeers() start");
    listSingleFullFree(_connections, freePeer);
    _connections = NULL;
        
    freeState();

    INFO2("[DS]: freePeers() end");
}

static int addTcpPeer(ConnectInfo* peer, char *strPort)
{
    // check is it port or unix socket
    char * ch = strPort;
    int isPort = 1;
    while (*ch != '\0') {
        if (isdigit(*ch)) {
            ch++;
        } else {
            isPort = 0;
            break;
        }
    }

    if (isPort) {

        peer->mode  = SERVER_TCP;
        peer->port  = atoi(strPort);

        if (peer->port <= 0) {
            printf("ERROR: Improper port to use %d!\n", peer->port);
            ERROR2("[DS]: Improper port %d to use", peer->port);
            return EXIT_ABORT;
        }

        DEBUG2("[DS]: Peer %d: TCP Server mode, port %d", peer->id, peer->port);

    } else {

        /*sprintf(tmp, "Unix socket Server mode. Use socket %s\n", strPort);
        logger(L_DBG, tmp);

        if ((ret = openSocketPort(SERVER_UX, -1, strPort)) < 0) {
            return -1;
        }
        peer->mode = SERVER_UX;
        peer->portStr = stringNew(strPort);
        */
        printf("ERROR: incorrect port\n");
        return EXIT_ABORT;

    }
    return EXIT_OK;
}

//
// fill ConnectInfo structure, return EXIT_ABORT in unsuccessful
//
static int addPeer(char * portIn)
{

    if (portIn == NULL) {
        return EXIT_ABORT;
    }
    INFO2("[DS]: addPeer() %s", portIn);

    char* strPort;

    // device should be in format:

    // deprecated
    // 1. (AT mode)     /dev/something                            used with AT mode commands

    // 1. (AT mode)     rfcomm:00:12:EF:32:21:1A:xx (xx is integer from 1 to 32)
    // 2. (Server mode) socket:NNNN or                             used with java client
    //                  socket:/path/to/socket                    used with java client (SERVER_UX, not used)
    // 3. (Server mode) bluetooth:NN or bluetooth (port will be = DEFAULT_BT_CHANNEL)     used with java client
    // 4. (Server mode) web:NNNN                            built-in web server
    // 5. (Server mode) cmxml:NNNN                            XML services interface
    // 6. local:/some/path                                    used with java client, like 1, but no AT commands
    // 7. ilirc:/dev/something                                 like 1, but no AT commands
    // 8. stdio                                                used with java client, like 1, but no AT commands
    // 9. avahi                                                skipped here


    ConnectInfo* peer = allocPeer(BOOL_YES);

    if ((strPort = strstr(portIn, INET_SOCKET)) != NULL) {

        strPort += strlen(INET_SOCKET);
    
        if (addTcpPeer(peer, strPort) == EXIT_ABORT) {
            free(peer);
            return EXIT_ABORT;
        }
    
    } else if ((strPort = strstr(portIn, PEER_TCP)) != NULL) {

        strPort += strlen(PEER_TCP);
    
        if (addTcpPeer(peer, strPort) == EXIT_ABORT) {
            free(peer);
            return EXIT_ABORT;
        }
    
    } else if ((strPort = strstr(portIn, ILIRC_SOCKET)) != NULL) {

        strPort += strlen(ILIRC_SOCKET);

        peer->mode  = CLIENT_ILIRC;
        peer->portStr = stringNew(strPort);

        DEBUG2("[DS]: Peer %d: Unix socket client mode, socket %s", peer->id, peer->portStr->str);

    } else if ((strPort = strstr(portIn, BT_SOCKET)) != NULL) {

        strPort += strlen(BT_SOCKET);

        peer->mode = SERVER_BT;
        if (strstr(strPort, ":") == NULL) { // just "bluetooth
            peer->port = DEFAULT_BT_CHANNEL;
        } else {
            strPort++;
            peer->port = atoi(strPort);
        }
        DEBUG2("[DS]: Peer %d: Bluetooth Server mode, channel %d", peer->id, peer->port);

    } else if ((strPort = strstr(portIn, L2CAP_SOCKET)) != NULL) {

        #ifdef USE_L2CAP
        strPort += strlen(L2CAP_SOCKET);

        peer->mode = SERVER_L2CAP;
        if (strstr(strPort, ":") == NULL) { // just "l2cap"
            peer->port = DEFAULT_L2CAP_PORT;
        } else {
            peer->port = atoi(strPort);
        }
        DEBUG2("[DS]: Peer %d: L2CAP Server mode, port %d", peer->id, peer->port);

        #endif

    } else if ((strPort = strstr(portIn, UNIX_SOCKET)) != NULL) {

        strPort += strlen(UNIX_SOCKET);

        peer->mode    = CLIENT_NOAT;
        peer->portStr = stringNew(strPort);

        DEBUG2("[DS]: Peer %d: Serial Client mode (no AT). Use device %s", peer->id, peer->portStr->str);

    } else if ((strPort = strstr(portIn, STDIN_STREAM)) != NULL) {

        peer->mode = SERVER_STDIN;
        DEBUG2("[DS]: Peer %d: stdin", peer->id);

    } else if ((strPort = strstr(portIn, RFCOMM_DEVICE)) != NULL) {

        if (!(strlen(portIn) == strlen(RFCOMM_DEVICE) + BT_ADDR_LEN + 3    || // 00:12:EF:32:21:1A:p
                strlen(portIn) == strlen(RFCOMM_DEVICE) + BT_ADDR_LEN + 4) || // 00:12:EF:32:21:1A:pp
                portIn[strlen(RFCOMM_DEVICE)] != ':' ||
                portIn[strlen(RFCOMM_DEVICE) + BT_ADDR_LEN + 1] != ':') {
            printf("ERROR: Improper connect string !\n");
            free(peer);
            return EXIT_ABORT;
        }

        char sBtAddr[18];
        strncpy(sBtAddr,portIn + strlen(RFCOMM_DEVICE) + 1,17);
        sBtAddr[17] = '\0';

        peer->mode  = CLIENT_RFCOMM;
        peer->port  = atoi(portIn + strlen(RFCOMM_DEVICE) + BT_ADDR_LEN + 2);
        peer->portStr = stringNew(sBtAddr);

        DEBUG2("[DS]: Peer %d: Serial Client mode, device %s : %d", peer->id, peer->portStr->str, peer->port);

    } else if ((strPort = strstr(portIn, WEB_SOCKET)) != NULL) {

        strPort += strlen(WEB_SOCKET);

        // check is it port
        char * ch = strPort;
        int isPort = 1;
        while (*ch != '\0') {
            if (isdigit(*ch)) {
                ch++;
            } else {
                isPort = 0;
                break;
            }
        }

        if (isPort) {

            peer->mode  = SERVER_WEB;
            peer->port  = atoi(strPort);
            if (peer->port <= 0) {
                printf("ERROR: Improper port to use !\n");
                ERROR2("[DS]: Improper port %d to use", peer->port);
                free(peer);
                return EXIT_ABORT;
            }

            DEBUG2("[DS]: Peer %d: Web Server mode. Use port %d", peer->id, peer->port);

        } else {
            logger(L_ERR,"[DS]: can not determine web server port");
            free(peer);
            return EXIT_ABORT;
        }

    } else if ((strPort = strstr(portIn, CMXML_SOCKET)) != NULL) {

        strPort += strlen(CMXML_SOCKET);

        // check is it port
        char * ch = strPort;
        int isPort = 1;
        while (*ch != '\0') {
            if (isdigit(*ch)) {
                ch++;
            } else {
                isPort = 0;
                break;
            }
        }

        if (isPort) {

            peer->mode  = SERVER_CMXML;
            peer->port  = atoi(strPort);

            DEBUG2("[DS]: Peer %d: XML Server mode. Use port %d", peer->id, peer->port);

        } else {
            logger(L_ERR,"[DS]: can not determine XML server port");
            free(peer);
            return EXIT_ABORT;
        }

    } else if ((strPort = strstr(portIn, AT_DEVICE)) != NULL) {

        strPort += strlen(AT_DEVICE);

        peer->mode  = CLIENT_AT;
        peer->portStr = stringNew(strPort);
        DEBUG2("[DS]: Peer %d: Serial Client mode. Use device %s", peer->id, peer->portStr->str);

    } else if (strstr(portIn, AVAHI_USE)) {
       
        // do nothing
        logger(L_DBG, "[DS]: Avahi flag is ON");
        freePeer(peer);
        return EXIT_OK;
       
    } else {

        peer->mode  = CLIENT_AT;
        peer->portStr = stringNew(portIn);
        DEBUG2("[DS]: Peer %d: Default: use serial Client mode. Use device %s", peer->id, peer->portStr->str);
    }

    _connections = listSingleAppend(_connections, peer);

    return EXIT_OK;
}

void writeToFrontEnd(const char *buf)
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;
        if (peer->mode == FRONT_END) {
            feWrite(peer,buf);
        }
        list = listSingleNext(list);
    }
}

void connectNotify(int peer)
{
    DEBUG2("[DS]: connectNotify");
    writeToFrontEnd("Connected");
    sendEventToExecutor(peer, ID_EVT_CONNECT);
}

static boolean_t needStoreState()
{
    int connCnt = 0;
    
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;

        if (peer->mode == SERVER_WEB || 
            peer->mode == SERVER_CMXML) {
            connCnt += 2;   // need to store state in any case if WEB/CMXML is used
        } else if (peer->mode == SERVER_TCP || 
                   peer->mode == SERVER_BT  ||
                   peer->mode == CLIENT_NOAT||
                   peer->mode == SERVER_UX  
                   #ifdef USE_L2CAP
                   || peer->mode == SERVER_L2CAP
                   #endif
                   ) {
            connCnt += 1;
        }
        if (connCnt > 1) {
            break;
        }
        list = listSingleNext(list);
    }
    DEBUG2("[DS]: needStoreState() %s", (connCnt > 1 ? "Y" : "N"));
    return (connCnt > 1 ? BOOL_YES : BOOL_NO);
}

boolean_t haveConnectionless()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;
        if (peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML) {
            return 1;
        }
        list = listSingleNext(list);
    }
    return 0;
}

int definePeers()
{
    DEBUG2("[DS]: definePeers");
    char* peers = getDevice();

    // split peers by ','
    char *sep;
    char* peer = peers;
    int ret = EXIT_ABORT;

    while ((sep = index(peer,','))) {

        *sep = '\0';

        INFO2("[DS]: definePeers() peer %s", peer);
        int ret1 = addPeer(peer);
        if (ret1 != EXIT_ABORT) {
            ret = EXIT_OK;
        }

        peer = sep+1;

    }

    int ret1 = addPeer(peer);
    if (ret1 != EXIT_ABORT) {
        ret = EXIT_OK;
    }

    free(peers);
    
    int fePort = getFrontEnd();
    if (fePort > 0) {
        ConnectInfo* peer = allocPeer(BOOL_NO);
        peer->mode  = FRONT_END;
        peer->port  = fePort;
        DEBUG2("[DS]: Add FE peer id=%d on port %d", peer->id, peer->port);
        
        _connections = listSingleAppend(_connections, peer);
    }
    
    if (needStoreState()) {
        DEBUG2("[DS]: definePeers() init state");
        initState();
    }
    if (haveConnectionless()) {
        DEBUG2("[DS]: definePeers() connectionless connectNotify");
        connectNotify(0);
    }

    DEBUG2("[DS]: definePeers done %d (peers #%d)", ret, listSingleLength(_connections));
    return ret;
}

int openPeers()
{
    DEBUG2("[DS]: openPeers %d", listSingleLength(_connections));

    int ret = EXIT_OK;
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;
        
        if (peer->state == PEER_DISCONNECTED) {
            INFO2("[DS]: Open peer with mode %d", peer->mode);

            int ret1 = _peerHandlers[peer->mode].openConnection(peer);
            
            DEBUG2("[DS]: openPeers peer %d ret code %d", peer->id, ret1);

            if (peer->state != PEER_DISCONNECTED) {  // for TCP (etc.) connection still can be in DISCONNECT
                INFO2("[DS]: Open peer %d connect fdescriptor %d", peer->id, _peerHandlers[peer->mode].descriptor(peer));
            }

            if (ret1 != EXIT_NOK) {
                ret = ret1;
            }
        } else {
            DEBUG2("[DS]: openPeers peer %d have state %s", peer->id, 
                    (peer->state == PEER_DISCONNECTED ? "DISCONNECTED" : 
                     (peer->state == PEER_WAIT_LISTEN ? "WAIT_LISTEN" : 
                      (peer->state == PEER_WAIT_ACCEPT ? "PEER_WAIT_ACCEPT" : 
                       (peer->state == PEER_CONNECTED ? "CONNECTED" : "UNKNOWN")
                      )
                     )
                    )
                   );
        }
        list = listSingleNext(list);
    }
    return ret;
}

void closePeers(int final)
{
    DEBUG2("[DS]: closePeers %d", final);
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;

        _peerHandlers[peer->mode].closeConnection(peer, final);

        list = listSingleNext(list);
    }
}

int disconnectPeersInternal(ConnectInfo* peer)
{
    int canForceExit = 0;

    dMessage* dm = allocDMessage();
    dm->type     = DM_SET;
    dm->subtype  = ID_SET_DISCONN;
    dm->size     = 16;
    dm->value    = strdup(CMD_STR_DISCONNECT);

    if (peer->mode == SERVER_BT) {

        if (getIViewer() || getBemused()) { 
            canForceExit = 1;
        } else {
            logger(L_INF, "[DS]: Got exit event: send disconnect message");
            if (btsppWrite(peer, dm) != EXIT_OK) {
                canForceExit = 1;
            }
        }

    } else if (peer->mode == SERVER_TCP) {

        if (getIViewer() || getBemused()) { 
            canForceExit = 1;
        } else {
            logger(L_INF, "[DS]: Got exit event: send disconnect message");
            if (socketWrite(peer, dm) != EXIT_OK) {
                canForceExit = 1;
            }
        }
        
    } else if (peer->mode == SERVER_UX) {

        logger(L_INF, "[DS]: Got exit event: send disconnect message");
        if (socketWrite(peer, dm) != EXIT_OK) {
            canForceExit = 1;
        }

    } else if (peer->mode == CLIENT_RFCOMM ||
               peer->mode == CLIENT_AT) {

        int fd = _peerHandlers[peer->mode].descriptor(peer);
        if (fd >= 0) {
            sendCMER(fd, CMER_OFF);
        }
        canForceExit = 1;

    #ifdef USE_L2CAP
    } else if (peer->mode == SERVER_L2CAP) {

        if (l2capWrite(peer, dm) != EXIT_OK) {
            canForceExit = 1;
        }
       
    #endif
    } else if (peer->mode == SERVER_WEB ||
               peer->mode == SERVER_CMXML) {
        canForceExit = 1;
    } else if (peer->mode == FRONT_END) {
        canForceExit = 1;
    }

    free(dm->value);
    free(dm);

    DEBUG2("[DS]: disconnectPeersInternal %d mode=%d can force exit=%d", peer->id, peer->mode, canForceExit);
    return canForceExit;
}

// retuns 1/-1
// return code is used in case of exit only
int disconnectPeers(void)
{
    DEBUG2("[DS]: disconnectPeers");
    int ret = 1;
    SingleList* list = _connections;
    while (list) {

        ConnectInfo* peer = (ConnectInfo*) list->data;

        int ret1 = disconnectPeersInternal(peer);
        if (ret1 != 1) {
            ret = ret1;
        }
        list = listSingleNext(list);
    }
    DEBUG2("[DS]: disconnectPeers can force exit=%d", ret);
    return ret;
}

// Do some tuning of opened connection 
// (used in client mode)
int setupPeersPre(void)
{
    DEBUG2("[DS]: setupPeersPre");
    int ret = -1;
    SingleList* list = _connections;
    while (list) {

        ConnectInfo* peer = (ConnectInfo*) list->data;
            
        int retS = _peerHandlers[peer->mode].setupPre(peer);

        if (retS != -1) {
            ret = retS;
        }
        list = listSingleNext(list);
    }
    return ret;
}

static boolean_t isDataCacheable(int what) 
{
    if (what == ID_SET_BG      ||
        what == ID_SET_CAPTION ||
        what == ID_SET_FG      ||
        what == ID_SET_FONT    ||
        what == ID_SET_ICONS   ||
        what == ID_SET_HINTS   ||
        what == ID_SET_LAYOUT  ||
        what == ID_SET_STATUS  ||
        what == ID_SET_TITLE   ||
        what == ID_SET_VOLUME  ||
        what == ID_SET_COVER) {
        return BOOL_YES;
    } else {
        return BOOL_NO;
    }
}

// 0/1
int isDataOld(ConnectInfo* peer, int subtype, const char* data, int size)
{
    if (!isDataCacheable(subtype)) {
        return 0;				// consider as new value
    }
    if (!peer->cache) {
        return 0;				// consider as new value
    }
    if (subtype >= ID_SET_MAX) {
        return 0;				// consider as new value
    }
    
    //DEBUG2("isDataOld(): %d -> %s", subtype, (data?data:"NULL"));
    
    DCache* cache = peer->cache;

    int isOld = 1;
    if (cache->lastValues[subtype] == NULL) {
        isOld = 0;		     	// consider as new value
    } else if (cache->lastValues[subtype] != NULL &&
               (cache->lastValuesSize[subtype] != size ||
                memcmp(cache->lastValues[subtype], data, cache->lastValuesSize[subtype]) != 0)) {

        free(cache->lastValues[subtype]);
        cache->lastValues[subtype]     = NULL;
        cache->lastValuesSize[subtype] = -1;

        isOld = 0;		     	// consider as new value
    }

    if (isOld == 0) {
        cache->lastValues[subtype] = (char*) calloc(size + 1, 1);
        memcpy(cache->lastValues[subtype], data, size);
        cache->lastValuesSize[subtype] = size;
    }
    return isOld;
}

//
// Default peer writer
//
static int writePeer(int fd, const char* buffer, int n)
{
    int nbytes = write(fd,buffer,n);
    if (nbytes < 0) {
        ERROR2("[DS]: writePeer() error %d",errno);
        errnoDebug("[DS]: writePeer() write ",errno);  // testing debug
    }
    return nbytes;
}

static int peerWrite(ConnectInfo* peer, const dMessage* msg)
{
    const char* command = msg->value;
    int count           = msg->size;

    if (!command || count <= 0) {
        return EXIT_OK;
    }

    if (strcmp("End();",command) == 0) {  // used only for WEB/CMXML
        return EXIT_OK;
    }
    
    if (msg->type == DM_SET) {
        if (isDataOld(peer, msg->subtype, command, count)) {
            INFO2("[DS]: Skip to send the same data to peer %d", peer->id);
            return EXIT_OK;
        }
    }

    int fd = _peerHandlers[peer->mode].descriptor(peer);
    if (fd < 0) {
        return EXIT_NOK;
    }

    return (writePeer(fd, command, count) > 0 ? EXIT_OK : EXIT_NOK);

}

//
// Sync peer after connect
//
static void syncPeer(ConnectInfo* peer)
{
    if (peer->mode == SERVER_TCP  ||
        peer->mode == SERVER_BT   ||
        peer->mode == SERVER_UX   ||
        peer->mode == CLIENT_NOAT 
        #ifdef USE_L2CAP
        || peer->mode == SERVER_L2CAP
        #endif
        ) {
        
        ERROR2("[DS]: syncPeer %d", peer->id);

        static struct {
           string_t* (*hooks[6]) (int) ; // CF,TX,LI,FM,WM,EF
        } _renderHooks[] = {
           //                  CF                  TX                  LI                  FM    WM                  EF 
           /* SERVER_TCP+  */{{renderCtrlForm,     renderTextForm,     renderListForm,     NULL, renderWmanForm,     renderEditForm}}
        };

         
        int f   = curForm() - 1;
        INFO2("[DS]: syncPeer form %d", f);
    
        if (_renderHooks[0].hooks[f]) {
            string_t* content = _renderHooks[0].hooks[f](peer->port);
	        if (content) {
                
                //char buf[256];
                //strncpy(buf,content->str,255);
                //buf[255] = '\0';
                //INFO2("[DS]: syncPeer sync content %s", buf);
                
                int fd = _peerHandlers[peer->mode].descriptor(peer);
                if (fd >= 0) {
                    INFO2("[DS]: syncPeer write to peer %d %d", f, fd);
                    writePeer(fd, content->str, content->len);
                }
	            stringFree(content, BOOL_YES);
	        }
        }
        
        if (curForm() == CF) {  // take care about cover 

            int fd = _peerHandlers[peer->mode].descriptor(peer);
            if (fd >= 0) {
                
                string_t* page = renderCtrlFormCover();
                writePeer(fd, page->str, page->len);
	            stringFree(page, BOOL_YES);

                const char* nc = cfNamedCover();
                const char* cv = cfCover();
                
                if (!nc && cv) {
                
                    string_t* page = stringNew("Set(cover,noname,");
                    stringAppend(page, cv);
                    stringAppend(page, ");");
               
                    eMessage* em = (eMessage*) malloc(sizeof(eMessage));
                    em->peer  = peer->id;
                    em->type  = EM_STRING;
                    em->value = strdup(page->str);
                    
                    stringFree(page, BOOL_YES);
                    
                    sendToExecutor(em);
                }
            }
        }
    }
}

//
// Default peer reader
//
int readPeer(int fd, char* buffer, int max)
{
    int nbytes = read(fd, buffer, max);
    if (nbytes < 0)  { // Read error
        //ERROR2("[DS]: readPeer() error %d",errno);
        errnoDebug("[DS]: readPeer() read ",errno);  // testing debug
    } else if (nbytes == 0) {
        DEBUG2("[DS]: readPeer() EOF");
    }

    //DEBUG2("[DS]: readPeer() got >%s< %d", buffer, nbytes);
    return nbytes;
}

//
// TCP peer reader with quirk for iViewer
//
static int readIVPeer(int fd, char* buffer, int max)
{
     //DEBUG2("[DS]: readIVPeer()");
    int nbytes = read(fd, buffer, max);
    
    if (nbytes < 0)  { // Read error
        ERROR2("[DS]: readIVPeer() error %d",errno);
        errnoDebug("[DS]: readIVPeer() read ",errno);  // testing debug
    } else if (nbytes == 0) {
        DEBUG2("[DS]: readIVPeer() EOF");
    } else {
        boolean_t heartbeat = BOOL_NO;
        char *hptr = buffer;
        //DEBUG2("[DS]: readIVPeer() got %s", buffer);
        
        //while ((hptr = strstr(hptr, "h=0"))) {
        if (strstr(hptr, "h=0")) {
            // do not erase it from read data, because it used as disconnect timer in cfg.files
            /*
            *hptr = '\r';
            hptr++;
            *hptr = '\r';
            hptr++;
            *hptr = '\r';
            */

            heartbeat = BOOL_YES;
        }

        hptr = buffer;
        while ((hptr = strchr(hptr, '\3'))) {  // end-of-text marker in CommandFusion
            // replace \3 separator to \r
            *hptr = '\r';
        }

        if (heartbeat) {
            DEBUG2("[DS]: readIVPeer() send iViever heartbeat");
            writeIViewerHeartbeat(fd);
        }
    }

    //DEBUG2("[DS]: readIVPeer() got >%s< %d", buffer, nbytes);
    return nbytes;
}

//
// CLIENT_NOAT peer reader
// in IR communication a lot of 'empty' chars (= -1) could be added
//
static int readIRPeer(int fd, char* buffer, int max)
{
    int nbytes = read(fd, buffer, max);
    if (nbytes < 0)  { // Read error
        ERROR2("[DS]: readIRPeer() error %d",errno);
        errnoDebug("[DS]: readIRPeer() read ",errno);  // testing debug
    } else if (nbytes == 0) {
        DEBUG2("[DS]: readIRPeer() EOF");
    } else {
        buffer[nbytes] = '\0';
        char * k2 = buffer;
        while (*k2 != '\0') {
            if (*k2 == -1) {
                *k2 = '\r';
            }
            k2++;
        }
    }

    //DEBUG2("[DS]: readIRPeer() got >%s< %d", buffer, nbytes);
    return nbytes;
}

//
// BT peer reader with quirk for Bemused
// must read VOLM command with an additional value byte
// SHFL/REPT _can_ follow with an additional byte - so don not bother about them
//
static int readBmPeer(int fd, char* buffer, int max)
{
    int nbytes = read(fd, buffer, max);
    if (nbytes < 0)  { // Read error
        ERROR2("[DS]: readBmPeer() error %d",errno);
        errnoDebug("[DS]: readBmPeer() read ",errno);  // testing debug
    } else if (nbytes == 0) {
        DEBUG2("[DS]: readBmPeer() EOF");
    } else {
        boolean_t heartbeat = BOOL_NO;
        char *hptr = NULL;
        while ((hptr = strstr(buffer, "CHCK"))) {
            // erase it from read data
            *hptr = '\r';
            hptr++;
            *hptr = '\r';
            hptr++;
            *hptr = '\r';
            hptr++;
            *hptr = '\r';

            heartbeat = BOOL_YES;
        }

        if (heartbeat) {
            DEBUG2("[DS]: readBmPeer() send Bemused heartbeat");
            writeBemusedHeartbeat(fd);
        }

        // hack to make it work correctly with Bemused clients
        char c = '\r';
        if (nbytes >=4 &&
                (strncmp((buffer+nbytes-4), "VOLM", 4) == 0 //|| // read only VOLM without value to set
                 /*strncmp((buffer+nbytes-4), "SHFL", 4) == 0 ||
                 strncmp((buffer+nbytes-4), "REPT", 4) == 0*/)) {

            int ret;
            if ((ret = bt_readchar(fd,&c,500000)) < 0) {
                DEBUG2("[DS]: readBmPeer: Bemused hack: read < 0");
            } else {
                sprintf(tmp, "[DS]: readBmPeer: Bemused hack: read >%c<", c);
                logger(L_DBG,tmp);
                buffer[nbytes] = c;
                nbytes++;
            }
        }

    }

    //DEBUG2("[DS]: readBmPeer() got >%s< %d", buffer, nbytes);
    return nbytes;
}

static int readPeersInternal(ConnectInfo* peer)
{
    //INFO2("[DS]: readPeersInternal peer %d", peer->id);
    char buf[MAXCMDLEN];    
    memset(buf, 0, MAXCMDLEN);

    int len = 0;
    //printf("read_command\n");

    if (peer->mode == SERVER_STDIN) {

        len = stdinRead(buf,MAXCMDLEN);  // returns EOF in case of error and EOF
        if (len > 0 && len != EOF) {
            DEBUG2("[DS]: read_command SERVER_STDIN %d %s\n", len, buf);
            parseCommand(peer->id, buf);
        }
    } else if (peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML) {

        len = checkWebPort(buf, MAXCMDLEN);  // returns EOF in case of error and EOF
        if (len > 0 && len != EOF) {
            DEBUG2("[DS]: read_command WEB/CMXML %d %s\n", len, buf);
            parseCommand(peer->id, buf);
         }

    } else {
        
        /* use select() for others
        int fd = _peerHandlers[peer->mode].descriptor(peer);
        if (peer->state == PEER_CONNECTED && fd >= 0) {
        
            char* p = buf;

            while (len < MAXCMDLEN) {
                char c;
                int ch = bt_readchar(fd,&c,100);
                
                INFO2("[DS]: readPeersInternal read from peer with mode %d got %d", peer->mode, ch);
                
                if (ch == EOF) {
                    INFO2("[DS]: readPeersInternal EOF peer with mode %d", peer->mode);
                    buf[0] = 0;
                    peer->state = PEER_DISCONNECTED;
                    break;
                } else if (ch == EOF-1) {
                    break;
                } else if (ch >= 0 && (c == '\r' || c == ';')) {
                    break;
                } else  if (ch >= 0 && c != '\r' && c != ';') {
                    *p++ = c;
                    len++;
                }
            }

            buf[len] = '\0';
            stringAppend(buffer,"\r");  // separator
            stringAppend(buffer,buf);
        }
        */
        
    }
    return len;
}

static int doReadPeer(ConnectInfo* peer, int fd)
{
    char buf[MAXCMDLEN];
    memset(buf, 0, MAXCMDLEN);
    
    //DEBUG2("[DS]: doReadPeer() ready to read from fd %d (peer %d)", fd, peer->id);

    int rc = 0;
    
    if (peer->mode == SERVER_TCP && getIViewer()) {
        rc = readIVPeer(fd, buf, MAXCMDLEN);
    } else if (peer->mode == SERVER_BT && getBemused()) {
        rc = readBmPeer(fd, buf, MAXCMDLEN);
    } else if (peer->mode == CLIENT_NOAT) {
        rc = readIRPeer(fd, buf, MAXCMDLEN);
    } else if (peer->mode == CLIENT_RFCOMM || peer->mode == CLIENT_AT) {
        rc = atRead(peer, buf, MAXCMDLEN);
    } else if (peer->mode == FRONT_END) {
        return feRead(fd);
    } else {
        rc = readPeer(fd, buf, MAXCMDLEN);
    }

    if (rc <= 0) {  // EOF or error
        DEBUG2("[DS]: doReadPeer() EOF or error fd %d (peer %d), reset connection", fd, peer->id);
        _peerHandlers[peer->mode].resetConnection(peer);
        return EOF;
    }
    
    parseCommand(peer->id, buf);
    return 1;
}

int processPeers()
{
    //DEBUG2("[DS]: processPeers()");
    
    fd_set read_fds;
    FD_ZERO(&read_fds);

    struct timeval tv;
    tv.tv_sec  = 0;
    tv.tv_usec = 100;

    int max_fd = -1;
    int readCnt = 0;

    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;
	
	//DEBUG2("[DS]: processPeers() peer %d state %s", peer->mode, 
	//                 (peer->state == PEER_CONNECTED ? "CONNECTED" : 
        //                  (peer->state == PEER_WAIT_LISTEN ? "WAIT_LISTEN" : "WAIT_ACCEPT")));

        if ((peer->state == PEER_CONNECTED && 
             !(peer->mode == SERVER_STDIN || peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML)) ||
            peer->state == PEER_WAIT_LISTEN || 
            peer->state == PEER_WAIT_ACCEPT) {

            int fd = _peerHandlers[peer->mode].descriptor(peer);
            //DEBUG2("[DS]: processPeers() peer %d connected %s state (fd=%d)", peer->id, 
            //             (peer->state == PEER_CONNECTED ? "CONNECTED" : 
            //              (peer->state == PEER_WAIT_LISTEN ? "WAIT_LISTEN" : "WAIT_ACCEPT")), fd);
                         
            if (fd >= 0) {
                FD_SET(fd, &read_fds);
            //} else {  web, xml can have that 
            //    DEBUG2("[DS]: processPeers() peer with mode %d improper descriptor", peer->mode); 
            }
            if (max_fd < fd) {
                max_fd = fd;
            }
        }
        list = listSingleNext(list);
    }

    if (max_fd >= 0) {  // have some peers

        int rc = select(max_fd + 1, &read_fds, NULL, NULL, &tv);
        
        //if (rc != 0) {
        //    DEBUG2("[DS]: processPeers() select()=%d, maxfd=%d",rc,max_fd);
        //}
        
        if (rc == 0) {          // timeout
            //DEBUG2("[DS]: processPeers() no data from select()");
        } else if (rc < 0) {    // some error
            //DEBUG2("[DS]: processPeers() error %d", errno);
            errnoDebug("[DS]: processPeers() select ",errno);  // testing debug
        } else {
            //DEBUG2("[DS]: --------------------------------------------");
            //DEBUG2("[DS]: processPeers() got data from select()");
            
            list = _connections;
            while (list) {
             
                ConnectInfo* peer = (ConnectInfo*) list->data;
                
                int fd = _peerHandlers[peer->mode].descriptor(peer);

                if (fd >= 0) {
                
                    //DEBUG2("[DS]: processPeers() %d descriptor on peer %d", fd, peer->id);

                    if (FD_ISSET(fd, &read_fds)) {
		    
                        //DEBUG2("[DS]: processPeers() FD_ISSET %d descriptor", fd);

                        if (peer->state == PEER_CONNECTED) {
                            
                            //DEBUG2("[DS]: processPeers() peer %d readable", peer->id);
                            int rr = doReadPeer(peer, fd);
                            if (rr == EOF) {
                                
                                freeCachedData(peer);
                                
                                int cnum = countConnections(); 
                                DEBUG2("[DS]: processPeers() countConnections %d", cnum);

                                // no more connections
                                if (cnum == 0) { 
                                     DEBUG2("[DS]: processPeers() disconnect notify");
                                     return EOF;
                                }
                            } else if (rr > 0) {
                                readCnt++;
                            }
                            
                        } else if (peer->state == PEER_WAIT_LISTEN) {
                            
                            DEBUG2("[DS]: processPeers() peer %d listenable", peer->id);
                            if (_peerHandlers[peer->mode].listenConnection(peer) < 0) {
                                DEBUG2("[DS]: readPeers() fails to listen from peer %d (fd=%d)", peer->id, fd);
                            }

                        } else if (peer->state == PEER_WAIT_ACCEPT) {

                            //logger(L_DBG,"[DS]: ************ new connection **********");
                            DEBUG2("[DS]: processPeers() accept connection from peer %d (fd=%d)", peer->id, fd);
                            if (_peerHandlers[peer->mode].acceptConnection(peer) == EXIT_OK) {
                                
                                int cnum = countOtherConnections(peer); 
                                DEBUG2("[DS]: processPeers() countOtherConnections %d", cnum);
  
                                // do this only on first connect
                                if (cnum == 0) {
                                    DEBUG2("[DS]: processPeers() connectNotify");
                                    connectNotify(peer->id);
                                } else {
                                    freeCachedData(peer);
                                    if (needStoreState()) {
                                        syncPeer(peer);
                                    }
                                }
                                
                                // too late, because syncPeer() was already done
                                //if (peer->state == PEER_CONNECTED) {
                                //    _peerHandlers[peer->mode].setupPost(peer);
                                //}
                           }
                        }
                    //} else {
                    //    DEBUG2("[DS]: processPeers() no FD_ISSET on peer %d", peer->id);
                    }
                //} else {
                //   DEBUG2("[DS]: processPeers() no descriptor on peer %d", peer->id);
                }
                list = listSingleNext(list);
            }
        }
    //} else {
    //    DEBUG2("[DS]: processPeers() no peers to read");
    }

    //DEBUG2("[DS]: processPeers() special cases");
    // special cases
    list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;
        if (peer->state == PEER_CONNECTED || peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML) {
            int rc = readPeersInternal(peer);
            if (rc < 0 || rc == EOF) {  // EOF or error
                DEBUG2("[DS]: processPeers() EOF or error special case (peer %d), close connection", peer->id);
                _peerHandlers[peer->mode].closeConnection(peer, 0);
                return EOF;
            } else if (rc > 0){
                readCnt++;
            }
        // Only stdin, web and cmxml goes here. None of them can have state PEER_WAIT_ACCEPT
        //} else if (peer->state == PEER_WAIT_ACCEPT) {
        //    //DEBUG2("[DS]: processPeers() TODO aa1");
        }
        list = listSingleNext(list);
    }

    //DEBUG2("[DS]: processPeers() return %d", readCnt);
    return readCnt;
}

int writePeers(dMessage* dm)
{
    INFO2("[DS]: writePeers");
    int ret = EXIT_NOK;
    
    if (needStoreState()) {
        updateState(dm);
    }

    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;

        if (dm->peer == PEER_ANY || peer->id == dm->peer) {
            if (peer->state == PEER_CONNECTED || 
                peer->mode  == SERVER_WEB     || 
                peer->mode  == SERVER_CMXML) {

                INFO2("[DS]: write to peer %d (%d)", peer->id, dm->peer);

                int ret1 = (_peerHandlers[peer->mode].writeConnection == NULL ?
                            EXIT_NOK : _peerHandlers[peer->mode].writeConnection(peer, dm));

                if (ret1 != EXIT_NOK) {
                    ret = ret1;
                }
            }
        }
        list = listSingleNext(list);
    }

    //INFO2("[DS]: write result %s", (ret == EXIT_NOK? "NOK": "OK"));
    return ret;
}

static int writeByteInternal(int fd, int byte)
{
    unsigned char byte2write[2];
    byte2write[0] = (unsigned char) byte;
    byte2write[1] = '\0';

    if (write(fd, byte2write, 1) < 0) {
        logger(L_ERR, "error writing bytes");
        return EXIT_NOK;
    }
    return EXIT_OK;
}

static int peerWriteBytes(ConnectInfo* peer, const char* command)
{
    int fd = _peerHandlers[peer->mode].descriptor(peer);
    if (fd < 0) {
        logger(L_DBG,"[DS]: peerWriteBytes() no connection data");
        return EXIT_NOK;
    }

    // send command
    if (fd >= 0 && command && command[0]) {

        char byteStr[MAXCKPDLEN];
        memset(byteStr,0,MAXCKPDLEN);

        strncpy(byteStr,command,MAXCKPDLEN-1);

        DEBUG2("[DS]: peerWriteBytes >%s<", byteStr); 

        char* bStr = strtok(byteStr,",");
        while (bStr != NULL) {

        //DEBUG2("[DS]: Next byte is >%s<", bStr); 

            char bStripped[4];

            while (*bStr == ' ') {
                bStr++;
            }
            int i = 0;
            while (*bStr != ' ' && i < 3) {  // 0 < ... < 256
                bStripped[i] = *bStr;
                bStr++;
                i++;
            }
            bStripped[i] = '\0';

            //DEBUG2("[DS]: Next byte is >%s<", bStripped); 

            if (writeByteInternal(fd, atoi(bStripped)) != EXIT_OK) {
                logger(L_DBG,"[DS]: Fails in peerWriteBytes()");
                return EXIT_NOK;
            }

            bStr = strtok(NULL,",");
        }
    }
    //logger(L_DBG, "peerWriteBytes EXIT");
    return EXIT_OK;
}

int writeBytesPeers(char* command)
{
    int ret = EXIT_NOK;
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* peer = (ConnectInfo*) list->data;
        if (peer && 
            peer->state == PEER_CONNECTED &&
            _peerHandlers[peer->mode].writeBytesConnection != NULL) {

            INFO2("[DS]: write bytes to peer %d", peer->mode);
            int ret1 = _peerHandlers[peer->mode].writeBytesConnection(peer, command);

            if (ret1 != EXIT_NOK) {
                ret = ret1;
            }
        }
        list = listSingleNext(list);
    }
    return ret;
}

//
// In case of WEB/CMXML it is enougn to send name of file, not full content
//
int writeFilePeers(dMessage* dm)
{
    INFO2("[DS]: DM_SETFILE %d %s %s %s", dm->peer, (char*) dm->value, dm->file, dm->scaled);
    
    if (needStoreState()) {
        updateState(dm);
    }

    int  size = 0;
    char* buf = NULL;
    dMessage* dm2 = NULL;
    boolean_t evaluated = BOOL_NO;

    SingleList* list = _connections;
    while (list) {

        ConnectInfo* peer = (ConnectInfo*) list->data;
        
        if (dm->peer == PEER_ANY || dm->peer ==  peer->id) {
            if (peer->state == PEER_CONNECTED || peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML) {
                INFO2("[DS]: write file to peer %d", peer->id);

                if (peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML) {

                    // nothing, state stored by updateState(...) above

                } else {

                    if (evaluated == BOOL_NO) {

                        evaluated = BOOL_YES;

                        // read data from file
                        buf = readFromFile(dm->value, dm->scaled, &size);

                        INFO2("[DS]: writeFilePeers got from file %s %d bytes", dm->scaled, size);

                        if (buf && size > 0) {

                            dm2 = allocDMessage();
                            dm2->type    = DM_SET;
                            dm2->subtype = dm->subtype;
                            dm2->value   = buf;
                            dm2->size    = size;
                        }
                    }

                    if (dm2) {
                        if (_peerHandlers[peer->mode].writeConnection != NULL) {
                            _peerHandlers[peer->mode].writeConnection(peer, dm2);
                        }
                    }
                }
            }
        }
        list = listSingleNext(list);
    }
    freeDMessage(dm2);
    return EXIT_OK;
}

int writeCKPD(dMessage* dm)
{
    //logger(L_DBG, "[DS]: Send CKPD");
    SingleList* list = _connections;
    while (list) {

        ConnectInfo* peer = (ConnectInfo*) list->data;
        
        if (dm->peer == PEER_ANY || dm->peer == peer->id) {
            if (peer->state == PEER_CONNECTED &&
                (peer->mode == CLIENT_RFCOMM || peer->mode == CLIENT_AT)) {

                int fd = _peerHandlers[peer->mode].descriptor(peer);
                if (fd >= 0) {
                    logger(L_DBG, "[DS]: Send CKPD");
                    sendSeq(fd, (char*) dm->value);
                }
            }
        }
        list = listSingleNext(list);
    }
    return EXIT_OK;
}

/*int writeCMER(dMessage* dm)
{
    logger(L_DBG, "[DS]: Send CMER");

    SingleList* list = _connections;
    while (list) {

        ConnectInfo* peer = (ConnectInfo*) list->data;

        if (peer->state == PEER_CONNECTED &&
            (peer->mode == CLIENT_RFCOMM || peer->mode == CLIENT_AT)) {

            int fd = _peerHandlers[peer->mode].descriptor(peer);
            if (fd >= 0) {
                sendCMER(fd, dm->size);
            }
        }
        list = listSingleNext(list);
    }
    return EXIT_OK;
}*/

//
// returns EXIT_OK if at least one connection exists
//
int connected()
{
    SingleList* list = _connections;
    while (list) {

        ConnectInfo* peer = (ConnectInfo*) list->data;
        if (peer->state == PEER_CONNECTED && peer->mode != FRONT_END) {
            return EXIT_OK;
        /*} else if (peer->mode == SERVER_WEB || peer->mode == SERVER_CMXML) {  // connection-less, check clients num
            _WebConnection* cn = (_WebConnection*) peer->connectionData;
            if (cn) {
                if (haveClients(cn)) {
                    return EXIT_OK;
                }
            }*/
        }
        
        list = listSingleNext(list);
    }
    return EXIT_NOK;
}

//
// Send heartbeat message to all TCP connection.
// Suppose we have only one connection to iViewer
//
static int socketWriteByte(int fd, int byte)
{
    unsigned char byte2write[2];
    byte2write[0] = (unsigned char) byte;
    byte2write[1] = '\0';

    if (write(fd, byte2write, 1) < 0) {
        logger(L_ERR, "error writing byte to socket");
        return EXIT_NOK;
    }
    return EXIT_OK;
}

static void writeIViewerHeartbeat(int fd)
{
    // reply message is h=1\03
    socketWriteByte(fd, 104); // h
    socketWriteByte(fd, 61);  // =
    socketWriteByte(fd, 49);  // 1
    socketWriteByte(fd, 3);   // \03
}

static void writeBemusedHeartbeat(int fd)
{
    // reply message to "CHCK" is "Y"
    writePeer(fd, "Y", 1);
}

void writeHeartbeat(ConnectInfo* peer)
{
    int fd = _peerHandlers[peer->mode].descriptor(peer);
    if (fd >= 0) {
        writeIViewerHeartbeat(fd);
    }
}

void sendIViewerHeartbeat(void)
{
    SingleList* list = _connections;
    while (list) {

        ConnectInfo* peer = (ConnectInfo*) list->data;
        if (peer->state == PEER_CONNECTED && 
            peer->mode == SERVER_TCP) {  // iViewer connection can be TCP only
            
            writeHeartbeat(peer);
        }
        list = listSingleNext(list);
    }
}

//
// returns EXIT_OK if there are only client peers and all peers unconnected
//
int needExit()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->state == PEER_CONNECTED) {
            return EXIT_NOK;
        }

        if (!(cn->mode == CLIENT_RFCOMM ||
              cn->mode == CLIENT_AT     ||
              cn->mode == CLIENT_ILIRC)) {
            return EXIT_NOK;
        }
        list = listSingleNext(list);
    }
    return EXIT_OK;
}

//
// returns EXIT_OK if there are web/cmxml peer exists
//
int needFinalizer()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == SERVER_WEB || cn->mode == SERVER_CMXML) {
            return EXIT_OK;
        }
        list = listSingleNext(list);
    }
    return EXIT_NOK;
}

//
// returns EXIT_OK if there are AT peer exists
//
// TODO: mutex ?
int needAtMainMenuReturn(int peerid)
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;
        if (cn->id == peerid && (cn->mode == CLIENT_RFCOMM || cn->mode == CLIENT_AT)) {
            return EXIT_OK;
        }
        list = listSingleNext(list);
    }
    return EXIT_NOK;
}

//
// returns EXIT_OK if there are server-mode peer exists
//
int isServerMode()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == SERVER_BT    ||
            cn->mode == SERVER_TCP   ||
            cn->mode == SERVER_WEB   ||
            cn->mode == SERVER_CMXML ||
            cn->mode == SERVER_UX    ||
            cn->mode == CLIENT_NOAT  
            #ifdef USE_L2CAP
            || cn->mode == SERVER_L2CAP
            #endif
            ) {
            return EXIT_OK;
        }
        list = listSingleNext(list);
    }
    return EXIT_NOK;
}

//
// returns EXIT_OK if there are server-mode (no WEB/CMXML) peer exists
//
/*int isServerModeNoWeb()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == SERVER_BT    ||
            cn->mode == SERVER_TCP   ||
            cn->mode == SERVER_UX    ||
            cn->mode == CLIENT_NOAT  
            #ifdef USE_L2CAP
            || cn->mode == SERVER_L2CAP
            #endif
            ) {
            return EXIT_OK;
        }
        list = listSingleNext(list);
    }
    return EXIT_NOK;
}

int isWebServer()
{
    return needFinalizer();
}

//
// returns EXIT_OK if there are at-mode peer exists
//
int isAtMode()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == CLIENT_RFCOMM ||
            cn->mode == CLIENT_AT     ||
            cn->mode == CLIENT_ILIRC) {
            return EXIT_OK;
        }
        list = listSingleNext(list);
    }
    return EXIT_NOK;
}*/

//
// returns EXIT_OK if there are CLIENT_RFCOMM/CLIENT_AT peer exists
//
int isAtModeDuplex()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == CLIENT_RFCOMM ||
            cn->mode == CLIENT_AT) {
            return EXIT_OK;
        }
        list = listSingleNext(list);
    }
    return EXIT_NOK;
}

//
// In iViewer mode returns TCP port (search for the first TCP connection)
//
int getIViewerTcpPort(void)
{
    int port = -1;
    
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == SERVER_TCP && cn->port >= 0) {  // SERVER_TCP handle also Unix (file) sockets
            return cn->port;
        }
        list = listSingleNext(list);
    }
    
    return port;
}

boolean_t checkActiveCall()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == CLIENT_RFCOMM) {
            if (rfcommCheckActiveCall(cn)) {
                return BOOL_YES;
            }
        } else if (cn->mode == CLIENT_AT) { 
            if (serialCheckActiveCall(cn)) {
                return BOOL_YES;
            }
        }
        list = listSingleNext(list);
    }
    return BOOL_NO;
}

boolean_t hasActiveCall()
{
    SingleList* list = _connections;
    while (list) {
        ConnectInfo* cn = (ConnectInfo*) list->data;

        if (cn->mode == CLIENT_RFCOMM) {
            if (rfcommHasActiveCall(cn)) {
                return BOOL_YES;
            }
        } else if (cn->mode == CLIENT_AT) { 
            if (serialHasActiveCall(cn)) {
                return BOOL_YES;
            }
        }
        list = listSingleNext(list);
    }
    return BOOL_NO;
}

void getClientSize(int peer, int fd)
{
    DEBUG2("[DS]: Detect cover size for peer %d", peer);

    int n = write(fd,CMD_STR_GETCOVERSZ,16);
    if (n < 0) {
    	logger(L_ERR, "[DS]: Error on detect cover size (w)");
    	return;
    }
    
    char buf[MAXCMDLEN];
    memset(buf, 0, MAXCMDLEN);
    
    n = readPeer(fd, buf, MAXCMDLEN);
    if (n <= 0) {  // EOF or error
        logger(L_ERR, "[DS]: Error on detect cover size (r)");
        return;
    }
    buf[n] = '\0';
    
    // Msg:CoverSize(xxx,);
    DEBUG2("[DS]: Cover size reply for peer %d is %s", peer, buf);
    char* pos = strstr(buf,"CoverSize(");
    if (!pos) {
        logger(L_ERR, "[DS]: Error on detect cover size (p)");
    }
    pos += 10; // "CoverSize("
    char *p2 = pos;
    while (isdigit(*p2)) {
       p2++;
    }
    *p2 = '\0';

    int sz = atoi(pos);
    DEBUG2("[DS]: Cover size for peer %d is %d", peer, sz);

    DEBUG2("[DS]: Detect screen size for peer %d", peer);

    n = write(fd,CMD_STR_GETSCREENSZ,17);
    if (n < 0) {
    	logger(L_ERR, "[DS]: Error on detect screen size (w)");
    	return;
    }

    n = readPeer(fd, buf, MAXCMDLEN);
    if (n <= 0) {  // EOF or error
        logger(L_ERR, "[DS]: Error on detect screen size (r)");
        return;
    }
    buf[n] = '\0';
    
    // Msg:SizeX(xxx,);SizeY(xxx);
    DEBUG2("[DS]: Cover size reply for peer %d is %s", peer, buf);
    pos = strstr(buf,"SizeX(");
    if (!pos) {
        logger(L_ERR, "[DS]: Error on detect screen X size");
        return;
    }
    pos += 6; // "SizeX("
    p2 = pos;
    while (isdigit(*p2)) {
       p2++;
    }
    *p2 = '\0';

    int xsz = atoi(pos);
    DEBUG2("[DS]: X screen size for peer %d is %d", peer, xsz);
    

    pos = strstr(buf,"SizeY(");
    if (!pos) {
        logger(L_ERR, "[DS]: Error on detect screen Y size (attempt 1)");    
        // try again
        n = readPeer(fd, buf, MAXCMDLEN);
        if (n <= 0) {  // EOF or error
            logger(L_ERR, "[DS]: Error on detect screen size (r)");
            return;
        }
        buf[n] = '\0';
        
        pos = strstr(buf,"SizeY(");
        if (!pos) { 
            logger(L_ERR, "[DS]: Error on detect screen Y size");
            return;
        }
    }
    pos += 6; // "SizeY("
    p2 = pos;
    while (isdigit(*p2)) {
       p2++;
    }
    *p2 = '\0';

    int ysz = atoi(pos);
    DEBUG2("[DS]: Y screen size for peer %d is %d", peer, ysz);

    customizePeer(peer, xsz, ysz, sz);
}
