#define __USE_SYSBASE	1
#include <clib/alib_protos.h>
#include <exec/alerts.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <intuition/intuitionbase.h>
#include <libraries/cybergraphics.h>
#include <proto/commodities.h>
#include <proto/cybergraphics.h>
#include <proto/dos.h>
#include <proto/exec.h>
#include <proto/intuition.h>
#include <proto/icon.h>
#include <proto/utility.h>
#include <workbench/startup.h>
#include <string.h>


/* Compiler specific stuff. */
#define SAVEDS	__saveds
#define ASM	__asm
#define REG(x)	register __ ## x
#define STKARGS	__stdargs

#define VERSION	"1.0"
#define DATE	"9.5.97"


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


struct NewBroker	NewBroker =
{
	NB_VERSION,
	"CGXDPMS",
	"CGXDPMS " VERSION "by Magnus Holmgren",
	"DPMS manager for CyberGraphX",
	NBU_UNIQUE, 0, 0,
	NULL, 0
};

struct Library		*CxBase;
struct Library		*CyberGfxBase;
struct DosLibrary	*DOSBase;
struct Library		*IconBase;
struct IntuitionBase	*IntuitionBase;
struct ExecBase		*SysBase;
struct Library		*UtilityBase;

struct MsgPort		*CxPort;
struct WBStartup	*WBMsg;
CxObj			*Broker, *Sender, *Custom;

LONG	StandbyTime,    SuspendTime,    OffTime, IETime;
LONG	StandbyCounter, SuspendCounter, OffCounter;
LONG	SendShift, NoMouse, State, FinalState;

struct InputEvent	ShiftDown, ShiftUp;


#define DEF_CXPRI	1
#define DEF_STANDBY	20
#define DEF_SUSPEND	20
#define DEF_OFF		20
#define DEF_SENDSHIFT	1

#define ID_SENDER	49
#define ID_CUSTOM	17
#define MAGIC	( APTR ) 87654321L

struct Args
{
	LONG	*CxPriority;
	LONG	*StandbyTime;
	LONG	*SuspendTime;
	LONG	*OffTime;
	LONG	*ShiftTime;
	LONG	SendShift;
	LONG	NoMouse;
};

#define TEMPLATE "CX_PRIORITY/K/N,STANDBYTIME/K/N,SUSPENDTIME/K/N,OFFTIME/K/N,SHIFTTIME/K/N,SENDSHIFT/S,NOMOUSE/S"


/******************** Startup code ********************/


static LONG	Main( VOID );


SAVEDS LONG
Startup( VOID )
{
	LONG	r;

	SysBase = *( ( struct ExecBase ** ) 4 );

	{
		struct Process	*thisTask = ( struct Process * ) FindTask( NULL );

		if( !thisTask->pr_CLI )
		{
			/* Handle Workbench startup message */
			struct MsgPort	*port = &thisTask->pr_MsgPort;

			WaitPort( port );
			WBMsg = ( struct WBStartup * ) GetMsg( port );
		}
	}

	SetSignal( 0, SIGBREAKF_CTRL_C );

	if( SysBase->LibNode.lib_Version >= 37 )
	{
		r = Main();
	}
	else
	{
		r = 100;
	}

	if( WBMsg )
	{
		Forbid();
		ReplyMsg( ( struct Message * ) WBMsg );
	}

	return( r );
}


/******************** String constants ********************/


static const TEXT Ver[]	= "$VER: CGXDPMS " VERSION " (" DATE ")";


/******************** Misc ********************/


static LONG
MyRequest( STRPTR title, STRPTR body, STRPTR gads, ... )
{
	struct EasyStruct	es;

	es.es_StructSize = sizeof( es );
	es.es_Flags = 0;
	es.es_Title = title;
	es.es_TextFormat = body;
	es.es_GadgetFormat = gads;
	return( EasyRequestArgs( NULL, &es, NULL, &gads + 1 ) );
}


static VOID
ErrRequest( STRPTR body, ... )
{
	if( WBMsg )
	{
		MyRequest( "CGXDPMS error message", body, "Quit", &body + 1 );
	}
	else
	{
		VPrintf( body, &body + 1 );
	}
}


