/*
 * CyberX10.c
 *
 * Copyright © 1992 by Christopher A. Wichura (caw@miroc.chi.il.us)
 * All Rights Reserved.
 */

 /* stuff for our ReadArgs() call */

#define ARG_TEMPLATE "TARGETS/M,ON/S,OFF/S,DIM/K/N,BASEHC/K,SETCLOCK/S,DIAG/S,DOWNLOAD/K,UPLOAD/K,DEVICE/K,UNIT/K/N,ATTEMPT/S"
enum ReadArgsArray {
	ARG_TARGETS,
	ARG_ON,
	ARG_OFF,
	ARG_DIM,
	ARG_BASEHC,
	ARG_SETCLOCK,
	ARG_DIAG,
	ARG_DOWNLOAD,
	ARG_UPLOAD,
	ARG_DEVICE,
	ARG_UNIT,
	ARG_ATTEMPT,
	ARG_sizeof
};

const UBYTE HouseCode[16] =
{0x60, 0xE0, 0x20, 0xA0, 0x10, 0x90, 0x50, 0xD0, 0x70, 0xF0, 0x30, 0xB0, 0x00, 0x80, 0x40, 0xC0};

struct Library *IconBase;
struct Library *UtilityBase;

struct LocaleInfo LocaleInfo;
struct Locale *DefaultLocale;

int __regargs main(char *cmdptr, int cmdlen, struct WBStartup * WBMsg)
{
	STRPTR WBArgs;
	STRPTR ArgHelp;
	LONG index;
	extern UBYTE __far arg_help[];
	extern UBYTE __far copyright[];

	/*
	 * the first thing we do is try and open up the locale library and
	 * grab our catalog as we will need it if we want to print any error
	 * messages or other text
	 */

	if (LocaleInfo.li_LocaleBase = (APTR) OpenLibrary("locale.library", 38)) {
		DefaultLocale = OpenLocale(NULL);	/* autodocs say this is
							 * guarenteed a valid
							 * return */
		LocaleInfo.li_Catalog = OpenCatalogA(NULL, "CyberX10.catalog", NULL);
	}

	/* try and open up utility.library */
	if (UtilityBase = OpenLibrary("utility.library", 37)) {
		if (ArgHelp = AllocVec(strlen(GetString(&LocaleInfo, MSG_ARG_HELP)) + strlen(GetString(&LocaleInfo, MSG_COPYRIGHT)) + strlen(PROGNAME) + strlen(VersionID) + 1, MEMF_CLEAR)) {
			sprintf(ArgHelp, GetString(&LocaleInfo, MSG_ARG_HELP), PROGNAME, VersionID, GetString(&LocaleInfo, MSG_COPYRIGHT));

			if (WBMsg) {
				ErrorsToEzReq = TRUE;

				if (IconBase = OpenLibrary("icon.library", 37)) {
					for (index = (WBMsg->sm_NumArgs != 1 ? 1 : 0); index < WBMsg->sm_NumArgs; index++) {
						if (WBArgs = WBtoCLIargs(&WBMsg->sm_ArgList[index], ARG_TEMPLATE)) {
							DoInstance(WBArgs, NULL);
							FreeVec(WBArgs);
						}
					}

					CloseLibrary(IconBase);
				}
			}
			else
				DoInstance(NULL, ArgHelp);

			FreeVec(ArgHelp);
		}

		CloseLibrary(UtilityBase);
	}
	else
		ErrorMsg(MSG_COULDNT_OPEN, "utility.library");

	if (LocaleInfo.li_LocaleBase) {
		CloseCatalog(LocaleInfo.li_Catalog);
		CloseLocale(DefaultLocale);
		CloseLibrary((struct Library *) LocaleInfo.li_LocaleBase);
	}
}

 /* actually proccess the argument line and do what was requested */

