//
// 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 "str.h"
#include "utils.h"
#include "conf.h"
#include "sys_util.h"
#include "state.h"
#include "gen_xml.h"
#include "mutex.h"
#include "var.h"

// not to include pr_web.h
extern void addLink(string_t* ip, string_t* addTo, int port, boolean_t trailSlash);

#define CMXML_MAX_STRING_SIZE 32

#define XML_HEAD1  "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"

#define XML_CFORM1  "<CiscoIPPhoneMenu>\n"
#define XML_CFORM99 "</CiscoIPPhoneMenu>\n"
#define XML_CFORM1G  "<CiscoIPPhoneGraphicFileMenu>\n"
#define XML_CFORM99G "</CiscoIPPhoneGraphicFileMenu>\n"

/*
x-CiscoIPPhoneModelName: CP-7941G
x-CiscoIPPhoneSDKVersion: 7.0.1
x-CiscoIPPhoneDisplay: 298,144,3,G

Cisco Unified IP Phone  7905      192 x 53

Cisco Unified IP Phone  7920      128 x 59 Grayscale 1
Cisco Unified IP Phones 7940G
                        7960G     133 x 65 Grayscale 2
Cisco Unified IP Phones 7941G,
                        7941G-GE,
                        7961G,
			            7961G-GE  298 x 144 Grayscale 4

Cisco Unified IP Phone  7970G
                        7971G     298 x 168 Color     12
*/

#define XML_LIST1  "<CiscoIPPhoneMenu>\n"
#define XML_LIST99 "</CiscoIPPhoneMenu>\n"

#define XML_TEXT1  "<CiscoIPPhoneText>\n"
#define XML_TEXT99 "</CiscoIPPhoneText>\n"

#define XML_EFORM1  "<CiscoIPPhoneInput>\n"
#define XML_EFORM99 "</CiscoIPPhoneInput>\n"

#define XML_WMAN1  "<CiscoIPPhoneImageFile>\n"
#define XML_WMAN99 "</CiscoIPPhoneImageFile>\n"

#define XML_MAX_LIST_SIZE 99

extern char tmp[MAXMAXLEN];

static int       _xmlScreenW = 298;    // Cisco-7941 as default
static int       _xmlScreenH = 144;    //
static int       _xmlLayoutW = 298;    //
static int       _xmlLayoutH = 144;    //
static boolean_t _xmlScreenG  = BOOL_YES;
static boolean_t _xmlLayoutOk = BOOL_NO;
static boolean_t _xmlGraphic  = BOOL_YES;


int xmlScreenWidth()
{
    return _xmlScreenW;
}

int xmlScreenHeight()
{
    return _xmlScreenH;
}

boolean_t xmlScreenGrayscale()
{
    return _xmlScreenG;
}

void xmlSetLayoutOk(boolean_t ok)
{
    _xmlLayoutOk = ok;
}


void parseScreenDef(char* buffer)
{
    // x-CiscoIPPhoneDisplay: 298,144,3,G
    //DEBUG2("[WS]:parseScreenDef  line: %s", buffer);

    int gap = strlen("x-CiscoIPPhoneDisplay: ");
    if (!buffer || strlen(buffer) < gap) {
        return;
    }

    char* start = buffer+gap;
    char* scrDef = strdup(start);

    char* w = strtok(scrDef, ",");
    char* h = strtok(NULL,   ",");
    /*char* d = */
    strtok(NULL,   ",");
    char* g = strtok(NULL,   ",");

    int sW      = atoi(w);
    int sH      = atoi(h);

    //printf("parseScreenDef w=%d h=%d g=%s\n", sW, sH, g);

    boolean_t sG = (g && g[0] == 'G');  // Grayscale

    if (_xmlLayoutOk &&
            (_xmlScreenW != sW ||
             _xmlScreenH != sH ||
             _xmlScreenG != sG)) {

        _xmlLayoutOk = BOOL_NO; // need to regenerate layout image
    }

    _xmlScreenW = sW;
    _xmlScreenH = sH;
    _xmlScreenG = sG;
    
    free(scrDef);
}

