/*  INDI frontend for KStars
    Copyright (C) 2003 Jasem Mutlaq (mutlaqja@ikarustech.com)
    		       Elwood C. Downey.

    This application 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 2 of the License, or (at your option) any later version.

    JM Changelog:
    2003-04-28 Used indimenu.c as a template. C --> C++, Xm --> KDE/TQt
    2003-05-01 Added tab for devices and a group feature
    2003-05-02 Added scrolling area. Most things are rewritten
    2003-05-05 Device/Group seperation
    2003-05-29 Replaced raw INDI time with KStars's timedialog
    2003-08-02 Upgrading to INDI v 1.11
    2003-08-09 Initial support for non-sidereal tracking
    2004-01-15 redesigning the GUI to support INDI v1.2 and fix previous GUI bugs
               and problems. The new GUI can easily incoperate extensions to the INDI
	       protocol as required.

 */

#include "indiproperty.h"
#include "indigroup.h"
#include "indidevice.h"
#include "devicemanager.h"
#include "indimenu.h"
#include "indidriver.h"
#include "indistd.h"
#include "indi/indicom.h"
#include "kstars.h"
#include "skyobject.h"
#include "timedialog.h"
#include "geolocation.h"
#include "indi/base64.h"

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <zlib.h>

#include <tqlineedit.h>
#include <tqtextedit.h>
#include <tqframe.h>
#include <tqtabwidget.h>
#include <tqcheckbox.h>
#include <tqlabel.h>
#include <tqpushbutton.h>
#include <tqlayout.h>
#include <tqtooltip.h>
#include <tqwhatsthis.h>
#include <tqbuttongroup.h>
#include <tqscrollview.h>
#include <tqsocketnotifier.h>
#include <tqvbox.h>
#include <tqdatetime.h>
#include <tqtable.h>
#include <tqstring.h>
#include <tqptrlist.h>

#include <kled.h>
#include <klineedit.h>
#include <kpushbutton.h>
#include <tdeapplication.h>
#include <tdelocale.h>
#include <tdemessagebox.h>
#include <tdelistview.h>
#include <kdebug.h>
#include <kcombobox.h>
#include <knuminput.h>
#include <kdialogbase.h>
#include <kstatusbar.h>
#include <tdepopupmenu.h>

#define NINDI_STD	26
/* INDI standard property used across all clients to enable interoperability. */

const char * indi_std[NINDI_STD] =
  {"CONNECTION", "DEVICE_PORT", "TIME", "SDTIME", "GEOGRAPHIC_COORD", "EQUATORIAL_COORD", "EQUATORIAL_EOD_COORD", "HORIZONTAL_COORD", "ABORT_MOTION", "ON_COORD_SET", "SOLAR_SYSTEM", "MOVEMENT", "PARK", "CCD_EXPOSE_DURATION", "CCD_TEMPERATURE", "CCD_FRAME", "CCD_FRAME_TYPE", "CCD_BINNING", "CCD_INFO", "CCDPREVIEW_STREAM", "CCDPREVIEW_CTRL", "VIDEO_STREAM", "FOCUS_SPEED", "FOCUS_MOTION", "FOCUS_TIMER", "FILTER_SLOT" };

/*******************************************************************
** INDI Device: The work-horse. Responsible for handling its
** child properties and managing signal and changes.
*******************************************************************/
INDI_D::INDI_D(INDIMenu *menuParent, DeviceManager *parentManager, TQString inName, TQString inLabel)
{
  name      = inName;
  label     = inLabel;
  parent    = menuParent;
  parentMgr = parentManager;

  gl.setAutoDelete(true);

 deviceVBox     = menuParent->addVBoxPage(inLabel);
 groupContainer = new TQTabWidget(deviceVBox);

 msgST_w        = new TQTextEdit(deviceVBox);
 msgST_w->setReadOnly(true);
 msgST_w->setMaximumHeight(100);

 dataBuffer = (unsigned char *) malloc (1);

 stdDev 	= new INDIStdDevice(this, parent->ksw);

 curGroup       = NULL;

 INDIStdSupport = false;

}