/******************** Libs ********************/


static struct Library *
OpenLib( STRPTR name, LONG ver )
{
	struct Library	*lib;

	if( !( lib = OpenLibrary( name, ver ) ) )
	{
		ErrRequest( "Couldn't open %s\nversion %ld or higher!", name, ver );
	}

	return( lib );
}


#define CloseLib(lib)	CloseLibrary( ( struct Library * ) lib )


static VOID
CloseLibs( VOID )
{
	if( DOSBase )
	{
		CloseLib( DOSBase );
		CloseLib( IntuitionBase );
		CloseLib( UtilityBase );
		CloseLib( CyberGfxBase );
		CloseLib( CxBase );
	}
}


static BOOL
OpenLibs( VOID )
{
	BOOL	rc = FALSE;

	if( DOSBase = ( struct DosLibrary * ) OpenLibrary( "dos.library", 37 ) )
	{
		if( IntuitionBase = ( struct IntuitionBase * ) OpenLibrary( "intuition.library", 37 ) )
		{
			if( ( UtilityBase = OpenLib( "utility.library", 37 ) )
				&& ( CyberGfxBase = OpenLib( "cybergraphics.library", 40 ) )
				&& ( CxBase = OpenLib( "commodities.library", 37 ) ) )
			{
				rc = TRUE;
			}
		}
		else
		{
			Alert( AT_Recovery | AN_Unknown | AG_OpenLib | AO_Intuition );
		}
	}
	else
	{
		Alert( AT_Recovery | AN_Unknown | AG_OpenLib | AO_DOSLib );
	}

	return( rc );
}


/******************** Argument parsing ********************/


static LONG
GetTimeTT( const struct DiskObject *icon, STRPTR name, LONG def )
{
	STRPTR	str;
	LONG	val;

	val = def;

	if( str = FindToolType( icon->do_ToolTypes, name ) )
	{
		if( StrToLong( str, &val ) == -1 )
		{
			MyRequest( "CGXDMPS Warning", "Argument for %s should be numeric.\nUsing default (%ld).", name, def );
			val = def;
		}
	}

	return( SMult32( val, 60 ) );
}


static BOOL
ParseWBArgs( VOID )
{
	BOOL	rc = FALSE;

	if( IconBase = OpenLib( "icon.library", 37 ) )
	{
		struct DiskObject	*icon;
		BPTR	oldDir;

		oldDir = CurrentDir( WBMsg->sm_ArgList[ 0 ].wa_Lock );

		/* Lazy parsing: Ignore any multiselected icons... */
		if( icon = GetDiskObject( WBMsg->sm_ArgList[ 0 ].wa_Name ) )
		{
			NewBroker.nb_Pri = GetTimeTT( icon, "CX_PRIORITY", DEF_CXPRI );
			StandbyTime      = GetTimeTT( icon, "STANDBYTIME", DEF_STANDBY );
			SuspendTime      = GetTimeTT( icon, "SUSPENDTIME", DEF_SUSPEND );
			OffTime          = GetTimeTT( icon, "OFFTIME",     DEF_OFF );
			IETime           = GetTimeTT( icon, "SHIFTTIME",   DEF_SENDSHIFT );
			SendShift        = ( LONG ) FindToolType( icon->do_ToolTypes, "SENDSHIFT" );
			NoMouse          = ( LONG ) FindToolType( icon->do_ToolTypes, "NOMOUSE" );
			rc = TRUE;

			FreeDiskObject( icon );
		}
		else
		{
			ErrRequest( "Couldn't get program icon!" );
		}

		CurrentDir( oldDir );
		CloseLibrary( IconBase );
	}
	else
	{
		ErrRequest( "Couldn't open icon.library\nversion 37 or higher!" );
	}

	return( rc );
}


static LONG
GetTime( LONG *val, LONG def )
{
	LONG	rc;

	rc = def;

	if( val )
	{
		rc = *val;
	}

	return( SMult32( rc, 60 ) );
}