void parseCiscoModel(char* buffer)
{
    // x-CiscoIPPhoneModelName: CP-7905
    //DEBUG2("[WS]:parseCiscoModel line: %s", buffer);

    int gap = strlen("x-CiscoIPPhoneModelName: ");
    if (!buffer || strlen(buffer) < gap) {
        return;
    }
    
    // http://www.cisco.com/c/en/us/td/docs/voice_ip_comm/cuipph/all_models/xsi/8_5_1/xsi_dev_guide/xmlobjects.html
    char* start = buffer+gap;
    if (strstr(start,"7905") ||  
        strstr(start,"7906") ||
        strstr(start,"7911") ||
        strstr(start,"7912") ||
        strstr(start,"7931") ||
        strstr(start,"7940") ||
        strstr(start,"7960")) {
    
        _xmlGraphic = BOOL_NO;
    }
}

static void renderConvertCmd(string_t* cmd)
{
    stringAppend(cmd, " -resize ");

    char num[16];
    sprintf(num,"%d",xmlScreenWidth() - 4);
    stringAppend(cmd, num);

    stringAppend(cmd, "x");

    sprintf(num,"%d",xmlScreenHeight() - 4);
    stringAppend(cmd, num);

    stringAppend(cmd, " -colorspace ");
    if (xmlScreenGrayscale()) {
        stringAppend(cmd, " Gray ");
    } else {
        stringAppend(cmd, " RGB -depth 12 ");
    }
}

static void addXMLFormMenu(int formId, string_t* ip, string_t* page, int p)
{
    SingleList* list = userMenu();

    INFO2("[WS]: addXMLFormMenu %d #%d", formId, listSingleLength(list));
    switch (formId) {

    case CF:
    case TX:
    case WM: {
        int llen = listSingleLength(list);

        if (llen > 0) {


            boolean_t longMenu = (llen > XML_SOFTKEY_NUM ? BOOL_YES : BOOL_NO);

            int position = 1;

            while(list) {

                if (longMenu && position == XML_SOFTKEY_NUM) {

                    stringAppend(page, "  <SoftKeyItem>\n    <Name>>></Name>\n    <URL>");
                    addLink(ip, page, p, BOOL_YES);
                    stringAppend(page, XML_LONG_MENU);

                    char buf[8];
                    sprintf(buf,"%d", formId);
                    stringAppend(page, buf);

                    sprintf(buf,"%d", position);
                    stringAppend(page, "</URL>\n    <Position>");
                    stringAppend(page, buf);
                    stringAppend(page, "</Position>\n  </SoftKeyItem>\n");

                    break;
                }

                if (list->data && ((string_t*) list->data)->str) {

                    INFO2("[WS]: addXMLFormMenu item %s",((string_t*) list->data)->str);

                    stringAppend(page, "  <SoftKeyItem>\n    <Name>");
                    stringAppend(page, ((string_t*) list->data)->str);
                    stringAppend(page, "</Name>\n    <URL>");

                    addLink(ip, page, p, BOOL_YES);

                    char buf[8];
                    sprintf(buf,"%d", position);

                    stringAppend(page, XML_SHORT_MENU);
                    stringAppend(page, buf);

                    stringAppend(page, "</URL>\n    <Position>");
                    stringAppend(page, buf);
                    stringAppend(page, "</Position>\n  </SoftKeyItem>\n");
                }
                list = listSingleNext(list);

                position++;
                if (!longMenu && position > XML_SOFTKEY_NUM) {  // should not happen
                    break;
                }
            }
        }
        break;
    }


    //case LI:  -- sendXMLMenu used instead
    /*case EF: -- use custom code

            if (listSingleLength(list) > 0) {

            int position = 1;

    	while(list) {

    	    if (list->data && ((string_t*) list->data)->str) {

    		stringAppend(page, "  <SoftKeyItem>\n    <Name>");
    		stringAppend(page, ((string_t*) list->data)->str);
    		stringAppend(page, "</Name>\n    <URL>");

    		addLink(ip, page, port, BOOL_YES);

    		char buf[8];
    		sprintf(buf,"%d", position);

    		stringAppend(page, XML_SHORT_MENU);
    		stringAppend(page, buf);

    		stringAppend(page, "</URL>    <Position>");
    		stringAppend(page, buf);

    		stringAppend(page, "</Position>\n  </SoftKeyItem>\n");
                    }
    	    list = listSingleNext(list);

    	    position++;
    	    if (position > XML_SOFTKEY_NUM) {
    	        break;
    	    }
                }
        }
        break;*/
    }
}