INDI_D::~INDI_D()
{
   gl.clear();
   delete(deviceVBox);
   delete (stdDev);
   free (dataBuffer);
   dataBuffer = NULL;
   deviceVBox = NULL;
   stdDev     = NULL;
}

void INDI_D::registerProperty(INDI_P *pp)
{

   if (isINDIStd(pp))
    pp->pg->dp->INDIStdSupport = true;

    stdDev->registerProperty(pp);

}

bool INDI_D::isINDIStd(INDI_P *pp)
{
  for (uint i=0; i < NINDI_STD; i++)
    if (!strcmp(pp->name.ascii(), indi_std[i]))
    {
      pp->stdID = i;
      return true;
    }

  return false;
}

/* Remove a property from a group, if there are no more properties
 * left in the group, then delete the group as well */
int INDI_D::removeProperty(INDI_P *pp)
{
    for (unsigned int i=0; i < gl.count(); i++)
       if (gl.at(i)->removeProperty(pp))
       {
         if (gl.at(i)->pl.count() == 0)
	    gl.remove(i);
         return 0;
	}


    kdDebug() << "INDI: Device " << name << " has no property named " << pp->name << endl;
    return (-1);
}

/* implement any <set???> received from the device.
 * return 0 if ok, else -1 with reason in errmsg[]
 */
int INDI_D::setAnyCmd (XMLEle *root, char errmsg[])
{
	XMLAtt *ap;
	INDI_P *pp;

	ap = findAtt (root, "name", errmsg);
	if (!ap)
	    return (-1);

	pp = findProp (valuXMLAtt(ap));
	if (!pp)
	{
	    snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.32s> device %.32s has no property named %.64s",
						tagXMLEle(root), name.ascii(), valuXMLAtt(ap));
	    return (-1);
	}

	parentMgr->checkMsg (root, this);

	return (setValue (pp, root, errmsg));
}

/* set the given GUI property according to the XML command.
 * return 0 if ok else -1 with reason in errmsg
 */
int INDI_D::setValue (INDI_P *pp, XMLEle *root, char errmsg[])
{
	XMLAtt *ap;

	/* set overall property state, if any */
	ap = findXMLAtt (root, "state");
	if (ap)
	{
	    if (crackLightState (valuXMLAtt(ap), &pp->state) == 0)
	      pp->drawLt (pp->state);
	    else
	    {
		snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s> bogus state %.64s for %.64s %.64s",
						tagXMLEle(root), valuXMLAtt(ap), name.ascii(), pp->name.ascii());
		return (-1);
	    }
	}

	/* allow changing the timeout */
	ap = findXMLAtt (root, "timeout");
	if (ap)
	    pp->timeout = atof(valuXMLAtt(ap));

	/* process specific GUI features */
	switch (pp->guitype)
	{
	case PG_NONE:
	    break;

	case PG_NUMERIC:	/* FALLTHRU */
	case PG_TEXT:
	    return (setTextValue (pp, root, errmsg));
	    break;

	case PG_BUTTONS:
	case PG_LIGHTS:
	case PG_RADIO:
	case PG_MENU:
	    return (setLabelState (pp, root, errmsg));
	    break;

	case PG_BLOB:
	    return (setBLOB(pp, root, errmsg));
	    break;

	default:
	    break;
	}

	return (0);
}


/* set the given TEXT or NUMERIC property from the given element.
 * root should have <text> or <number> child.
 * return 0 if ok else -1 with reason in errmsg
 */
