/*    
 *	File:		SoupDrink.c
 * 
 *	Contains:	Demonstration of the Newton Desktop Integration Libraries (DILs)
 *              Communication DILs (CDILs) and High Level Frames DILs (FDILs)
 *
 *              This is the MacOS version of SoupDrink.
 *              For the Windows code, see the file "SOUPDRNK.C".
 *
 *              Nearly all of the DIL code can be found in the file "engine.c".
 *              The only interesting DIL code in here can be found near the 
 *              top - in SetupPortMenu and InitializePipe
 * 
 *	Written by:	Rob Langhorne, J. Christopher Bell, and David Fedor
 * 
 *	Copyright:	© 1995-1996 by Apple Computer, Inc.  All rights reserved.
 *
 *  Disclaimer: This is nearly all MacOS-specific code.  It is not intended
 *              to be an example of good MacOS sample code :-)  but it works to
 *              demonstrate & test how the Newton DILs work.
 *
 */ 

#include <Types.h>
#include <memory.h>
#include <Packages.h>
#include <Errors.h>
#include <quickdraw.h>
#include <fonts.h>
#include <dialogs.h>
#include <windows.h>
#include <menus.h>
#include <events.h>
#include <OSEvents.h>
#include <Desk.h>
#include <diskinit.h>
#include <OSUtils.h>
#include <resources.h>
#include <toolutils.h>
#include <AppleEvents.h>
#include <string.h>
#include <Strings.h>
#include <stdio.h>

#define forMac  // this is a Macintosh-only source file...
#include "DILCPipe.h"
#include "HLFDIL.h"
#include "AppDialogs.h"
#include "SoupDrink.h"
#include "Engine.h"



/******	Function prototypes  *******/
void 			EventLoop(void) ;
Boolean			DoMenuItem(long val) ;
void 			DoDiskEvents(long dinfo) ;
void 			DoDeskAccCall(MenuHandle themenu, long theit) ;
void 			DisableExpertFuncs(void);
void 			EnableExpertFuncs(void);


/********	Global variables **********/

// UI stuff
MenuHandle		gAppleMenuHandle, gFileMenuHandle, gEditMenuHandle, gTestHandle, gPortHandle, gFuncMenuHandle;
Boolean 		gQuit;					// Flags for application state
int				gNumPorts;				// how many ports are in our port menu

// Configuration options set through menus
long			gWhichPort,				// the number in the menu of which port to use
				gWhichTool;				// Selector for which communications tool to use
Boolean 		gEncrypt = false,		// Toggles for advanced reading and writing
				gByteSwap = false,
				gUseUnicode = false,
				gExpertMode = false;

// the following are globals set in here, used in Engine.c
void			*ourPipe;
void			*gThisObject;				/* This is the only DIL frame active at one time in this app */
int				gInputMode;					/* drink mode (receive frames) or spit mode (receive strings) */
char			gReceivedString[kMAXSTR]; 	/* received string; used in 'spit mode' */
long			gReceivedStringLen;			/* received string length (see above) */
char 			gTempBuffer [256];			/* a global string buffer for input dialogs, etc */


// SetupPortMenu() demonstrates finding the available ports available on your machine.
// Different model MacOS computers have different ports, and the port names can be 
// localized into other languages.
//
// The port names are gotten from the Communications Toolbox and placed directly into
// menu items.  Note that InitCRM() is not called - that is called by the DILs as part
// of CDInitCDIL().  (Future versions of the DILs might not call it, though - watch
// for more information on this.)
//
// SoupDrink has a "config" menu to which this function adds port names.  When it comes
// time to call CDPipeInit, the port name is taken directly out of the menu.
//
// The basis for this routine was stolen from:
// http://dev.info.apple.com/source/code/Snippets/Communications_Toolbox/FindSerialPorts/ReadMe.html

#include <CommResources.h>
#include <CRMSerialDevices.h>

