#if 0
    INDI
    Copyright (C) 2003 Elwood C. Downey

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Lesser General Public
    License as published by the Free Software Foundation; either
    version 2.1 of the License, or (at your option) any later version.

    This library 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
    Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public
    License along with this library; if not, write to the Free Software
    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

#endif

/* simulate a telescope mount on an INDI interface.
 * it can be told to slew to an RA or Dec and will go there slowly enough to
 * watch.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <math.h>
#include <sys/time.h>
#include <unistd.h>

#include "astro.h"
#include "indidevapi.h"

static void mountSim (void *);

#define	SLEWRATE	degrad(10.0)		/* slew rate, rad/s */
#define	POLLMS		67			/* poll period, ms */
#define	MINMOVE		degrad(0.25/3600)	/* report if move more */


/* INDI controls */

#define	mydev	"Mount"				/* our Device name */
#define	mygroup	""				/* property group name */

/* equatorial position */
static INumber eq[] = {
    {"RA",  "RA  H:M:S", "%12.8m",  0., 24., 0., 0.},
    {"DEC", "Dec D:M:S", "%10.6m", -90., 90., 0., 0.},
};
static INumberVectorProperty eqNum = {
    mydev, "EQUATORIAL_COORD", "Equatorial J2000", mygroup, IP_RW,
    2*PI/SLEWRATE, IPS_IDLE, eq, NARRAY(eq),
};
#define	currentRA	(eq[0].value)		/* handy shortcut */
#define	currentDec	(eq[1].value)		/* handy shortcut */

/* lat/long loc */
static INumber loc[] = {
    {"LAT",  "Lat  D:M:S +N", "%10.6m",   -90.,  90., 0., 40.},
    {"LONG", "Long D:M:S +E", "%12.8m",  -180., 180., 0., -90.},
};
static INumberVectorProperty locNum = {
    mydev, "GEOGRAPHIC_COORD", "Location", mygroup, IP_RW, 0.0,
    IPS_IDLE, loc, NARRAY(loc),
};

/* target in edb format */
static IText edb[] = {
    {"edb", "edb format line"},
};
static ITextVectorProperty edbText = {
    mydev, "GOTOedb", "Target", mygroup, IP_RW, 2*PI/SLEWRATE,
    IPS_IDLE, edb, NARRAY(edb),
}; 

/* main power switch */
static ISwitch power[] = {
    {"Off", "Off", ISS_ON},
    {"On",  "On",  ISS_OFF},
};
static ISwitchVectorProperty powSw = {
    mydev, "Power", "Main Power", mygroup, IP_RW, ISR_1OFMANY, 0., IPS_IDLE,
    power, NARRAY(power),
};
#define	powerIsOn()	(power[1].s == ISS_ON)

/* operational info */
static Now now, *np = &now;	/* observing circumstances */
static Obj obj;			/* target object */


/* call first, then benign there after */
static void
mountInit()
{
	static int inited;		/* set once mountInit is called */

	if (inited)
	    return;

	/* work in J2k */
	epoch = J2000;
	pressure = 1000;
	temp = 10;

	lat = degrad(loc[0].value);
	lng = degrad(loc[1].value);

	/* initial target object */
	obj.o_type = FIXED;
	obj.f_epoch = J2000;
	obj.f_RA = 0;
	obj.f_dec = 0;

	/* start timer to simulate mount motion */
	IEAddTimer (POLLMS, mountSim, NULL);

	inited = 1;
}

/* send client definitions of all properties */
void
ISGetProperties (const char *dev)
{
	if (dev && strcmp (mydev, dev))
	    return;

	mountInit();

	IDDefSwitch (&powSw, NULL);
	IDDefNumber (&eqNum, NULL);
	IDDefNumber (&locNum, NULL);
	IDDefText   (&edbText, NULL);
}