int INDI_D::setTextValue (INDI_P *pp, XMLEle *root, char errmsg[])
{
	XMLEle *ep;
	XMLAtt *ap;
	INDI_E *lp;
	TQString elementName;
	char iNumber[32];
	double min, max;

	for (ep = nextXMLEle (root, 1); ep != NULL; ep = nextXMLEle (root, 0))
	{
	    if (strcmp (tagXMLEle(ep), "oneText") && strcmp(tagXMLEle(ep), "oneNumber"))
		continue;

	    ap = findXMLAtt(ep, "name");
	    if (!ap)
	    {
	        kdDebug() << "Error: unable to find attribute 'name' for property " << pp->name << endl;
	        return (-1);
	    }

	    elementName = valuXMLAtt(ap);

	    lp = pp->findElement(elementName);

	    if (!lp)
	    {
	      snprintf(errmsg, ERRMSG_SIZE, "Error: unable to find element '%.64s' in property '%.64s'", elementName.ascii(), pp->name.ascii());
	      return (-1);
	    }

	    //fprintf(stderr, "tag okay, getting perm\n");
	   switch (pp->perm)
	   {
	   case PP_RW:	// FALLTHRU
	   case PP_RO:
	     if (pp->guitype == PG_TEXT)
	     {
	     	lp->text = TQString(pcdataXMLEle(ep));
		lp->read_w->setText(lp->text);
             }
	     else if (pp->guitype == PG_NUMERIC)
	     {
	       lp->value = atof(pcdataXMLEle(ep));
	       numberFormat(iNumber, lp->format.ascii(), lp->value);
	       lp->text = iNumber;
	       lp->read_w->setText(lp->text);

	       ap = findXMLAtt (ep, "min");
	       if (ap) { min = atof(valuXMLAtt(ap)); lp->setMin(min); }
	       ap = findXMLAtt (ep, "max");
	       if (ap) { max = atof(valuXMLAtt(ap)); lp->setMax(max); }

	       /*if (lp->spin_w)
	       {
	        lp->spin_w->setValue(lp->value);
		lp->spinChanged(lp->value);
	     }*/

	     }
	     break;

	case PP_WO:
	    if (pp->guitype == PG_TEXT)
	      lp->write_w->setText(TQString(pcdataXMLEle(ep)));
	    else if (pp->guitype == PG_NUMERIC)
	    {
	      lp->value = atof(pcdataXMLEle(ep));
	      numberFormat(iNumber, lp->format.ascii(), lp->value);
	      lp->text = iNumber;

	      if (lp->spin_w)
                lp->spin_w->setValue(lp->value);
	      else
 	        lp->write_w->setText(lp->text);

	       ap = findXMLAtt (ep, "min");
	       if (ap) { min = (int) atof(valuXMLAtt(ap)); lp->setMin(min); }
	       ap = findXMLAtt (ep, "max");
	       if (ap) { max = (int) atof(valuXMLAtt(ap)); lp->setMax(max); }
	    }
	    break;

	  }
       }

       /* handle standard cases if needed */
       stdDev->setTextValue(pp);

	// suppress warning
	errmsg = errmsg;

	return (0);
}

/* set the given BUTTONS or LIGHTS property from the given element.
 * root should have some <switch> or <light> children.
 * return 0 if ok else -1 with reason in errmsg
 */
int INDI_D::setLabelState (INDI_P *pp, XMLEle *root, char errmsg[])
{
	int menuChoice=0;
	unsigned i=0;
	XMLEle *ep;
	XMLAtt *ap;
        INDI_E *lp = NULL;
	int islight;
	PState state;

	/* for each child element */
	for (ep = nextXMLEle (root, 1), i=0; ep != NULL; ep = nextXMLEle (root, 0), i++)
	{

	    /* only using light and switch */
	    islight = !strcmp (tagXMLEle(ep), "oneLight");
	    if (!islight && strcmp (tagXMLEle(ep), "oneSwitch"))
		continue;

	    ap =  findXMLAtt (ep, "name");
	    /* no name */
	    if (!ap)
	    {
		snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s> %.64s %.64s %.64s requires name",
						    tagXMLEle(root), name.ascii(), pp->name.ascii(), tagXMLEle(ep));
		return (-1);
	    }

	    if ((islight && crackLightState (pcdataXMLEle(ep), &state) < 0)
		    || (!islight && crackSwitchState (pcdataXMLEle(ep), &state) < 0))
	    {
		snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s> unknown state %.64s for %.64s %.64s %.64s",
					    tagXMLEle(root), pcdataXMLEle(ep), name.ascii(), pp->name.ascii(), tagXMLEle(ep));
		return (-1);
	    }

	    /* find matching label */
	    //fprintf(stderr, "Find matching label. Name from XML is %s\n", valuXMLAtt(ap));
	    lp = pp->findElement(TQString(valuXMLAtt(ap)));

	    if (!lp)
	    {
		snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s> %.64s %.64s has no choice named %.64s",
						    tagXMLEle(root), name.ascii(), pp->name.ascii(), valuXMLAtt(ap));
		return (-1);
	    }

	    TQFont buttonFont;
	    /* engage new state */
	    lp->state = state;

	    switch (pp->guitype)
	    {
	     case PG_BUTTONS:
	      if (islight)
	       break;

                lp->push_w->setDown(state == PS_ON);
		buttonFont = lp->push_w->font();
		buttonFont.setBold(state == PS_ON);
		lp->push_w->setFont(buttonFont);

		break;

	     case PG_RADIO:
                 lp->check_w->setChecked(state == PS_ON);
		 break;
	     case PG_MENU:
	       if (state == PS_ON)
	       {
	      	if (menuChoice)
	      	{
	        	snprintf(errmsg, ERRMSG_SIZE, "INDI: <%.64s> %.64s %.64s has multiple ON states", tagXMLEle(root), name.ascii(), pp->name.ascii());
			return (-1);
              	}
	      	menuChoice = 1;
	        pp->om_w->setCurrentItem(i);
	       }
	       break;

	     case PG_LIGHTS:
	      lp->drawLt();
	      break;

	     default:
	      break;
	   }

	}

	stdDev->setLabelState(pp);

	return (0);
}

