/*
 * SpeakTime_Cx.c - Commodity version 1.2 of SpeakTime.c
 *
 * Copyright (c) 1994  Gary Walker.
 *
 * This program sends a string of phonetic text to the narrator
 * device to tell the local system time.
 *
 * Requires Kickstart V37 or greater.
 */

#include <exec/types.h>
#include <exec/exec.h>
#include <exec/libraries.h>
#include <dos/dos.h>
#include <devices/narrator.h>
#include <libraries/commodities.h>
#include "clib/translator_protos.h"
#include <clib/exec_protos.h>
#include <clib/alib_protos.h>
#include <clib/dos_protos.h>
#include <clib/alib_stdio_protos.h>
#include <clib/commodities_protos.h>
#include <string.h>
#include "stdlib.h"
#include <time.h>
#include "SpeakTime_Cx_protos.h"

#ifdef LATTICE
int CXBRK(void) { return(0); }    /* Disable SAS CTRL/C handling */
int chkabort(void) { return(0); } /* really */
#endif

extern struct Library *SysBase;   /* Used to check the OS version number */

#define EVT_HOTKEY 1L
#define BUFLEN 500
UBYTE PhonBuffer[BUFLEN];
STRPTR hourmin;
char sentence[100];
char numbers[7];

void main(int, char **);
void ProcessMsg(void);

struct Library *CxBase, *IconBase, *TranslatorBase;
struct MsgPort *broker_mp;
CxObj *broker, *filter, *sender, *translate;

struct NewBroker newbroker = {
    NB_VERSION,
    "SpeakTime HotKey",           /* string to identify this broker */
    "A Simple HotKey",
    "A simple hot key commodity",
    NBU_UNIQUE | NBU_NOTIFY,    /* Don't want any new commodities starting with this name. */
    0, 0, 0, 0                  /* If someone tries it, let me know */
};

ULONG cxsigflag;

void main(int argc, char **argv)
{
	UBYTE *hotkey, **ttypes;
	CxMsg *msg;

   /* Check the version number of the operating system */
if(SysBase->lib_Version>=36)
   {
    if (CxBase = OpenLibrary("commodities.library", 37L))
    {
        /* open the icon.library for the support library */
        /* functions, ArgArrayInit() and ArgArrayDone()  */
        if (IconBase = OpenLibrary("icon.library", 36L))
        {
            if (broker_mp = CreateMsgPort())
            {
                newbroker.nb_Port = broker_mp;
                cxsigflag = 1L << broker_mp->mp_SigBit;

                /* ArgArrayInit() is a support library function (from the 2.0 version
                 * of amiga.lib) that makes it easy to read arguments from either a
                 * CLI or from Workbench's ToolTypes.  Because it uses icon.library,
                 * the library has to be open before calling this function.
                 * ArgArrayDone() cleans up after this function.
                 */
                ttypes = ArgArrayInit(argc, argv);

                /* ArgInt() (also from amiga.lib) searches through the array set up
                 * by ArgArrayInit() for a specific ToolType.  If it finds one, it
                 * returns the numeric value of the number that followed the
                 * ToolType (i.e., CX_PRIORITY=7). If it doesn't find the ToolType,
                 * it returns the default value (the third argument)
                 */
                newbroker.nb_Pri = (BYTE)ArgInt(ttypes, "CX_PRIORITY", 0);

                /* ArgString() works just like ArgInt(), except it returns a pointer to a string
                 * rather than an integer. In the example below, if there is no ToolType
                 * "HOTKEY", the function returns a pointer to "rawkey control esc".
                 */
                hotkey = ArgString(ttypes, "HOTKEY", "rawkey alt z");

                if (broker = CxBroker(&newbroker, NULL))
                {
                    /* CxFilter() is a macro that creates a filter CxObject.  This filter
                     * passes input events that match the string pointed to by hotkey.
                     */
                    if (filter = CxFilter(hotkey))
                    {
                        /* Add a CxObject to another's personal list */
                        AttachCxObj(broker, filter);

                        /* CxSender() creates a sender CxObject.  Every time a sender gets
                         * a CxMessage, it sends a new CxMessage to the port pointed to in
                         * the first argument. CxSender()'s second argument will be the ID
                         * of any CxMessages the sender sends to the port.  The data pointer
                         * associated with the CxMessage will point to a *COPY* of the
                         * InputEvent structure associated with the orginal CxMessage.
                         */
                        if (sender = CxSender(broker_mp, EVT_HOTKEY))
                        {
                            AttachCxObj(filter, sender);

                            /* CxTranslate() creates a translate CxObject. When a translate
                             * CxObject gets a CxMessage, it deletes the original CxMessage
                             * and adds a new input event to the input.device's input stream
                             * after the Commodities input handler. CxTranslate's argument
                             * points to an InputEvent structure from which to create the new
                             * input event.  In this example, the pointer is NULL, meaning no
                             * new event should be introduced, which causes any event that
                             * reaches this object to disappear from the input stream.
                             */
                            if (translate = (CxTranslate(NULL)))
                            {
                                AttachCxObj(filter, translate);

                                /* CxObjError() is a commodities.library function that returns
                                 * the internal accumulated error code of a CxObject.
                                 */
                                if (! CxObjError(filter))
                                {
                                    ActivateCxObj(broker, 1L);
                                    ProcessMsg();
                                }
                            }
                        }
                    }

    }

                    /* DeleteCxObjAll() is a commodities.library function that not only
                     * deletes the CxObject pointed to in its argument, but it deletes
                     * all of the CxObjects that are attached to it.
                     */
                    DeleteCxObjAll(broker);

                    /* Empty the port of all CxMsgs */
                    while(msg = (CxMsg *)GetMsg(broker_mp))
                        ReplyMsg((struct Message *)msg);
                }
                DeletePort(broker_mp);
            }
            /* this amiga.lib function cleans up after ArgArrayInit() */
            ArgArrayDone();
            CloseLibrary(IconBase);
        }
        CloseLibrary(CxBase);
   }
else
   printf("Error: Release 2 (V36) or a later version of the OS required.\n");
}