void SetupPortMenu()
{
    CRMRec			c;
    CRMRecPtr		cPtr = &c;
    CRMSerialPtr	serialPtr;
        
    gNumPorts = 0;
    c.crmDeviceType = crmSerialDevice;
    c.crmDeviceID   = 0;
    while (cPtr != nil) {
        cPtr = (CRMRecPtr)CRMSearch(&c);
        if (cPtr) {
            serialPtr = (CRMSerialPtr)cPtr->crmAttributes;
            AppendMenu(gPortHandle, *(serialPtr->name));	// this is a Pascal-style string, which is what AppendMenu wants.
            gNumPorts++;
            c.crmDeviceID = cPtr->crmDeviceID;
        }
    }
   	gWhichPort = kFirstPortItem;				// default to the first port in the list
   	if (gNumPorts)
	   	CheckItem(gPortHandle, gWhichPort, true);	// put a checkmark next to it.
}


/*
 * InitializePipe
 *
 * Purpose: This function sets up port options for the CDIL function called CDPipeInit.
 *
 * Note that if you do not "dispose" of the connection, the port can be locked and you will
 * get the "port busy" error which will make life difficult for both you and users. It would
 * be best if the function which Initializes the pipe takes responsibility for disposing of
 * of the pipe.
 */
OSErr InitializePipe () 
{
	OSErr	fErr;
	char	Port[100];

	// Copy the text for the port right out of the menu item.
	// It'll look like "Modem Port" but could be localized into another language!
	getitem(gPortHandle, gWhichPort, Port);
	
	switch (gWhichTool) {
		case kSerialTool:
			fErr = CDPipeInit(ourPipe, "Serial","","Baud 38400 dataBits 8 Parity None Port ", Port, kDefaultBufferSize, kDefaultBufferSize);	
			break;
		case kModemTool:
			fErr = CDPipeInit(ourPipe, "Modem","","ModemType \"Newton Serial Connection\" dataBits 8 Parity None Baud 38400 Port ", Port, kDefaultBufferSize, kDefaultBufferSize);	
			break;
		case kADSPTool:
			fErr = CDPipeInit(ourPipe, "ADSP","My Desktop Mac","LocalADSPType \"SoupDrink\" RegisterName 1 LocalADSPName ", Port, kDefaultBufferSize, kDefaultBufferSize);	
			break;
		case kIRTool:
			fErr = CDPipeInit(ourPipe, "IR","","", Port, kDefaultBufferSize, kDefaultBufferSize);	
			break;
		}
	
	return fErr;
}

void ListenCallback( CommErr errorValue, long refCon )
// This is only used by the "expert" mode, to test asynchronous listening...
{
	long length=5;
	
	if (errorValue)
		PostAlertMessage("CDPipeAccept asynch returned:", ErrorStrings((int) errorValue, gTempBuffer), "", "");
	else {
		errorValue = CDPipeAccept(ourPipe) ;
		if (errorValue)
			PostAlertMessage("CDPipeAccept asynch returned:", ErrorStrings((int) errorValue, gTempBuffer), "", "");
		else {
			errorValue = CDPipeWrite(ourPipe, (void *)"asdf\4", &length, true,0,0,600,0,0);		// will generate an error on the Newton side
			if (errorValue)
				PostAlertMessage("CDPipeWrite asynch returned:", ErrorStrings((int) errorValue, gTempBuffer), "", "");
		}
	}
}


/*
 * encryptProc
 *
 * encryptProc is the encryption callback function
 */
static void encryptProc ( void *pData, Size Count, long refCon ) 
{
	Str255 refConStr;
	char * localStr;
	
	
	localStr = (char *) NewPtrClear(Count + 1);
	if (!localStr)
		return;
		
	strcpy(localStr, (char *)pData);
	sprintf((char*) refConStr, "%d", refCon);
	PostAlertMessage("We have encrypted:", (char*) localStr, " Reference constant: ", (char*) refConStr);
}


/*
 * decryptProc
 *
 * decryptProc is the decryption callback function
 */