/* Set BLOB vector. Process incoming data stream
 * Return 0 if okay, -1 if error
*/
int INDI_D::setBLOB(INDI_P *pp, XMLEle * root, char errmsg[])
{

  XMLEle *ep;
  INDI_E *blobEL;

  for (ep = nextXMLEle(root,1); ep; ep = nextXMLEle(root,0))
  {

    if (strcmp(tagXMLEle(ep), "oneBLOB") == 0)
    {

      blobEL = pp->findElement(TQString(findXMLAttValu (ep, "name")));

      if (blobEL)
	return processBlob(blobEL, ep, errmsg);
      else
      {
	sprintf (errmsg, "INDI: set %64s.%64s.%64s not found", name.ascii(), pp->name.ascii(), findXMLAttValu(ep, "name"));
	return (-1);
      }
    }
  }

  return (0);

}

/* Process incoming data stream
 * Return 0 if okay, -1 if error
*/
int INDI_D::processBlob(INDI_E *blobEL, XMLEle *ep, char errmsg[])
{
  XMLAtt *ap;
  int blobSize=0, r=0, dataType=0;
  uLongf dataSize=0;
  TQString dataFormat;
  char *baseBuffer;
  unsigned char *blobBuffer(NULL);
  bool iscomp(false);

  ap = findXMLAtt(ep, "size");
  if (!ap)
  {
    sprintf (errmsg, "INDI: set %64s size not found", blobEL->name.ascii());
    return (-1);
  }

  dataSize = atoi(valuXMLAtt(ap));

  ap = findXMLAtt(ep, "format");
  if (!ap)
  {
    sprintf (errmsg, "INDI: set %64s format not found", blobEL->name.ascii());
    return (-1);
  }

  dataFormat = TQString(valuXMLAtt(ap));

  baseBuffer = (char *) malloc ( (3*pcdatalenXMLEle(ep)/4) * sizeof (char));
  blobSize   = from64tobits (baseBuffer, pcdataXMLEle(ep));
  blobBuffer = (unsigned char *) baseBuffer;

  /* Blob size = 0 when only state changes */
  if (dataSize == 0)
  {
    free (blobBuffer);
    return (0);
  }
  else if (blobSize < 0)
  {
    free (blobBuffer);
    sprintf (errmsg, "INDI: %64s.%64s.%64s bad base64", name.ascii(), blobEL->pp->name.ascii(), blobEL->name.ascii());
    return (-1);
  }

  iscomp = (dataFormat.find(".z") != -1);

  dataFormat.remove(".z");

  if (dataFormat == ".fits") dataType = DATA_FITS;
  else if (dataFormat == ".stream") dataType = DATA_STREAM;
  else if (dataFormat == ".ccdpreview") dataType = DATA_CCDPREVIEW;
  else dataType = DATA_OTHER;

  //kdDebug() << "We're getting data with size " << dataSize << endl;
  //kdDebug() << "data format " << dataFormat << endl;

  if (iscomp)
  {

    dataBuffer = (unsigned char *) realloc (dataBuffer,  (dataSize * sizeof(unsigned char)));
    r = uncompress(dataBuffer, &dataSize, blobBuffer, (uLong) blobSize);
    if (r != Z_OK)
    {
      sprintf(errmsg, "INDI: %64s.%64s.%64s compression error: %d", name.ascii(), blobEL->pp->name.ascii(), blobEL->name.ascii(), r);
      free (blobBuffer);
      return -1;
    }

    //kdDebug() << "compressed" << endl;
  }
  else
  {
    //kdDebug() << "uncompressed!!" << endl;
    dataBuffer = (unsigned char *) realloc (dataBuffer,  (dataSize * sizeof(unsigned char)));
    memcpy(dataBuffer, blobBuffer, dataSize);
  }

  stdDev->handleBLOB(dataBuffer, dataSize, dataFormat);

  free (blobBuffer);

  return (0);

}