/* client is sending us a new value for a Text vector property */
void
ISNewText (const char *dev, const char *name, char *texts[],
char *names[], int n)
{
	/* ignore if not ours */
	if (strcmp (dev, mydev))
	    return;

	mountInit();

	if (!strcmp (name, edbText.name)) {
	    IText *tp = IUFindText (&edbText, names[0]);
	    char *newedb = texts[0];
	    char msg[1024];

	    if (!tp)
		return;

	    /* check target and return IDLE */
	    edbText.s = IPS_IDLE;
	    if (!powerIsOn()) {
		IDSetText (&edbText, "Power is off");
		return;
	    }
	    if (db_crack_line (newedb, &obj, NULL, 0, msg) < 0) {
		IDSetText (&edbText, "Bad format: %s", msg);
		return;
	    }

	    /* new target object */
	    IUSaveText (tp, newedb);
	    edbText.s = IPS_BUSY;
	    IDSetText (&edbText, "Slewing to %s", obj.o_name);

	    eqNum.s = IPS_BUSY;
	    IDSetNumber (&eqNum, NULL);
	    return;
	}
}

void
ISNewBLOB (const char *dev, const char *name, int sizes[],
    int blobsizes[], char *blobs[], char *formats[], char *names[], int n)
{
}

/* client is sending us a new value for a Numeric vector property */
void
ISNewNumber (const char *dev, const char *name, double values[],
char *names[], int n)
{
	/* ignore if not ours */
	if (strcmp (dev, mydev))
	    return;

	if (!strcmp (name, eqNum.name)) {
	    /* new equatorial target coords */
	    double newra = 0, newdec = 0;
	    int i, nset;

	    if (!powerIsOn()) {
		eqNum.s = IPS_IDLE;
		IDSetNumber (&eqNum, "Power is off");
		return;
	    }

	    for (nset = i = 0; i < n; i++) {
		INumber *eqp = IUFindNumber (&eqNum, names[i]);
		if (eqp == &eq[0]) {
		    newra = hrrad (values[i]);
		    nset += newra >= 0 && newra < 2*PI;
		} else if (eqp == &eq[1]) {
		    newdec = degrad (values[i]);
		    nset += newdec >= -PI/2 && newdec <= PI/2;
		}
	    }
	    if (nset == 2) {
		char r[32], d[32];
		eqNum.s = IPS_BUSY;
		memset (&obj, 0, sizeof(obj));
		obj.o_type = FIXED;
		obj.f_RA = newra;
		obj.f_dec = newdec;
		obj.f_epoch = J2000;
		fs_sexa (r, radhr(obj.f_RA), 2, 36000);
		fs_sexa (d, raddeg(obj.f_dec), 3, 3600);
		IDSetNumber (&eqNum, "Slewing to RA %s Dec %s", r, d);

		edbText.s = IPS_IDLE;
		IDSetText (&edbText, NULL);
	    } else {
		eqNum.s = IPS_IDLE;
		IDSetNumber (&eqNum, "RA or Dec absent or bogus");
	    }

	    return;
	}

	if (!strcmp (name, locNum.name)) {
	    /* new location */
	    int i;

	    if (!powerIsOn()) {
		locNum.s = IPS_IDLE;
		IDSetNumber (&locNum, "Power is off");
		return;
	    }

	    locNum.s = IPS_IDLE;
	    for (i = 0; i < n; i++) {
		char buf[32];
		if (strcmp (loc[0].name, names[i]) == 0) {
		    loc[0].value = values[i];
		    lat = degrad(values[i]);
		    fs_sexa (buf, values[i], 3, 3600);
		    IDSetNumber (&locNum, "Latitude set to %s", buf);
		} else if (strcmp (loc[1].name, names[i]) == 0) {
		    loc[1].value = values[i];
		    lng = degrad(values[i]);
		    fs_sexa (buf, values[i], 3, 3600);
		    IDSetNumber (&locNum, "Longitude set to %s", buf);
		} else {
		    IDSetNumber (&locNum, "Bogus value");
		    break;
		}
	    }
	}
}