static void decryptProc ( void *pData, Size Count, long refCon ) 
{
	Str255 refConStr;
	char * localStr;
	
	localStr = (char *)NewPtrClear(Count + 1);
	if (!localStr)
		return;

	strcpy(localStr,(char *) pData);
	NumToString(Count, (unsigned char*) gTempBuffer);
	sprintf((char*) refConStr, "%d", refCon);
	PostAlertMessage("We have encrypted:", localStr, "Reference constant: ",(char*) refConStr);
}


/* StateStrings
 * 
 * Purpose: return state string based on state number from the CDILs
 * (as a pascal string)
 * note, if theErr is greater than 50, it is a user CDIL error
 */
char* StateStrings(int theState, Str255 theString)
{
	char* s;
	char theNum[128];
	
	switch (theState) {
		case kCDIL_Uninitialized: 		s = "CDIL is uninitialized."; break;
		case kCDIL_InvalidConnection:	s = "CDIL tried to bring up connection, but it failed."; break;
		case kCDIL_Disconnected:  		s = "CDIL is disconnected."; break;
		case kCDIL_Listening:			s = "CDIL is listening for a connection."; break;
		case kCDIL_ConnectPending:		s = "A CDIL connection is pending."; break;
		case kCDIL_Connected:			s = "CDIL is connected."; break;
		case kCDIL_Busy:				s = "CDIL is busy (either reading or writing)."; break;
		case kCDIL_Aborting:			s = "CDIL is aborting."; break;
		case kCDIL_Startup:				s = "CDIL is starting up."; break;
		default:  s = (theState < 50) ? "Unknown CDIL state." : "(User-defined CDIL state)";
		};
		
	strcpy((char*) theString, s);
	strcat((char*) theString, ": State #");
	NumToString(theState, (unsigned char*) theNum);
	strcat((char*) theString,  (char*) p2cstr((unsigned char*)theNum));

	return (( char*) theString);
}


/*
 *	Main
 * 
 *	This is the entry point for this application.
 */
void main(void)
{
	CommErr			fErr;
 	CursHandle 		W;			/*	Cursor handle to get cursors	*/
    DialogPtr		splash;		/*	For use in splash screen */
	Handle 			MyMenu;           		/* my menu bar handle */
	Cursor			myWatchCursor ;			/* The watch "busy" cursor */

#ifdef DEFINE_QD
// This is needed for some compilers...
    QDGlobals		qd;
#endif

    /* 	Start out with the standard Macintosh initializations */
    MaxApplZone();
    InitGraf((Ptr)&qd.thePort);
    InitFonts();
    InitWindows();
    InitMenus();
    TEInit();
    InitDialogs(nil);
    InitCursor();
    MoreMasters();
    MoreMasters();
    MoreMasters();
		
 	fErr = CDInitCDIL() ; 
	if (fErr)  {
		PostAlertMessage("Error initializing CDIL", ErrorStrings((int) fErr, gTempBuffer), "", "");
		return;
	}
    
	// Initialize the libraries themselves (does not make a connection)
	fErr = FDInitFDIL();
	if (fErr)  {
		PostAlertMessage("Error initializing FDIL", ErrorStrings((int) fErr, gTempBuffer), "", "");
		return;
	}

    // Set up the menus
    MyMenu = GetNewMBar(kMBarID);
    SetMenuBar(MyMenu);
    
    gAppleMenuHandle = GetMenuHandle(kAppleMenu);
    gFileMenuHandle = GetMenuHandle(kFileMenu);
    gEditMenuHandle = GetMenuHandle(kEditMenu);
    gTestHandle = GetMenuHandle(kTestMenu);
    gPortHandle = GetMenuHandle(kPortMenu);
    gFuncMenuHandle = GetMenuHandle(kFnMenu);
    
	// add desk accessories to the apple menu
	AppendResMenu(gAppleMenuHandle, 'DRVR');
		
	SetupPortMenu();
	if (gNumPorts==0){
		PostAlertMessage("Error getting serial port names", "", "", "");
		return;
	}
	
	CheckItem(gPortHandle, kSerialTool, false);
	CheckItem(gPortHandle, kModemTool, true);		// default to the modem tool
	CheckItem(gPortHandle, kADSPTool, false);
	CheckItem(gPortHandle, kIRTool, false);
	gWhichTool = kModemTool;
	
	DisableExpertFuncs();

    DrawMenuBar();

    // Turn on the watch cursor while the splash screen is showing
	W = GetCursor (watchCursor);
    if (W)
		myWatchCursor = **W;

  	SetCursor((CursPtr)&myWatchCursor);
  	ShowCursor();
	
    ShowSplash(&splash);

	ourPipe = CDCreateCDILObject();
	if (!ourPipe) {
		PostAlertMessage("Error instantiating CDIL", "", "", "");
		return;
	}

	HideSplash(splash);
	HideCursor();
	SetCursor((const Cursor *)&qd.arrow);
	ShowCursor();

	EventLoop ();

	fErr = CDPipeDisconnect (ourPipe);
	
	CDDisposeCDILObject( ourPipe ) ;
	CDDisposeCDIL() ;
#ifdef useASLM
	CleanupLibraryManager();
#endif
}