bool INDI_D::isOn()
{

  INDI_P *prop;

  prop = findProp(TQString("CONNECTION"));
  if (!prop)
   return false;

  return (prop->isOn(TQString("CONNECT")));
}

INDI_P * INDI_D::addProperty (XMLEle *root, char errmsg[])
{
	INDI_P *pp = NULL;
	INDI_G *pg = NULL;
	XMLAtt *ap = NULL;

        // Search for group tag
	ap = findAtt (root, "group", errmsg);
        if (!ap)
        {
                kdDebug() << TQString(errmsg) << endl;
                return NULL;
        }
	// Find an existing group, if none found, create one
        pg = findGroup(TQString(valuXMLAtt(ap)), 1, errmsg);

	if (!pg)
	 return NULL;

        /* get property name and add new property to dp */
	ap = findAtt (root, "name", errmsg);
	if (ap == NULL)
	    return NULL;

	if (findProp (valuXMLAtt(ap)))
	{
	    snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s %.64s %.64s> already exists.\n", tagXMLEle(root),
							name.ascii(), valuXMLAtt(ap));
	    return NULL;
	}

	/* Remove Vertical spacer from group layout, this is done everytime
	  * a new property arrives. The spacer is then appended to the end of the
	  * properties */
	pg->propertyLayout->removeItem(pg->VerticalSpacer);

	pp = new INDI_P(pg, TQString(valuXMLAtt(ap)));

	/* init state */
	ap = findAtt (root, "state", errmsg);
	if (!ap)
	{
	    delete(pp);
	    return (NULL);
	}

	if (crackLightState (valuXMLAtt(ap), &pp->state) < 0)
	{
	    snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s> bogus state %.64s for %.64s %.64s",
				tagXMLEle(root), valuXMLAtt(ap), pp->pg->dp->name.ascii(), pp->name.ascii());
	    delete(pp);
	    return (NULL);
	}

	/* init timeout */
	ap = findAtt (root, "timeout", NULL);
	/* default */
	pp->timeout = ap ? atof(valuXMLAtt(ap)) : 0;

	/* log any messages */
	parentMgr->checkMsg (root, this);

	pp->addGUI(root);

	/* ok! */
	return (pp);
}

INDI_P * INDI_D::findProp (TQString name)
{
       for (unsigned int i = 0; i < gl.count(); i++)
	for (unsigned int j = 0; j < gl.at(i)->pl.count(); j++)
	    if (name == gl.at(i)->pl.at(j)->name)
		return (gl.at(i)->pl.at(j));

	return NULL;
}

INDI_G *  INDI_D::findGroup (TQString grouptag, int create, char errmsg[])
{
  INDI_G *ig;

  for (ig = gl.first(); ig != NULL; ig = gl.next() )
    if (ig->name == grouptag)
    {
      curGroup = ig;
      return ig;
    }

  /* couldn't find an existing group, create a new one if create is 1*/
  if (create)
  {
  	if (grouptag.isEmpty())
	  grouptag = "Group_1";

	curGroup = new INDI_G(this, grouptag);
  	gl.append(curGroup);
        return curGroup;
  }

  snprintf (errmsg, ERRMSG_SIZE, "INDI: group %.64s not found in %.64s", grouptag.ascii(), name.ascii());
  return NULL;
}