string_t* renderXMLFormHead(int form, const char* caption)
{
    string_t* head = stringNew(XML_HEAD1);

    if (form == CF) {
        if (_xmlGraphic == BOOL_YES) {
            stringAppend(head,XML_CFORM1G);
        } else {
            stringAppend(head,XML_CFORM1);
        }
    } else if (form == LI) {
        stringAppend(head,XML_LIST1);
    } else if (form == TX) {
        stringAppend(head,XML_TEXT1);
    } else if (form == EF) {
        stringAppend(head,XML_EFORM1);
    } else if (form == WM) {
        if (_xmlGraphic == BOOL_YES) {
            stringAppend(head,XML_WMAN1);
        } else {
            stringAppend(head,XML_CFORM1);
        }
        
    }

    stringAppend(head, "  <Title>");
    stringAppend(head, caption);
    stringAppend(head, "</Title>\n");
    stringAppend(head, "  <Prompt>");
    
    if (form == CF) {
	    const char* title = cfTitle();
	    int sz = (title ? strlen(title) : -1);
	    if (sz > 0) {
	        if (sz > CMXML_MAX_STRING_SIZE) { 
                stringAppendLen(head, cfTitle(), CMXML_MAX_STRING_SIZE);
	        } else {
                stringAppend(head, title);
	        }
	    }
    } else {
        if (strlen(caption) > CMXML_MAX_STRING_SIZE) {
            stringAppendLen(head, caption, CMXML_MAX_STRING_SIZE);
	    } else {
	        stringAppend(head, caption);
	    }
    }
    stringAppend(head, "</Prompt>\n");

    //printf("HEAD\n%s\n",head->str);
    return head;
}

// Send menu as list (CMXML)
string_t* sendXMLMenu(int form, string_t* ip, int p, int idx)
{
    string_t* page = renderXMLFormHead(LI,"Menu");  // menu-as-list

    SingleList* list = userMenu();

    INFO2("[WS]: sendXMLMenu %d #%d", form, listSingleLength(list));
    if (listSingleLength(list) > 0) {

        int position = 1;

        while(list) {
            if (list->data && ((string_t*) list->data)->str) {

                stringAppend(page, "  <MenuItem>\n   <Name>");

                if (((string_t*) list->data)->len > CMXML_MAX_STRING_SIZE) {
                    stringAppendLen(page, ((string_t*) list->data)->str, CMXML_MAX_STRING_SIZE);
                } else {
                    stringAppend(page, ((string_t*) list->data)->str);
                }
                stringAppend(page, "</Name>\n   <URL>");

                addLink(ip, page, p, BOOL_YES);

                char pos[8];
                sprintf(pos, "%d",position);

                if (idx >= 0) {
                    stringAppend(page, XML_LIST_MENU2);
                    char buf[8];
                    sprintf(buf, "%d,",idx);
                    stringAppend(page, buf);
                } else {
                    stringAppend(page, XML_SHORT_MENU);
                }

                stringAppend(page, pos);
                stringAppend(page, "</URL>\n  </MenuItem>\n");
            }
            list = listSingleNext(list);
            position++;
        }
    }

    stringAppend(page, "  <SoftKeyItem>\n    <Name>Select</Name>\n    <URL>SoftKey:Select</URL>\n    <Position>1</Position>\n  </SoftKeyItem>\n");
    stringAppend(page, "  <SoftKeyItem>\n    <Name>Back</Name>\n    <URL>SoftKey:Back</URL>\n    <Position>2</Position>\n  </SoftKeyItem>\n");

    stringAppend(page, XML_LIST99);
    
    return page;
}