/*
 * EventLoop
 *
 * Purpose: This is the main Macintosh event loop processor.
 *          This function must handle any type of Macintosh event
 *          like mouse clicks and keyboard events.
 */
void EventLoop ( void )
{
	EventRecord 	gERecord;				/* Current Macintosh Toolbox event record */
	WindowPtr	twindow;
	short		part;
	
     do {
        CDIdle(ourPipe);
        WaitNextEvent(everyEvent, &gERecord, 30, nil);
		
        switch (gERecord.what) {
            case mouseDown:
                /* first see where the hit was */
				part = FindWindow(gERecord.where, &twindow);
                switch (part) {
                    
                    case inDesk:                            /* if they hit in desk, then the process manager */
                        break;                              /* will switch us out, we don't need to do anything */

                    case inMenuBar:
                        DoMenuItem(MenuSelect(gERecord.where));
                        break;
                        
                    case inSysWindow:	/* pass to the system */
                        SystemClick(&gERecord, twindow);
                        break;
						
                    case inContent:	/*	This app doesn't do windows	*/
                    case inDrag:
                    case inGrow:
                    case inGoAway:
					case inZoomIn:							
					case inZoomOut:
                        break;
		
                }
				break;

            case keyUp:				/* don't care */
            case mouseUp:			/* don't care */
            case networkEvt:		/* don't care */
            case driverEvt:			/* don't care */
            case kHighLevelEvent:	/*	AppleEvents	*/
                break;

            case keyDown:
            case autoKey:
                if (gERecord.modifiers & cmdKey)
                    DoMenuItem(MenuKey((short)gERecord.message & charCodeMask));
				else
					SysBeep(2);
                break;

            case diskEvt:
                /* We don't do anything special for disk events, this just passes them
                 * to a function that checks for an error on the mount
                 */
                DoDiskEvents((long)gERecord.message);
                break;

            default:
                break;
                
        }
    } while (gQuit != true);
}

/*
 * DoDeskAccCall
 *
 * DoDeskAccCall opens the requested DA
 */

void DoDeskAccCall(MenuHandle themenu, long theit)
{
    Str255 DAname;
    
    GetMenuItemText(themenu, (short)theit, DAname);
    OpenDeskAcc(DAname);
}	/*	end DoDeskAccCall	*/

/*
 * DoDiskEvents
 *
 * DoDiskEvents performs disk mount operations
 */
void DoDiskEvents(long dinfo)    /* hi word is error code, lo word is drive number */
{
    short theHighWord, theLowWord, hdl;
    Point mountpoint =  {
        40, 40
    };
    
    theHighWord = HiWord(dinfo);
    theLowWord = LoWord(dinfo);
    if (theHighWord != noErr)/* something happened */ {
        hdl = DIBadMount(mountpoint, dinfo);
    }
}	

/*
 * DoMenuItem
 *
 * DoMenuItem processes a menu event
 */