void DoInstance(STRPTR ArgLine, STRPTR ArgHelp)
{
	struct RDArgs *RArgs, *MyRArgs;
	STRPTR ArgArray[ARG_sizeof];

	STRPTR SerDevice;
	ULONG SerUnit;

	/* do the stuff needed to call ReadArgs to parse the command line */
	memset(ArgArray, 0, sizeof(ArgArray));

	if (MyRArgs = AllocDosObject(DOS_RDARGS, TAG_DONE)) {
		if (ArgLine) {
			MyRArgs->RDA_Source.CS_Buffer = ArgLine;
			MyRArgs->RDA_Source.CS_Length = strlen(ArgLine);
			MyRArgs->RDA_Source.CS_CurChr = 0L;
		}

		if (ArgHelp)
			MyRArgs->RDA_ExtHelp = ArgHelp;
		else
			MyRArgs->RDA_Flags |= RDAF_NOPROMPT;

		if (RArgs = ReadArgs(ARG_TEMPLATE, (LONG *) & ArgArray, MyRArgs)) {
			if (ArgArray[ARG_DEVICE])
				SerDevice = ArgArray[ARG_DEVICE];
			else
				SerDevice = "serial.device";

			if (ArgArray[ARG_UNIT])
				SerUnit = *((LONG *) ArgArray[ARG_UNIT]);
			else
				SerUnit = 0;

			if (OpenTimer()) {
				if (OpenSerial(SerDevice, SerUnit, (BOOL) ArgArray[ARG_ATTEMPT])) {
					if (ArgArray[ARG_ON])
						X10TurnOn((STRPTR *) ArgArray[ARG_TARGETS]);
					else if (ArgArray[ARG_OFF])
						X10TurnOff((STRPTR *) ArgArray[ARG_TARGETS]);
					else if (ArgArray[ARG_DIM])
						X10Dim((STRPTR *) ArgArray[ARG_TARGETS], *((LONG *) ArgArray[ARG_DIM]));
					else if (ArgArray[ARG_BASEHC])
						X10SetBaseHC(ArgArray[ARG_BASEHC]);
					else if (ArgArray[ARG_SETCLOCK])
						X10SetClock();
					else if (ArgArray[ARG_DIAG])
						X10Diagnostic();
					else if (ArgArray[ARG_DOWNLOAD])
						X10Download(ArgArray[ARG_DOWNLOAD]);
					else if (ArgArray[ARG_UPLOAD])
						X10Upload(ArgArray[ARG_UPLOAD]);
					else
						ErrorMsg(MSG_NOTHING_TO_DO);

					CloseSerial();
				}
				else
					ErrorMsg(MSG_NO_SER_DEV, SerDevice, SerUnit);

				CloseTimer();
			}
			else
				ErrorMsg(MSG_NO_TIMER);

			FreeArgs(RArgs);
		}
		else
			PrintFault(IoErr(), PROGNAME);

		FreeDosObject(DOS_RDARGS, MyRArgs);
	}
	else
		ErrorMsg(MSG_NO_RDARGS);
}

 /* turn on the selected X10 units */

void X10TurnOn(STRPTR * Targets)
{
	OnOffDimHandler(Targets, 0, 2, MSG_TURN_ON);
}

 /* turn off the selected X10 units */

void X10TurnOff(STRPTR * Targets)
{
	OnOffDimHandler(Targets, 0, 3, MSG_TURN_OFF);
}

 /* set the selected targets to the specified dim level */

void X10Dim(STRPTR * Targets, ULONG DimLevel)
{
	if (DimLevel < 1 || DimLevel > 16) {
		ErrorMsg(MSG_INVALID_DIM_LEVEL, DimLevel);
		return;
	}

	DimLevel--;

	OnOffDimHandler(Targets, DimLevel, 5, MSG_DIM);
}

 /* loop that handles X10TurnOn, X10TurnOff and X10Dim */

void OnOffDimHandler(STRPTR * Targets, ULONG DimLevel, ULONG Function, LONG Msg)
{
	int hc;
	int unit;
	UBYTE *currentTarget;

	if (!Targets)
		ErrorMsg(MSG_NO_UNITS_SPECIFIED);
	else
		do {
			currentTarget = *Targets;

			hc = X10GetHC(*currentTarget++);

			if (hc == -1)
				ErrorMsg(MSG_INVALID_UNIT, *Targets);
			else {
				unit = atol(currentTarget);

				if (unit < 1 || unit > 16)
					ErrorMsg(MSG_INVALID_UNIT, *Targets);
				else {
					unit--;
					if (!(X10SendDirectCmd(hc, unit, Function, DimLevel)))
						ErrorMsg(MSG_CP290_ERROR_DIRECT, GetString(&LocaleInfo, Msg), *Targets);
				}
			}

			Targets++;

		} while (*Targets);
}

 /* set the base housecode for the buttons on the controller unit */