static void renderXMLLayout()
{
    mutexLock(M_STATE);
    
    const char* cv = cfCover();
    const char* nc = cfNamedCover();
   
    INFO2("[WS]: renderXMLLayout (%d,%d) pix, %s, %s", _xmlScreenW, _xmlScreenH,
          (_xmlScreenG ? "Grayscale" : "Color"),
          ((cv || nc) ? "with cover" : "no cover"));

    // Use ImageMagic to create picture to show

    char* confDir = dupVarValue(VAR_CFGDIR);
    string_t* iconpath = stringNew(confDir ? confDir : ".");
    free(confDir);
    
    stringAppend(iconpath,"/Icons/32/");

    string_t* cmd = NULL;

    if (bottomlineSkin()) {

        int iNum = 1;
        while (iNum <= 7) {

            const char* icon = cfIcon(iNum-1);
	    
            if (!icon || strcmp(icon,"none") == 0 || iNum == 7) {
                break; // no more icons
            }
            iNum++;
        }

        cmd = stringNew("montage -background transparent -tile ");

        char num[32];
        sprintf(num,"%dx1 -geometry 32x32 ", iNum);
        stringAppend(cmd, num);

        //stringAppend(cmd, "-draw \'text 0,0 \"");
        //stringAppend(cmd, cfTitle());
        //stringAppend(cmd, "\";gravity Center\' ");

        int i;
        for (i=0; i<iNum; i++) {
	
	        const char* icon = cfIcon(i);
	        if (icon) {
        	    stringAppend(cmd, iconpath->str);
        	    stringAppend(cmd, icon);
        	    stringAppend(cmd, ".png ");
	        }
        }

        if (nc || cv) {
            
           stringAppend(cmd, " $HOME/.anyRemote/layout_xml_raw0.png; convert -gravity Center -background transparent ");
           if (nc) {
                 string_t* file = findNamedCover(nc);
                 if (file) {
                     stringAppend(cmd, file->str);
                     stringFree(file, BOOL_YES);
                 }
            } else {  // cv
                 stringAppend(cmd, cv);
            }
            stringAppend(cmd, " $HOME/.anyRemote/layout_xml_raw0.png -append $HOME/.anyRemote/layout_xml_raw.png; ");
        } else {
            stringAppend(cmd, " $HOME/.anyRemote/layout_xml_raw.png; ");
        }
        stringAppend(cmd, "convert $HOME/.anyRemote/layout_xml_raw.png ");

    } else {

        cmd = stringNew("montage -background transparent -tile 3x4 -geometry 32x32 ");   // use 32x32 icons

        //stringAppend(cmd, "-draw \'text 0,0 \"");
        //stringAppend(cmd, cfTitle());
        //stringAppend(cmd, "\";gravity Center\' ");

        int i;
        for (i=0; i<ICON_NUM; i++) {
	        const char* icon = cfIcon(i);
	        if (icon) {
                stringAppend(cmd, iconpath->str);
        	    stringAppend(cmd, icon);
        	    stringAppend(cmd, ".png ");
	        }
        }
        stringAppend(cmd, " $HOME/.anyRemote/layout_xml_raw.png; convert $HOME/.anyRemote/layout_xml_raw.png ");
    }
    mutexUnlock(M_STATE);

    renderConvertCmd(cmd);

    stringAppend(cmd, " $HOME/.anyRemote/layout_xml.png");

    INFO2("[WS]: renderXMLLayout CMD=%s", cmd->str);

    _xmlLayoutW = _xmlScreenW; // reset
    _xmlLayoutH = _xmlScreenH;

    size_t sz = 0;
    char* dummy = executeCommandPipe(cmd->str, &sz);
    if (dummy) {  // can be NULL
        free(dummy);
    }

    char* buf = executeCommandPipe("identify -format \"%w,%h\" $HOME/.anyRemote/layout_xml.png", &sz);
    if (buf) {

        char* w = strtok(buf, ",");
        char* h = strtok(NULL,",");

        _xmlLayoutW = atoi(w);
        _xmlLayoutH = atoi(h);
        INFO2("[WS]: renderXMLLayout rendered image is (%d,%d)", _xmlLayoutW, _xmlLayoutH);

        free(buf);

    } else {
        ERROR2("[WS]: renderXMLLayout can not get size layout image");
    }

    stringFree(cmd,      BOOL_YES);
    stringFree(iconpath, BOOL_YES);

    _xmlLayoutOk = BOOL_YES;
}

void renderXMLImage()
{
    INFO2("[WS]:renderXMLImage (%d,%d) pix, %s", _xmlScreenW, _xmlScreenH,
          (_xmlScreenG ? "Grayscale" : "Color"));

    // Use ImageMagic to create picture to show

    string_t* cmd = stringNew("convert ");
    
    mutexLock(M_STATE);
    
    stringAppend(cmd, wfImage());
    
    mutexUnlock(M_STATE);

    renderConvertCmd(cmd);

    stringAppend(cmd, " $HOME/.anyRemote/image_xml.png");

    INFO2("[WS]:renderXMLImage CMD=%s", cmd->str);

    size_t sz = 0;
    char* str = executeCommandPipe(cmd->str, &sz);
    // it is on to get NULL in str, because command does not produce any output
    free(str);

    stringFree(cmd, BOOL_YES);
}