void Sayit()
{
	struct  MsgPort     *VoiceMP;
	struct  narrator_rb *VoiceIO;
	BYTE    audio_chan[4]   = {3, 5, 10, 12};

	ThisTime();
	Phoneme();

      /* Create the message port */
   if (VoiceMP=CreateMsgPort())
      {
         /* Create the IORequest */
      if (VoiceIO = CreateIORequest(VoiceMP,sizeof(struct narrator_rb)))
         {
         /*   Set the NEWIORB bit in the flags field to use the new fields */
         VoiceIO->flags = NDF_NEWIORB;

            /* Open the narrator device */
         if (OpenDevice("narrator.device",0,(struct IORequest *)VoiceIO,0L))

            /* Inform user that it could not be opened */
            printf("Error: narrator.device did not open\n");

         else
            {
             /* Speak the string using the default parameters */
             VoiceIO->ch_masks = &audio_chan[0];
             VoiceIO->nm_masks = sizeof(audio_chan);
             VoiceIO->message.io_Command = CMD_WRITE;
             VoiceIO->message.io_Data = PhonBuffer;
             VoiceIO->message.io_Length = strlen(PhonBuffer);
             DoIO((struct IORequest *)VoiceIO);


            /* Close the narrator device */
            CloseDevice((struct IORequest *)VoiceIO);
            }
         /* Delete the IORequest */
         DeleteIORequest(VoiceIO);
         }
      else
         /* Inform user that the IORequest could be created */
         printf("Error: Could not create IORequest\n");

      /* Delete the message port */
      DeleteMsgPort(VoiceMP);
      }
   else
      /* Inform user that the message port could not be created */
      printf("Error: Could not create message port\n");
}

void ProcessMsg(void)
{
    extern struct MsgPort *broker_mp;
    extern CxObj *broker;
    extern ULONG cxsigflag;
    CxMsg *msg;
    ULONG sigrcvd, msgid, msgtype;
    LONG returnvalue = 1L;

    while(returnvalue)
    {
        sigrcvd = Wait(cxsigflag);

        while(msg = (CxMsg *)GetMsg(broker_mp))
        {
            msgid = CxMsgID(msg);
            msgtype = CxMsgType(msg);
            ReplyMsg((struct Message *)msg);

            switch(msgtype)
            {
                case CXM_IEVENT:
                    switch(msgid)
                    {
                        case EVT_HOTKEY: /* We got the message from the sender CxObject */
			    Sayit();
                            break;
                        default:
                            break;
                    }
                    break;
                case CXM_COMMAND:
                    switch(msgid)
                    {
                        case CXCMD_UNIQUE:
                        /* Commodities Exchange can be told not only to refuse to launch a
                         * commodity with a name already in use but also can notify the
                         * already running commodity that it happened. It does this by
                         * sending a CXM_COMMAND with the ID set to CXMCMD_UNIQUE.  If the
                         * user tries to run a windowless commodity that is already running,
                         * the user wants the commodity to shut down. */
                            returnvalue = 0L;
                            break;
                        default:
                            break;
                    }
                    break;
                default:
                    break;
            }
        }
    }
}