Boolean DoMenuItem(long val)
{
    short 				theLowWord, theHighWord;
	CommErr				fErr = 0;
	Str255				stateString;
	CDIL_State			state;

    theLowWord = LoWord(val);
    theHighWord = HiWord(val);
    
    switch (theHighWord) {                                  /* switch off the menu number selected */
        case kAppleMenu:                            		/* Apple menu */
            if (theLowWord != 1)         					/* if this was not About, it's a DA */
                DoDeskAccCall(gAppleMenuHandle, theLowWord);
            else 
                Alert(kAboutDialog, nil);                 /* do about box */
            break;
			
        case kFileMenu:										/* File menu */
            gQuit = true;									/* only edit item */
            break;

        case kEditMenu:										/* don't care */
            SysBeep(1);  /* in this release, we don't do anything */
            break;

        case kTestMenu:										/*	Test suites	*/
        	switch(theLowWord) {
				case kSoupDrinkItem:
					fErr = SoupDrink();
					if (fErr)
						PostAlertMessage("Error from Soup Drink", ErrorStrings((int) fErr, gTempBuffer), "", "");
					break;
				
				case kNewNameItem:							/* Create a new Names Soup entry on the Newton */
					fErr = UploadNewName();
					if (fErr)
						PostAlertMessage("Error from UploadNewName", ErrorStrings((int) fErr, gTempBuffer), "", "");
					break;
						
        		default:									/* Sanity check. Shouldn't get here */
            		SysBeep(1);  /* in this release, we don't do anything */
            		break;
        	}
			break;
			
        case kPortMenu:	//	Choose a port…
         	switch(theLowWord) {
				case kSerialTool:
				case kModemTool:
				case kADSPTool:
				case kIRTool:
					CheckItem(gPortHandle, kSerialTool, (theLowWord==kSerialTool));
					CheckItem(gPortHandle, kModemTool, (theLowWord==kModemTool));
					CheckItem(gPortHandle, kADSPTool, (theLowWord==kADSPTool));
					CheckItem(gPortHandle, kIRTool, (theLowWord==kIRTool));
					gWhichTool = theLowWord;
					break;

				default: {
					int i;
					
					for (i=0; i<gNumPorts; i++)
						CheckItem(gPortHandle, kFirstPortItem + i, (theLowWord== kFirstPortItem + i));
					gWhichPort = theLowWord;
					break;
				}
        	} 
			break;
			
		case kFnMenu: // Choose a function …
         	switch(theLowWord) {
				case kFInitItem:
					fErr = InitializePipe();
					if (fErr)
						PostAlertMessage("InitializePipe returned:", ErrorStrings((int) fErr, gTempBuffer), "", "");
					break;
					
				case kFListenItem:
					fErr = CDPipeListen(ourPipe, kDefaultTimeout, (CDILCompletionProcPtr)ListenCallback, 0) ;
					if (fErr)
						PostAlertMessage("CDPipeListen returned:", ErrorStrings((int) fErr, gTempBuffer), "", "");
					break;
					
				case kFAcceptItem:
					fErr = CDPipeAccept(ourPipe) ;
					if (fErr)
						PostAlertMessage("CDPipeAccept returned:", ErrorStrings((int) fErr, gTempBuffer), "", "");
					break;
					
				case kFAbortItem:
					fErr = CDPipeAbort(ourPipe, kAllPipes) ;
					if (fErr)
						PostAlertMessage("CDPipeAbort returned:", ErrorStrings((int) fErr, gTempBuffer), "", "");
					break;
					
				case kFDiscItem:
					fErr = CDPipeDisconnect(ourPipe) ;
					if (fErr)
						PostAlertMessage("CDPipeDisconnect returned:", ErrorStrings((int) fErr, gTempBuffer), "", "");
					break;
					
				case kFReadItem:
            		SysBeep(1);  /* in this release, we don't do anything */
					break;
					
				case kFCountItem:
            		SysBeep(1);  /* in this release, we don't do anything */
					break;
					
				case kFWriteItem:
            		SysBeep(1);  /* in this release, we don't do anything */
					break;
					
				case kFGetStateItem:
					state = CDGetPipeState(ourPipe);

					PostAlertMessage("Current state:", StateStrings(state, stateString), "", "");
					break;
					
				case kFSetStateItem: /* Set State number (for example, kCDIL_Busy) */
            		SysBeep(1);  /* in this release, we don't do anything */
					break;

				case kToggleEncryption:
					if (gEncrypt) {
						CDEncryptFunction(ourPipe, (CDILEncryptionProcPtr) nil, 0 ) ;
						CDDecryptFunction(ourPipe, (CDILDecryptionProcPtr) nil, 0 ) ;
						gEncrypt = false ;
					}
					else {
						CDEncryptFunction(ourPipe, (CDILEncryptionProcPtr) encryptProc, 100 ) ;
						CDDecryptFunction(ourPipe,  (CDILDecryptionProcPtr) decryptProc, 100 ) ;
						gEncrypt = true ;
					}	
					CheckItem(gFuncMenuHandle, kToggleEncryption, gEncrypt);
					
					break;
					
				case kToggleByteSwap:
					if (gByteSwap)
						gByteSwap = false ;
					else
						gByteSwap = true ;
						
					CheckItem(gFuncMenuHandle, kToggleByteSwap, gByteSwap);
					break;
					
				case kToggleUnicode:
					if (gUseUnicode)
						gUseUnicode = false ;
					else
						gUseUnicode = true ;
						
					CheckItem(gFuncMenuHandle, kToggleUnicode, gUseUnicode);
					break;
				case kExpertModeToggle:
					if (gExpertMode) {
						gExpertMode = false ;
						DisableExpertFuncs();
						}
					else {
						gExpertMode = true ;
						EnableExpertFuncs();
						}
						
					CheckItem(gFuncMenuHandle, kExpertModeToggle, gExpertMode);
					break;

        		default:	
            		SysBeep(1);  //	Sanity check; shouldn't get here
            		break;
			}
			
    }
    HiliteMenu(0);						// Unhilite the menu label itself
	return noErr;
}	/*	end DoMenuItem	*/