string_t* renderCtrlXMLForm(string_t* ip, int port)
{
    INFO2("[WS]: renderCtrlXMLForm");

    if (_xmlGraphic == BOOL_YES && !_xmlLayoutOk) {
        renderXMLLayout();
    }
    
    mutexLock(M_STATE);
    string_t* page = renderXMLFormHead(CF, cfCaption());
    mutexUnlock(M_STATE);
    
    char num[16];
    sprintf(num,"%d", (_xmlScreenW - _xmlLayoutW)/2);
    
    if (_xmlGraphic == BOOL_YES) {
        
        stringAppend(page, "  <LocationX>");
        stringAppend(page, num);
        stringAppend(page, "</LocationX>\n");

        stringAppend(page, "  <LocationY>");

        sprintf(num,"%d", (bottomlineSkin() ? _xmlScreenH - _xmlLayoutH : (_xmlScreenH - _xmlLayoutH)/2));
        stringAppend(page, num);

        stringAppend(page, "</LocationY>\n");

        stringAppend(page, "  <URL>");

        addLink(ip, page, port, BOOL_YES);

        stringAppend(page, "xml_layout</URL>\n");
    }

    int i = 0;
    
    mutexLock(M_STATE);
    
    while (i < ICON_NUM) {

        const char* icon = cfIcon(i);

        if (bottomlineSkin() && (!icon || strcmp(icon,"none") == 0)) {
            break; // skip if empty icon specified
        }

        stringAppend(page, "  <MenuItem>\n   <Name>");
        if (_xmlGraphic == BOOL_YES) {
            stringAppend(page, icon);
        } else {
            const char* hint = cfHint(i);
            stringAppend(page, hint);
        }
        stringAppend(page, "</Name>\n   <URL>");

        addLink(ip, page, port, BOOL_YES);

        stringAppend(page, XML_BUTTON_PRESS);

        sprintf(num,"%d", i+1);
        stringAppend(page, num);

        stringAppend(page, "</URL>\n  </MenuItem>\n");

        i++;
    }
    mutexUnlock(M_STATE);

    addXMLFormMenu(CF, ip, page, port);

    if (_xmlGraphic == BOOL_YES) {
        stringAppend(page, XML_CFORM99G);
    } else {
        stringAppend(page, XML_CFORM99);
    }
    //printf("renderCtrlXMLForm FULL \n%s\n",page->str);
    return page;
}

string_t* renderTextXMLForm(string_t* ip, int port)
{
    mutexLock(M_STATE);
    string_t* page = renderXMLFormHead(TX,tfCaption());
     
    stringAppend(page, "  <Text>");
    
    stringAppend(page, tfText());
    
    mutexUnlock(M_STATE);
    
    stringAppend(page, "&#10;</Text>");

    addXMLFormMenu(TX, ip, page, port);

    stringAppend(page, XML_TEXT99);
    return page;
}

string_t* renderListXMLForm(string_t* ip, int port)
{
    mutexLock(M_STATE);
    
    string_t* page = renderXMLFormHead(LI,lfCaption());
    
    SingleList* list = lfList();
    char num[16];

    int lsz = listSingleLength(list);

    if (lsz > 0) {

        int idx = 0;

        while (list && idx <= XML_MAX_LIST_SIZE) {

            stringAppend(page, "  <MenuItem>\n   <Name>");
            char* listItem = ((ListItem*) list->data)->string->str;
            int i = 0;
            for (;i<((ListItem*) list->data)->string->len;i++) {
                if (*(listItem+i) == '\''|| *(listItem+i) == '&') {
                    *(listItem+i) = '-';
                }
            }
            if (((ListItem*) list->data)->string->len > CMXML_MAX_STRING_SIZE) {
                stringAppendLen(page, listItem, CMXML_MAX_STRING_SIZE);
            } else {
                stringAppend(page, listItem);
            }
            stringAppend(page, "</Name>\n   <URL>");

            sprintf(num,"%d", idx);

            stringAppend(page, "QueryStringParam:");
            stringAppend(page, num);
            stringAppend(page, "</URL>\n  </MenuItem>\n");

            list = listSingleNext(list);
            idx++;
        }
        if (list && idx == XML_MAX_LIST_SIZE) {
            INFO2("[WS]: renderListXMLForm: too many items in list, skip the rest");
        }

    } else {  // add dummy item
        stringAppend(page, "  <MenuItem>\n	<Name>---empty---</Name>\n   <URL>");

        stringAppend(page, "QueryStringParam:");
        stringAppend(page, "-1");
        stringAppend(page, "</URL>\n  </MenuItem>\n");
    }
   
    mutexUnlock(M_STATE);

    SingleList* mlist = userMenu();

    int llen = listSingleLength(mlist);

    if (llen > 0) {

        int position = 1;
        boolean_t longMenu = (llen > XML_SOFTKEY_NUM ? BOOL_YES : BOOL_NO);

        while(mlist) {

            if (mlist->data && ((string_t*) mlist->data)->str) {

                INFO2("[WS]: renderListXMLForm menu item %s",((string_t*) mlist->data)->str);

                stringAppend(page, "  <SoftKeyItem>\n    <Name>");
                if (longMenu && position == XML_SOFTKEY_NUM) {
                    stringAppend(page, ">>");
                } else {
                    stringAppend(page, ((string_t*) mlist->data)->str);
                }
                stringAppend(page, "</Name>\n    <URL>");

                addLink(ip, page, port, BOOL_YES);

                char buf[8];
                sprintf(buf,"%d", position);

                if (longMenu && position == XML_SOFTKEY_NUM) {
                    stringAppend(page, XML_LIST_MENU_EXT);
                } else {
                    stringAppend(page, XML_LIST_MENU);
                    stringAppend(page, buf);
                }

                stringAppend(page, "</URL>\n    <Position>");
                stringAppend(page, buf);

                stringAppend(page, "</Position>\n  </SoftKeyItem>\n");
            }
            mlist = listSingleNext(mlist);

            position++;
            if (position > XML_SOFTKEY_NUM) {  // should not happen
                break;
            }
        }
    }

    stringAppend(page, XML_LIST99);
    //printf("renderListXMLForm FULL \n%s\n",page->str);
    return page;
}