void X10SetBaseHC(STRPTR BaseHC)
{
	int hc;
	UBYTE Buf[18];

	if (!BaseHC)
		ErrorMsg(MSG_NO_BASE_HC);
	else {
		hc = X10GetHC(BaseHC[0]);

		if (hc == -1)
			ErrorMsg(MSG_INVALID_BASE_HC, BaseHC[0]);
		else {
			memset(Buf, 0xFF, 16);
			Buf[16] = 0;
			Buf[17] = HouseCode[hc];

			ClearSerial();
			SerWrite(Buf, 18);
			if (!X10WaitForAck(FALSE))
				ErrorMsg(MSG_CP290_ERROR_BASEHC);
		}
	}
}

 /* set the controller unit's clock */

void X10SetClock(void)
{
	UBYTE Buf[21];
	struct timeval tr_time;
	ULONG secs;
	int DOW;

	GetSysTime(&tr_time);
	secs = tr_time.tv_secs % SECSINDAY;

	memset(Buf, 0xFF, 16);
	Buf[16] = 2;

	Buf[18] = secs / SECSINHOUR;
	secs %= SECSINHOUR;
	Buf[17] = secs / SECSINMINUTE;

	DOW = ((tr_time.tv_secs / SECSINDAY) % 7) - 1;
	if (DOW == -1)
		DOW = 6;

	Buf[19] = 1L << DOW;

	Buf[20] = Buf[17] + Buf[18] + Buf[19];

	ClearSerial();
	SerWrite(Buf, 21);
	if (!X10WaitForAck(FALSE))
		ErrorMsg(MSG_CP290_ERROR_SETCLOCK);
}

 /* runs a diagnostic test on the x10 interface */

void X10Diagnostic(void)
{
	UBYTE Buf[17];
	int index;
	UWORD raw;
	UBYTE BaseHC;
	STRPTR DOW;

	/*
	 * send the code for a self diagnostic and wait for the unit's
	 * response
	 */

	memset(Buf, 0xFF, 16);
	Buf[16] = 7;

	ClearSerial();
	SerWrite(Buf, 17);

	for (index = 0;; index++) {
		raw = SerGetRawChar(15);

		if (raw == TIMEOUT) {
			ErrorMsg(MSG_CP290_ERROR_DIAG, GetString(&LocaleInfo, MSG_TIMEOUT));
			return;
		}

		if (raw == 0xFF)
			index++;
		else if (index > 5)
			if ((raw & 1) == 1) {
				ErrorMsg(MSG_CP290_ERROR_DIAG, GetString(&LocaleInfo, MSG_STATUS_FLAG));
				return;
			}
			else
				break;
	}

	/*
	 * if the interface passed the diagnositc test then query it about
	 * the base housecode and what time it thinks it is and display this
	 * to the user as part of the status message.
	 */

	memset(Buf, 0xFF, 16);
	Buf[16] = 4;

	ClearSerial();
	SerWrite(Buf, 17);

	for (index = 0;;) {
		raw = SerGetRawChar(15);

		if (raw == TIMEOUT) {
			ErrorMsg(MSG_CP290_ERROR_NO_RESPONSE);
			return;
		}

		if (raw == 0xFF) {
			Buf[index++] = raw;
			break;
		}
	}

	while (index < 12) {
		raw = SerGetRawChar(2);

		if (raw == TIMEOUT) {
			ErrorMsg(MSG_CP290_ERROR_READING, GetString(&LocaleInfo, MSG_TIMEOUT));
			return;
		}

		Buf[index++] = raw;
	}

	if (Buf[11] != Buf[7] + Buf[8] + Buf[9] + Buf[10]) {
		ErrorMsg(MSG_CP290_ERROR_READING, GetString(&LocaleInfo, MSG_CHECKSUM));
		return;
	}

	for (index = 0; index < 16; index++)
		if (HouseCode[index] == Buf[10])
			BaseHC = 'A' + index;

	switch (Buf[9]) {
	case 64:
		if (LocaleBase)
			DOW = GetLocaleStr(DefaultLocale, DAY_1);
		else
			DOW = "Sunday";
		break;

	case 32:
		if (LocaleBase)
			DOW = GetLocaleStr(DefaultLocale, DAY_7);
		else
			DOW = "Saturday";
		break;

	case 16:
		if (LocaleBase)
			DOW = GetLocaleStr(DefaultLocale, DAY_6);
		else
			DOW = "Friday";
		break;

	case 8:
		if (LocaleBase)
			DOW = GetLocaleStr(DefaultLocale, DAY_5);
		else
			DOW = "Thursday";
		break;

	case 4:
		if (LocaleBase)
			DOW = GetLocaleStr(DefaultLocale, DAY_4);
		else
			DOW = "Wednesday";
		break;

	case 2:
		if (LocaleBase)
			DOW = GetLocaleStr(DefaultLocale, DAY_3);
		else
			DOW = "Tuesday";
		break;

	case 1:
		if (LocaleBase)
			DOW = GetLocaleStr(DefaultLocale, DAY_2);
		else
			DOW = "Monday";
		break;

	default:
		DOW = "???";
		break;
	};

	MyPrintf(MSG_CP290_PASSED_DIAG, BaseHC, DOW, Buf[8], Buf[7]);

	/*
	 * when a diagnostic is run, the interface whacks out all of its
	 * memory.  Thus, if we tried to upload data from it we would get
	 * garbage.  we also have garbage sitting there that might cause
	 * funny events to happen.  So we set the unit's base housecode to
	 * what we were just told it was by the interface.  This causes the
	 * interface to properly reset its memory so that we don't have bogus
	 * data sitting in it.
	 */
	sprintf(Buf, "%lc", BaseHC);
	X10SetBaseHC(Buf);

	return;
}

 /*
  * look at the character passed in and decide if it's a valid house code or
  * not.  translate it into the appripriate index value for use with the
  * controller unit if it's valid.  else return -1.
  */