/* client is sending us a new value for a Switch property */
void
ISNewSwitch (const char *dev, const char *name, ISState *states,
char *names[], int n)
{
	/* ignore if not ours */
	if (strcmp (dev, mydev))
	    return;

	if (strcmp (name, powSw.name) == 0) {
	    ISwitch *sp = IUFindSwitch (&powSw, names[0]);
	    if ((sp == &power[0] && states[0] == ISS_OFF)
		|| (sp == &power[1] && states[0] == ISS_ON)) {
		powSw.s = IPS_OK;
		power[0].s = ISS_OFF;
		power[1].s = ISS_ON;
		IDSetSwitch (&powSw, "Power on");
		eqNum.s = IPS_BUSY;
	    } else if ((sp == &power[1] && states[0] == ISS_OFF)
		|| (sp == &power[0] && states[0] == ISS_ON)) {
		powSw.s = IPS_IDLE;
		power[0].s = ISS_ON;
		power[1].s = ISS_OFF;
		IDSetSwitch (&powSw, "Power off");
		eqNum.s = IPS_IDLE;
		IDSetNumber (&eqNum, NULL);
	    }
	}
}

/* update the "mount" over time */
void
mountSim (void *p)
{
	static struct timeval ltv;
	struct timeval tv;
	double dt, da, dx;
	int nlocked;

	/* update elapsed time since last poll, don't presume exactly POLLMS */
	gettimeofday (&tv, NULL);
	if (ltv.tv_sec == 0 && ltv.tv_usec == 0)
	    ltv = tv;
	dt = tv.tv_sec - ltv.tv_sec + (tv.tv_usec - ltv.tv_usec)/1e6;
	ltv = tv;
	da = SLEWRATE*dt;

	/* update ephemeris */
	mjd = (25567.5 + (tv.tv_sec + tv.tv_usec/1e6)/3600.0/24.0);
	obj_cir (np, &obj);

	/* process per current state */
	switch (eqNum.s) {
	case IPS_IDLE:
	    /* RA moves at sidereal, Dec stands still */
	    currentRA += (24*SIDRATE*dt/SPD);
	    range (&currentRA, 24.0);
	    IDSetNumber (&eqNum, NULL);
	    break;

	case IPS_BUSY:
	    /* slewing - nail it when both within one pulse @ SLEWRATE */
	    nlocked = 0;

	    dx = obj.s_ra - hrrad(currentRA);
	    if (delra(dx) < da) {
		currentRA = radhr(obj.s_ra);
		nlocked++;
	    } else {
		range (&dx, 2*PI);
		if (dx > 0 && dx < PI)
		    currentRA += radhr(da);
		else
		    currentRA -= radhr(da);
		range (&currentRA, 24.0);
	    }

	    dx = obj.s_dec - degrad(currentDec);
	    if (fabs(dx) < da) {
		currentDec = raddeg(obj.s_dec);
		nlocked++;
	    } else {
		if (dx > 0)
		    currentDec += raddeg(da);
		else
		    currentDec -= raddeg(da);
	    }

	    if (nlocked == 2) {
		int e = (edbText.s == IPS_BUSY);
		if (e) {
		    edbText.s = IPS_OK;
		    IDSetText (&edbText, "Now tracking %s", obj.o_name);
		}
		if (eqNum.s == IPS_BUSY) {
		    eqNum.s = IPS_OK;
		    IDSetNumber (&eqNum, e ? NULL : "Now tracking");
		}
	    } else
		IDSetNumber (&eqNum, NULL);

	    break;

	case IPS_OK:
	    /* tracking -- send if either moved more than MINMOVE */
	    if (delra(obj.s_ra - hrrad(currentRA))*cos(obj.s_dec) > MINMOVE
			    || fabs(obj.s_dec - degrad(currentDec)) > MINMOVE) {
		currentRA = radhr(obj.s_ra);
		currentDec = raddeg(obj.s_dec);
		IDSetNumber (&eqNum, NULL);
	    }
	    break;

	case IPS_ALERT:
	    break;
	}

	/* again */
	IEAddTimer (POLLMS, mountSim, NULL);
}

/* For RCS Only -- Do Not Edit */
static char *rcsid[2] = {(char *)rcsid, "@(#) $RCSfile: tmount.c,v $ $Date: 2006/08/26 18:04:43 $ $Revision: 1.22 $ $Name:  $"};