void EnableExpertFuncs(void) {
	EnableItem(gFuncMenuHandle,kFInitItem);
	EnableItem(gFuncMenuHandle,kFListenItem);
	EnableItem(gFuncMenuHandle,kFAcceptItem);
	EnableItem(gFuncMenuHandle,kFAbortItem);
	EnableItem(gFuncMenuHandle,kFDiscItem);
	// in this release, these items are not supported...
	//EnableItem(gFuncMenuHandle,kFReadItem);
	//EnableItem(gFuncMenuHandle,kFCountItem);
	//EnableItem(gFuncMenuHandle,kFWriteItem);
	//EnableItem(gFuncMenuHandle,kFSetStateItem);
	EnableItem(gFuncMenuHandle,kToggleEncryption);
	EnableItem(gFuncMenuHandle,kToggleByteSwap);
	EnableItem(gFuncMenuHandle,kToggleUnicode);
}

void DisableExpertFuncs(void) {
	DisableItem(gFuncMenuHandle,kFInitItem);
	DisableItem(gFuncMenuHandle,kFListenItem);
	DisableItem(gFuncMenuHandle,kFAcceptItem);
	DisableItem(gFuncMenuHandle,kFAbortItem);
	DisableItem(gFuncMenuHandle,kFDiscItem);
	DisableItem(gFuncMenuHandle,kFReadItem);
	DisableItem(gFuncMenuHandle,kFCountItem);
	DisableItem(gFuncMenuHandle,kFWriteItem);
	DisableItem(gFuncMenuHandle,kFSetStateItem);
	DisableItem(gFuncMenuHandle,kToggleEncryption);
	DisableItem(gFuncMenuHandle,kToggleByteSwap);
	DisableItem(gFuncMenuHandle,kToggleUnicode);
}


long CurrentTimeInSeconds()
// return something indicating what time it is, in seconds units
// This is only used to compare one time against another; it does
// not need to have any relationship to the time of day.
{
	return (TickCount() / 60);
}