int X10GetHC(UBYTE Char)
{
	if (isalpha(Char)) {
		Char = tolower(Char);

		if (Char >= 'a' && Char <= 'p')
			return Char - 'a';
	}

	return -1;
}

 /* build an x10 direct command to turn a specified unit on, off, etc. */

BOOL X10SendDirectCmd(int HC, int Unit, ULONG Function, ULONG DimLevel)
{
	UBYTE Buf[22];
	int unitByte;

	memset(Buf, 0xFF, 16);
	Buf[16] = 1;
	Buf[17] = ((DimLevel & 0x0F) << 4L) | (Function & 0x0F);
	Buf[18] = HouseCode[HC];

	if (Unit < 8) {
		Buf[19] = 0;
		unitByte = 20;
	}
	else {
		Buf[20] = 0;
		unitByte = 19;
		Unit -= 8;
	}

	Buf[unitByte] = 1L << (7 - Unit);

	Buf[21] = Buf[17] + Buf[18] + Buf[19] + Buf[20];

	ClearSerial();
	SerWrite(Buf, 22);
	return X10WaitForAck(TRUE);
}

 /*
  * wait for the ack message from the controller unit.  we return the ack's
  * status bit as either true or false
  */

BOOL X10WaitForAck(BOOL WaitForUploadReport)
{
	int SYNCs;
	UWORD raw;

	/* look for ack to our command */
	for (SYNCs = 0; SYNCs < 16; SYNCs++) {
		raw = SerGetRawChar(15);

		if (raw == TIMEOUT)
			return FALSE;

		if (raw != 0xFF)
			SYNCs = 255;
	}

	if (WaitForUploadReport) {
		/* look for upload report */
		for (SYNCs = 0; SYNCs < 16; SYNCs++) {
			raw = SerGetRawChar(15);

			if (raw == TIMEOUT)
				return FALSE;

			if (raw != 0xFF)
				SYNCs = 255;
		}

		/* suck the remaining characters in the upload report */
		do {
			raw = SerGetRawChar(1);
		} while (raw != TIMEOUT);
	}

	return TRUE;
}
