#include <clib/alib_protos.h>
#include <cybergraphics/cybergraphics.h>
#include <exec/execbase.h>
#include <exec/memory.h>
#include <intuition/intuitionbase.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 <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.2"
#define DATE	"9.10.97"


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


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

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

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

struct InputEvent	ShiftDown;
struct InputEvent	ShiftUp;

TEXT	BlankKey[ 128 ];

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

#define ID_SENDER	1
#define ID_CUSTOM	2
#define ID_BLANK	3
	/* Don't know if this is a good value for ie.dead... They key code
	 * should be for a non-existant key. Oh well, doesn't cause Enforcer
	 * hits for me at least. ;)
	 */
#define MAGIC	( APTR ) 81008100L

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

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


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


static LONG	Main( VOID );


LONG SAVEDS
Startup( VOID )
{
	LONG	r = 100;

	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();
	}

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

	return( r );
}


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


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


/* ----- Misc ------------------------------------------------------------- */


static LONG
MyRequestA( STRPTR title, STRPTR body, STRPTR gads, APTR args )
{
	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, args ) );
}


static LONG
MyRequest( STRPTR title, STRPTR body, STRPTR gads, ... )
{
	return( MyRequestA( title, body, gads, &gads + 1 ) );
}


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


/* ----- 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( CyberGfxBase );
		CloseLib( CxBase );
	}
}


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

	if( DOSBase = ( struct DosLibrary * ) OpenLibrary( "dos.library", 37 ) )
	{
		if( IntuitionBase = ( struct IntuitionBase * ) OpenLibrary( "intuition.library", 37 ) )
		{
			if( ( CyberGfxBase = OpenLib( "cybergraphics.library", 40 ) )
				&& ( CxBase = OpenLib( "commodities.library", 37 ) ) )
			{
				rc = TRUE;
			}
		}
	}

	return( rc );
}


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


static VOID
GetBlankKey( STRPTR str )
{
	if( str )
	{
		strncpy( BlankKey, str, sizeof( BlankKey ) - 1 );
	}
}


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

	val = def;

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

	return( val );
}


static LONG
GetTimeTT( struct DiskObject *icon, STRPTR name, LONG def )
{
	/* return( SMult32( GetNumTT( icon, name, def ), 60 ) ); */
	return( GetNumTT( icon, name, def ) * 60 );
}


static LONG
ParseWBArgs( VOID )
{
	LONG	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 = GetNumTT( icon, "CX_PRIORITY", DEF_CXPRI );
			GetBlankKey( FindToolType( ( UBYTE ** ) icon->do_ToolTypes, "BLANKKEY" ) );
			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( ( UBYTE ** ) icon->do_ToolTypes, "SENDSHIFT" );
			NoMouse          = ( LONG ) FindToolType( ( UBYTE ** ) 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
GetNum( LONG *val, LONG def )
{
	LONG	rc;

	rc = def;

	if( val )
	{
		rc = *val;
	}

	return( rc );
}


static LONG
GetTime( LONG *val, LONG def )
{
	/* return( SMult32( GetNum( val, def ), 60 ) ); */
	return( GetNum( val, def ) * 60 );
}


/* SAS/C insists on using in an optimized memset (that also is quite large)
 * when compiling for a 68020+. Not what we want here, since we only want
 * to clear less than 100 bytes *once*. So we have our own simple one
 * instead.
 */
static VOID
ClrMem( UBYTE *buf, LONG size )
{
	do
	{
		*buf++ = 0;
	}
	while( --size );
}


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

	ClrMem( ( UBYTE * ) &args, sizeof( args ) );

	if( rdarg = ReadArgs( TEMPLATE, ( LONG * ) &args, NULL ) )
	{
		NewBroker.nb_Pri = GetNum( args.CxPriority,  DEF_CXPRI );
		GetBlankKey( args.BlankKey );
		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 LONG
ParseArgs( VOID )
{
	return( ( LONG ) ( WBMsg ? ParseWBArgs() : ParseCLIArgs() ) );
}


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


static VOID
Unblank( CxMsg *msg )
{
	StandbyCounter = StandbyTime;
	SuspendCounter = SuspendTime;
	OffCounter     = OffTime;

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


/* 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 STKARGS VOID SAVEDS
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:
			/* Ignore mouse movements while blanked? */
			if( !( NoMouse && ( State != DPMS_ON ) && ( ie->ie_Code == IECODE_NOBUTTON ) ) )
			{
				Unblank( msg );
			}

			break;

		case IECLASS_RAWKEY:
			/* Ignore rawkeys we send out, as well as key releases
			 * (to make the hotkey possible).
			 */
			if( !( ( ie->ie_position.ie_addr == MAGIC ) || ( ie->ie_Code & IECODE_UP_PREFIX ) ) )
			{
				Unblank( msg );
			}

			break;

		case IECLASS_DISKREMOVED:
		case IECLASS_DISKINSERTED:
			Unblank( msg );
			break;

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

			/* Check for blanking at most once per second, if needed */
			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. At most once per minute. ;)
						 */
						AddIEvents( &ShiftDown );
					}
				}
			}

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


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


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

	ShiftUp.ie_Class = ShiftDown.ie_Class = IECLASS_RAWKEY;
	ShiftDown.ie_Code = 0x60,	/* Shift (Left, I think) */
	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;
	}

	if( SuspendTime )
	{
		FinalState = DPMS_SUSPEND;
	}

	if( OffTime )
	{
		FinalState = DPMS_OFF;
	}

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

		if( Broker = CxBroker( &NewBroker, &err ) )
		{
			if( *BlankKey )
			{
				AttachCxObj( Broker, HotKey( BlankKey, CxPort, ID_BLANK ) );
			}

			AttachCxObj( Broker, Custom = CxCustom( CxCustomFunc, ID_CUSTOM ) );
			AttachCxObj( Custom, Sender = CxSender( CxPort, ID_SENDER ) );

			if( CxObjError( Broker ) == COERR_BADFILTER )
			{
				ErrRequest( "Bad hotkey description for BLANKKEY!\n('%s')", BlankKey );
			}
			else 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
HandleDPMS( VOID )
{
	struct Screen	*wb;

	/* Configurable screen name? Maybe for a future version... */
	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 VOID
HandleKey( VOID )
{
	if( State == DPMS_ON )
	{
		/* Maybe we should use Semaphores or something here to prevent
		 * any odd behaviour. Not that likely to happen though (I
		 * think ;), and even if it does happen, the effects are
		 * really minor (not going to the "right" state).
		 */
		if( StandbyTime )
		{
			StandbyCounter = 1;
		}
		else if( SuspendTime )
		{
			SuspendCounter = 1;
		}
		else if( OffTime )
		{
			OffCounter = 1;
		}
	}
}


static LONG
HandleCxPort( VOID )
{
	CxMsg	*msg;
	LONG	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:
				switch( id )
				{
					case ID_SENDER:
						HandleDPMS();
						break;

					case ID_BLANK:
						HandleKey();
						break;
				}

				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;

	ActivateCxObj( Broker, TRUE );

	for( ;; )
	{
		LONG	sigs;

		sigs = Wait( cxMask | SIGBREAKF_CTRL_C );

		if( sigs & cxMask )
		{
			if( !HandleCxPort() )
			{
				break;
			}
		}

		if( sigs & SIGBREAKF_CTRL_C )
		{
			break;
		}
	}
}


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

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

				FreeBroker();
			}
		}

		CloseLibs();
	}

	return( ret );
}