static BOOL
ParseCLIArgs( VOID )
{
	struct Args	args;
	struct RDArgs	*rdarg;
	BOOL	rc = FALSE;

	memset( &args, 0, sizeof( args ) );

	if( rdarg = ReadArgs( TEMPLATE, ( LONG * ) &args, NULL ) )
	{
		NewBroker.nb_Pri = GetTime( args.CxPriority,  DEF_CXPRI );
		StandbyTime      = GetTime( args.StandbyTime, DEF_STANDBY );
		SuspendTime      = GetTime( args.SuspendTime, DEF_SUSPEND );
		OffTime          = GetTime( args.OffTime,     DEF_OFF );
		IETime           = GetTime( args.ShiftTime,   DEF_SENDSHIFT );
		SendShift        = args.SendShift;
		NoMouse          = args.NoMouse;
		rc = TRUE;

		FreeArgs( rdarg );
	}

	return( rc );
}


static BOOL
ParseArgs( VOID )
{
	return( ( BOOL ) ( WBMsg ? ParseWBArgs() : ParseCLIArgs() ) );
}


/******************** Commodity support ********************/


/* This function runs on the input.device task context. Thus, we need to
 * set smalldata base, make sure we don't call any nasty functions, and
 * generally try to do our job quickly.
 */
static SAVEDS STKARGS VOID
CxCustomFunc( register CxMsg *msg, CxObj *obj )
{
	struct InputEvent	*ie;
	static LONG	LastSecs, LastIE;
	LONG		diff;

	ie = ( struct InputEvent * ) CxMsgData( msg );

	switch( ie->ie_Class )
	{
		case IECLASS_RAWMOUSE:
		case IECLASS_POINTERPOS:
		case IECLASS_NEWPOINTERPOS:
			if( NoMouse && ( ie->ie_Code == IECODE_NOBUTTON ) )
			{
				/* Ignore mouse movements */
				break;
			}

		case IECLASS_RAWKEY:
			if( ie->ie_position.ie_addr == MAGIC )
			{
				/* Ignore rawkeys we send out */
				break;
			}

		case IECLASS_DISKREMOVED:
		case IECLASS_DISKINSERTED:
			StandbyCounter = StandbyTime;
			SuspendCounter = SuspendTime;
			OffCounter     = OffTime;

			if( State != DPMS_ON )
			{
				State = DPMS_ON;
				RouteCxMsg( msg, Sender );
			}

			break;

		case IECLASS_TIMER:
			diff = ie->ie_TimeStamp.tv_secs - LastSecs;

			if( ( diff > 0 ) && ( State != FinalState ) && LastSecs )
			{
				if( ( State < DPMS_STANDBY ) && ( StandbyCounter > 0 ) )
				{
					StandbyCounter -= diff;

					if( StandbyCounter <= 0 )
					{
						State = DPMS_STANDBY;
						RouteCxMsg( msg, Sender );
					}
				}
				else if( ( State < DPMS_SUSPEND ) && ( SuspendCounter > 0 ) )
				{
					SuspendCounter -= diff;

					if( SuspendCounter <= 0 )
					{
						State = DPMS_SUSPEND;
						RouteCxMsg( msg, Sender );
					}
				}
				else if( ( State < DPMS_OFF ) && ( OffCounter > 0 ) )
				{
					OffCounter -= diff;

					if( OffCounter <= 0 )
					{
						State = DPMS_OFF;
						RouteCxMsg( msg, Sender );
					}
				}

				if( SendShift && ( State > DPMS_ON ) )
				{
					diff = ie->ie_TimeStamp.tv_secs - LastIE;

					if( diff > IETime )
					{
						LastIE = ie->ie_TimeStamp.tv_secs;
						ShiftDown.ie_TimeStamp = ie->ie_TimeStamp;
						ShiftUp.ie_TimeStamp   = ie->ie_TimeStamp;

						/* Hm.. Maybe this would be better to do on the
						 * main process' context. Oh well, not called
						 * often anyway. ;)
						 */
						AddIEvents( &ShiftDown );
					}
				}
			}

			LastSecs = ie->ie_TimeStamp.tv_secs;
			break;
	}
}