Phoneme()
{
	LONG EnglLen;
	LONG rtnCode;

	TranslatorBase = OpenLibrary("translator.library",37);
	if (TranslatorBase == NULL)
		{
		printf("No Translator Library available.\n");
		exit(0);
		}
	Makeword();
	EnglLen = strlen(sentence);
	rtnCode = Translate(sentence, EnglLen, (STRPTR)&PhonBuffer[0], BUFLEN);

	CloseLibrary(TranslatorBase);
	return(rtnCode);
}
STRPTR ThisTime()
{
	struct tm *p;
	long t;

	time(&t);
	p = localtime(&t);
	hourmin = asctime(p);
	return(hourmin);
}
void Makeword()
{
	int i,m,n,hourflag,ampm, pmdigits;

	for(i=0;i<=6;++i)
		{ numbers[i] = hourmin[i+11];
		}
	numbers[6] = NULL;
	strcpy(sentence, "Local time is ");
	m = numbers[0]-48;
	n = numbers[1]-48;
	hourflag = 1;
	ampm = 0;
	pmdigits = (m*10)+n;
	if (pmdigits>11) 
			{ ampm = 1;
			pmdigits=pmdigits-12;
			if (pmdigits==0) pmdigits=12;
			if (pmdigits>9)
				{
				n=pmdigits % 10;
				m=1;
				}
			else {
				m=0;
				n=pmdigits;
			     }
			}
	testdigit(m,n,ampm,hourflag);
	m = numbers[3]-48;
	n = numbers[4]-48;
	hourflag = 0;
	testdigit(m,n,ampm,hourflag);
}
void testdigit(m,n,ampm,hourflag)
{
	char *texts[]=
	{
	"ten", "alevehn", "twelve", "thir", "fif", "teeen", "twenty", "thirty", "forty", "fifty", "ayh m", "p m,"
	};
	char *nums[]= { "o", "1", "2", "3", "4", "5", "6", "7", "8", "9", ":", "00" };

	switch (m)
		{
	case 0:
		switch (n)
		{
		case 0:
			if (!hourflag) 
				{
				strcat(sentence, nums[11]);
				break;
				}
			if (ampm) break;
			strcat(sentence, texts[2]);
			break;
		default:
			if (!hourflag) strcat(sentence, nums[0]);
			strcat(sentence, nums[n]);
			break;
		}
		break;
	case 1:
		switch (n)
		{
		case 0:
			strcat(sentence, texts[0]);
			break;
		case 1:
			strcat(sentence, texts[1]);
			break;
		case 2:
			strcat(sentence, texts[2]);
			break;
		case 3:
			strcat(sentence, texts[3]);
			strcat(sentence, texts[5]);
			break;
		case 4:
			strcat(sentence, nums[4]);
			strcat(sentence, texts[5]);
			break;
		case 5:
			strcat(sentence, texts[4]);
			strcat(sentence, texts[5]);
			break;
		case 6:
			strcat(sentence, nums[6]);
			strcat(sentence, texts[5]);
			break;
		case 7:
			strcat(sentence, nums[7]);
			strcat(sentence, texts[5]);
			break;
		case 8:
			strcat(sentence, nums[8]);
			strcat(sentence, texts[5]);
			break;
		case 9:
			strcat(sentence, nums[9]);
			strcat(sentence, texts[5]);
			break;
		}
		break;
	case 2:
		strcat(sentence, texts[6]);
		if (n) strcat(sentence, nums[n]);
		break;
	case 3:
		strcat(sentence, texts[7]);
		if (n) strcat(sentence, nums[n]);
		break;
	case 4:
		strcat(sentence, texts[8]);
		if (n) strcat(sentence, nums[n]);
		break;
	case 5:
		strcat(sentence, texts[9]);
		if (n) strcat(sentence, nums[n]);
		break;
		}
	strcat(sentence, nums[10]);
	if (!ampm && !hourflag)  
		{
		strcat(sentence, texts[10]);
		strcat(sentence, nums[10]);
		}
	else if (!hourflag)
		{
		strcat(sentence, texts[11]);
		strcat(sentence, nums[10]);
		}

}