string_t* renderWmanXMLForm(string_t* ip, int port)
{
    INFO2("[WS]: renderWmanXMLForm");

    mutexLock(M_STATE);
    string_t* page = renderXMLFormHead(WM, cfCaption());
    mutexUnlock(M_STATE);
    
    if (_xmlGraphic == BOOL_YES) {
        stringAppend(page, "  <LocationX>0</LocationX>\n");
        stringAppend(page, "  <LocationY>0</LocationY>\n");
        stringAppend(page, "  <URL>");

        addLink(ip, page, port, BOOL_YES);

        stringAppend(page, "xml_image</URL>\n");
    }

    addXMLFormMenu(WM, ip, page, port);

    stringAppend(page, XML_WMAN99);
    //printf("renderWmanXMLForm FULL \n%s\n",page->str);
    return page;
}

static string_t* renderEditXML(const char* caption, const char* label, const char* value, string_t* ip, int p)
{
    string_t* page = renderXMLFormHead(EF, caption);

    stringAppend(page, "  <URL>");
    addLink(ip, page, p, BOOL_YES);
    stringAppend(page, ";");

    stringAppend(page, "</URL>\n  <InputItem>\n   <DisplayName>");
    stringAppend(page, label);
    stringAppend(page, "</DisplayName>\n   <QueryStringParam>xml_ef</QueryStringParam>\n   <DefaultValue>");
    stringAppend(page, value);
    stringAppend(page, "</DefaultValue>\n   <InputFlags>A</InputFlags>\n  </InputItem>\n");

    //addXMLFormMenu(EF, ip, page, p);  -- add hardcoded Ok and Back menu items

    stringAppend(page, "  <SoftKeyItem>\n    <Name>Ok</Name>\n    <URL>SoftKey:Submit</URL>\n    <Position>1</Position>\n  </SoftKeyItem>\n");
    stringAppend(page, "  <SoftKeyItem>\n    <Name>Back</Name>\n    <URL>");
    addLink(ip, page, p, BOOL_YES);
    stringAppend(page, XML_EFIELD_CANCEL);
    stringAppend(page, "</URL>\n    <Position>2</Position>\n  </SoftKeyItem>\n");
    stringAppend(page, "  <SoftKeyItem>\n    <Name>&lt;&lt;</Name>\n    <URL>SoftKey:&lt;&lt;</URL>\n    <Position>3</Position>\n  </SoftKeyItem>\n");

    stringAppend(page, XML_EFORM99);
    return page;
}

string_t* renderEditXMLForm(string_t* ip, int port)
{
    INFO2("[WS]: renderXMLEditForm");
    mutexLock(M_STATE);
    string_t* val = renderEditXML(efCaption(), efLabel(), efText(), ip, port);
    mutexUnlock(M_STATE);
    return val;
}

string_t* renderPassXMLForm(string_t* ip, int port)
{
    INFO2("[WS]: renderPassXMLForm");
    return renderEditXML("Enter Password", "Enter Password", "", ip, port);
}