static VOID
FreeBroker( VOID )
{
	DeleteCxObjAll( Broker );
	DeleteMsgPort( CxPort );
}


static BOOL
InitBroker( VOID )
{
	BOOL	rc = FALSE;

	ShiftUp.ie_Class = ShiftDown.ie_Class = IECLASS_RAWKEY;
	ShiftDown.ie_Code = 0x60,
	ShiftUp.ie_Code = IECODE_UP_PREFIX | 0x60;
	ShiftUp.ie_position.ie_addr = ShiftDown.ie_position.ie_addr = MAGIC;
	ShiftDown.ie_NextEvent = &ShiftUp;

	StandbyCounter = StandbyTime;
	SuspendCounter = SuspendTime;
	OffCounter     = OffTime;
	State          = DPMS_ON;

	if( StandbyTime )
	{
		FinalState = DPMS_STANDBY;
	}
	else if( SuspendTime )
	{
		FinalState = DPMS_SUSPEND;
	}
	else
	{
		FinalState = DPMS_OFF;
	}

	if( CxPort = NewBroker.nb_Port = CreateMsgPort() )
	{
		LONG	err;

		if( Broker = CxBroker( &NewBroker, &err ) )
		{
			AttachCxObj( Broker, Custom = CxCustom( CxCustomFunc, ID_CUSTOM ) );
			AttachCxObj( Custom, Sender = CxSender( CxPort, ID_SENDER ) );

			if( CxObjError( Broker ) || CxObjError( Custom ) )
			{
				ErrRequest( "Not enough memory for Commodities objects!" );
			}
			else
			{
				rc = TRUE;
			}
		}
		else if( err != CBERR_DUP )
		{
			ErrRequest( "Couldn't create broker!" );
		}
	}
	else
	{
		ErrRequest( "Couldn't create message port!" );
	}

	if( !rc )
	{
		FreeBroker();
	}

	return( rc );
}


static VOID
HandleEvent( VOID )
{
	struct Screen	*wb;

	/* Configurable screen name? Maybe for 1.1... */
	if( wb = LockPubScreen( "Workbench" ) )
	{
		/* Passing args via globals isn't the best perhaps, but it works. ;) */
		CVideoCtrlTags( &wb->ViewPort,
			SETVC_DPMSLevel,	State,
		TAG_DONE );

		UnlockPubScreen( NULL, wb );
	}
}


static BOOL
HandleCxPort( VOID )
{
	CxMsg	*msg;
	BOOL	run = TRUE;

	while( msg = ( CxMsg * ) GetMsg( CxPort ) )
	{
		LONG	id, type;

		id = CxMsgID( msg );
		type = CxMsgType( msg );
		ReplyMsg( ( struct Message * ) msg );

		switch( type )
		{
			case CXM_IEVENT:
				HandleEvent();
				break;

			case CXM_COMMAND:
				switch( id )
				{
					case CXCMD_DISABLE:
						ActivateCxObj( Broker, FALSE );
						break;

					case CXCMD_ENABLE:
						ActivateCxObj( Broker, TRUE );
						break;

					case CXCMD_KILL:
						run = FALSE;
						break;
				}

				break;
		}
	}

	return( run );
}


/******************** Main program ********************/


static VOID
MainLoop( VOID )
{
	LONG	cxMask = 1 << CxPort->mp_SigBit;
	BOOL	run = TRUE;

	ActivateCxObj( Broker, TRUE );

	while( run )
	{
		LONG	sigs;

		sigs = Wait( cxMask | SIGBREAKF_CTRL_C );

		if( sigs & cxMask )
		{
			if( !HandleCxPort() )
			{
				run = FALSE;
			}
		}

		if( sigs & SIGBREAKF_CTRL_C )
		{
			run = FALSE;
		}
	}
}


static LONG
Main( VOID )
{
	LONG	ret = RETURN_FAIL;

	if( OpenLibs() )
	{
		if( ParseArgs() )
		{
			if( InitBroker() )
			{
				MainLoop();
				ret = RETURN_OK;

				FreeBroker();
			}
		}

		CloseLibs();
	}

	return( ret );
}