/* find "perm" attribute in root, crack and set *pp.
 * return 0 if ok else -1 with excuse in errmsg[]
 */

 int INDI_D::findPerm (INDI_P *pp, XMLEle *root, PPerm *permp, char errmsg[])
{
	XMLAtt *ap;

	ap = findXMLAtt(root, "perm");
	if (!ap) {
	    snprintf (errmsg, ERRMSG_SIZE,"INDI: <%.64s %.64s %.64s> missing attribute 'perm'",
					tagXMLEle(root), pp->pg->dp->name.ascii(), pp->name.ascii());
	    return (-1);
	}
	if (!strcmp(valuXMLAtt(ap), "ro") || !strcmp(valuXMLAtt(ap), "r"))
	    *permp = PP_RO;
	else if (!strcmp(valuXMLAtt(ap), "wo"))
	    *permp = PP_WO;
	else if (!strcmp(valuXMLAtt(ap), "rw") || !strcmp(valuXMLAtt(ap), "w"))
	    *permp = PP_RW;
	else {
	    snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s> unknown perm %.64s for %.64s %.64s",
				tagXMLEle(root), valuXMLAtt(ap), pp->pg->dp->name.ascii(), pp->name.ascii());
	    return (-1);
	}

	return (0);
}

/* convert the given light/property state string to the PState at psp.
 * return 0 if successful, else -1 and leave *psp unchanged.
 */
int INDI_D::crackLightState (char *name, PState *psp)
{
	typedef struct
	{
	    PState s;
	    const char *name;
	} PSMap;

	PSMap psmap[] =
	{
	    {PS_IDLE,  "Idle"},
	    {PS_OK,    "Ok"},
	    {PS_BUSY,  "Busy"},
	    {PS_ALERT, "Alert"},
	};

	for (int i = 0; i < 4; i++)
	    if (!strcmp (psmap[i].name, name)) {
		*psp = psmap[i].s;
		return (0);
	    }

	return (-1);
}

/* convert the given switch state string to the PState at psp.
 * return 0 if successful, else -1 and leave *psp unchanged.
 */
int INDI_D::crackSwitchState (char *name, PState *psp)
{
	typedef struct
	{
	    PState s;
	    const char *name;
	} PSMap;

	PSMap psmap[] =
	{
	    {PS_ON,  "On"},
	    {PS_OFF, "Off"},
	};


	for (int i = 0; i < 2; i++)
	    if (!strcmp (psmap[i].name, name))
	    {
		*psp = psmap[i].s;
		return (0);
	    }

	return (-1);
}

int INDI_D::buildTextGUI(XMLEle *root, char errmsg[])
{
       	INDI_P *pp = NULL;
	PPerm p;

	/* build a new property */
	pp = addProperty (root, errmsg);

	if (pp == NULL)
	    return (-1);

	/* get the permission, it will determine layout issues */
	if (findPerm (pp, root, &p, errmsg))
	{
	    delete(pp);
	    return (-1);
	}

	/* we know it will be a general text GUI */
	pp->guitype = PG_TEXT;
	pp->perm = p;

	if (pp->buildTextGUI(root, errmsg) < 0)
	{
	  delete (pp);
	  return (-1);
	}

	pp->pg->addProperty(pp);

	return (0);
}

/* build GUI for a number property.
 * return 0 if ok, else -1 with reason in errmsg[]
 */
int INDI_D::buildNumberGUI (XMLEle *root, char *errmsg)
{
        INDI_P *pp = NULL;
        PPerm p;

        /* build a new property */
	pp = addProperty (root, errmsg);

	if (pp == NULL)
	    return (-1);

	/* get the permission, it will determine layout issues */
	if (findPerm (pp, root, &p, errmsg))
	{
	    delete(pp);
	    return (-1);
	}

	/* we know it will be a number GUI */
	pp->guitype = PG_NUMERIC;
	pp->perm = p;

	if (pp->buildNumberGUI(root, errmsg) < 0)
	{
	  delete (pp);
	  return (-1);
	}

	pp->pg->addProperty(pp);

	return (0);
}

/* build GUI for switches property.
 * rule and number of will determine exactly how the GUI is built.
 * return 0 if ok, else -1 with reason in errmsg[]
 */
int INDI_D::buildSwitchesGUI (XMLEle *root, char errmsg[])
{
	INDI_P *pp;
	XMLAtt *ap;
	XMLEle *ep;
	int n, err;

	/* build a new property */
	pp = addProperty (root, errmsg);
	if (!pp)
	    return (-1);

	ap = findAtt (root, "rule", errmsg);
	if (!ap)
	{
	    delete(pp);
	    return (-1);
	}

	/* decide GUI. might use MENU if OneOf but too many for button array */
	if (!strcmp (valuXMLAtt(ap), "OneOfMany") || !strcmp (valuXMLAtt(ap), "AtMostOne"))
	{
	    /* count number of switches -- make menu if too many */
	    for ( ep = nextXMLEle(root, 1) , n = 0 ; ep != NULL; ep = nextXMLEle(root, 0))
		if (!strcmp (tagXMLEle(ep), "defSwitch"))
		    n++;

	    if (n > MAXRADIO)
	    {
	        pp->guitype = PG_MENU;
		err = pp->buildMenuGUI (root, errmsg);
		if (err < 0)
		{
		    delete(pp);
		    pp=0;
		    return err;
		}

		pp->pg->addProperty(pp);
		return (err);
	    }

	    /* otherwise, build 1-4 button layout */
	    pp->guitype = PG_BUTTONS;

	    err = pp->buildSwitchesGUI(root, errmsg);
	    if (err < 0)
	    {
	      delete (pp);
	      pp=0;
	      return err;
	    }

	    pp->pg->addProperty(pp);
	    return (err);

	}
	else if (!strcmp (valuXMLAtt(ap), "AnyOfMany"))
	{
	    /* 1-4 checkboxes layout */
	    pp->guitype = PG_RADIO;

	    err = pp->buildSwitchesGUI(root, errmsg);
	    if (err < 0)
	    {
	      delete (pp);
	      pp=0;
	      return err;
	    }

	    pp->pg->addProperty(pp);
	    return (err);
	}

	snprintf (errmsg, ERRMSG_SIZE, "INDI: <%.64s> unknown rule %.64s for %.64s %.64s",
				tagXMLEle(root), valuXMLAtt(ap), name.ascii(), pp->name.ascii());

	delete(pp);
	return (-1);
}



/* build GUI for a lights GUI.
 * return 0 if ok, else -1 with reason in errmsg[] */
int INDI_D::buildLightsGUI (XMLEle *root, char errmsg[])
{
	INDI_P *pp;

	// build a new property
	pp = addProperty (root, errmsg);
	if (!pp)
	    return (-1);

	pp->guitype = PG_LIGHTS;

	if (pp->buildLightsGUI(root, errmsg) < 0)
	{
	  delete (pp);
	  return (-1);
	}

	pp->pg->addProperty(pp);
	return (0);
}

/* build GUI for a BLOB GUI.
 * return 0 if ok, else -1 with reason in errmsg[] */
int INDI_D::buildBLOBGUI  (XMLEle *root, char errmsg[])
{
  INDI_P *pp;
  PPerm p;

  // build a new property
  pp = addProperty (root, errmsg);
  if (!pp)
    return (-1);

  /* get the permission, it will determine layout issues */
  if (findPerm (pp, root, &p, errmsg))
  {
    delete(pp);
    return (-1);
  }

  /* we know it will be a number GUI */
  pp->perm = p;
  pp->guitype = PG_BLOB;

  if (pp->buildBLOBGUI(root, errmsg) < 0)
  {
    delete (pp);
    return (-1);
  }

  pp->pg->addProperty(pp);
  return (0);
}

INDI_E * INDI_D::findElem(TQString name)
{
  INDI_G *grp;
  INDI_P *prop;
  INDI_E *el;

  for (grp = gl.first(); grp != NULL; grp = gl.next())
	{
	   for (prop = grp->pl.first(); prop != NULL; prop = grp->pl.next())
	   {
	     el = prop->findElement(name);
	     if (el != NULL) return el;
	   }
	 }

  return NULL;

}


#include "indidevice.moc"
