#include <proto/exec.h>
#include <proto/dos.h>
#include <string.h>
#include <stdlib.h>
#include <time.h>
#include <dos/dostags.h>
#include <dos/stdio.h>

#include <cl/lists.h>
#include <cl/chatserver.h>
#include "rev.h"

char version[] = { "$VER: CLChat-ChatBot " VERTAG };

static int Done;
static time_t starttime;

static struct MsgPort *serverport, *myreplyport, *myport, *ipcport;
static APTR myid;

struct ipcmsg {
	struct Message m;
	char data[ 256 ];
};

long __stack = 8192;

struct usernode {
	struct MinNode n;
	char username[ 256 ];	// Already ParsedPattern
	char nick[ 32 ];	// Current nick
	int pri;
	ULONG autoop;
	char comment[ 256 ];
};

struct nsn {
	struct MinNode n;
	char nick[ 40 ];
};

struct cmd {
	struct MinNode n;
	char cmd[ 40 ];
	int pri;
	char exec[ 256 ];
};

struct pat {
	struct MinNode n;
	char pat[ 256 ];
	char exec[ 256 ];
	time_t lastused;
};

struct MinList userlist;
struct MinList nickscanlist;
struct MinList cmdlist;
struct MinList patlist;

static char *mynick;

static int shutup;

static UWORD fmtfunc[] = { 0x16c0, 0x4e75 };
void __stdargs sprintf( char *to, char *fmt, ... )
{
	RawDoFmt( fmt, &fmt + 1, (APTR)fmtfunc, to );
}

static void __stdargs sendmsg( char *fmt, ... )
{
	struct chatmsg *svm = malloc( sizeof( *svm ) );
	svm->cmd = CM_DATA;
	svm->id = myid;
	svm->m.mn_ReplyPort = myreplyport;
	
	RawDoFmt( fmt, &fmt + 1, (APTR)fmtfunc, svm->data );
	PutMsg( serverport, (struct Message*)svm );
}

static int findnick( char *nick, int remove )
{
	struct nsn *n;

	for( n = FIRSTNODE( &nickscanlist ); NEXTNODE( n ); n = NEXTNODE( n ) )
	{
		if( !stricmp( n->nick, nick ) )
		{
			if( remove )
			{
				REMOVE( n );
				free( n );
			}
			return( TRUE );
		}
	}
	return( FALSE );
}

static void addnicktoscanlist( char *nick )
{
	struct nsn *nsn;

	if( findnick( nick, FALSE ) )
		return;
	
	nsn = malloc( sizeof( *nsn ) );
	strcpy( nsn->nick, nick );
	ADDTAIL( &nickscanlist, nsn );
}

//
//	Format der Userliste:
//  Username Pri AutoopYN Comment
//

static void clnl( char *s )
{
	s = stpbrk( s, "\r\n" );
	if( s )
		*s = 0;
}

static void loadusers( void )
{
	BPTR f = Open( "S:ChatBot.users", MODE_OLDFILE );
	char buffer[ 512 ], *p;
	struct usernode *un;

	NEWLIST( &userlist );

	if( !f )
		return;

	while( FGets( f, buffer, 256 ) )
	{
		clnl( buffer );
		if( buffer[ 0 ] == ';' || !buffer[ 0 ] || buffer[ 0 ] == '#' )
			continue;

		un = malloc( sizeof( *un ) );
		if( !un )
			break;

		memset( un, 0, sizeof( *un ) );

		p = strtok( buffer, " \t" );
		if( !p )
			continue;
		ParsePatternNoCase( p, un->username, 256 );

		ADDTAIL( &userlist, un );

		p = strtok( NULL, " \t" );
		if( !p )
			continue;
		un->pri = atoi( p );

		p = strtok( NULL, " \t" );
		if( !p )
			continue;
		if( *p == 'Y' || *p == 'y' )
			un->autoop = TRUE;

		p = strtok( NULL, "" );
		if( !p )
			continue;

		strcpy( un->comment, p );
	}
	Close( f );
}

static void loadpatterns( void )
{
	BPTR f = Open( "S:ChatBot.patterns", MODE_OLDFILE );
	char buffer[ 512 ], *p;
	struct pat *pn;

	NEWLIST( &patlist );

	if( !f )
		return;

	while( FGets( f, buffer, 512 ) )
	{
		clnl( buffer );
		if( buffer[ 0 ] == ';' || !buffer[ 0 ] )
			continue;

		pn = malloc( sizeof( *pn ) );
		if( !pn )
			break;

		memset( pn, 0, sizeof( *pn ) );

		p = strtok( buffer, "  \t" );
		if( !p )
			continue;
		ParsePatternNoCase( p, pn->pat, 256 );

		p = strtok( NULL, "·" );
		if( !p )
			continue;

		strcpy( pn->exec, p );
		ADDTAIL( &patlist, pn );
	}
	Close( f );
}


static void loadcmd( void )
{
	BPTR f = Open( "S:ChatBot.cmds", MODE_OLDFILE );
	char buffer[ 256 ], *p;
	struct cmd *un;

	NEWLIST( &cmdlist );

	if( !f )
		return;

	while( FGets( f, buffer, 256 ) )
	{
		clnl( buffer );
		if( buffer[ 0 ] == ';' || !buffer[ 0 ] || buffer[ 0 ] == '#' )
			continue;

		un = malloc( sizeof( *un ) );
		if( !un )
			break;

		memset( un, 0, sizeof( *un ) );

		p = strtok( buffer, "  \t" );
		if( !p )
			continue;
		strcpy( un->cmd, p );

		p = strtok( NULL, "  \t" );
		if( !p )
			continue;
		un->pri = atoi( p );

		p = strchr( p, 0 ) + 1;
		strcpy( un->exec, stpblk( p ) );

		ADDTAIL( &cmdlist, un );
	}
	Close( f );
}

static struct cmd *findcmd( char *name )
{
	struct cmd *cmd;

	for( cmd = FIRSTNODE( &cmdlist ); NEXTNODE( cmd ); cmd = NEXTNODE( cmd ) )
		if( !stricmp( cmd->cmd, name ) )
			return( cmd );
	return( NULL );
}


static void scanuser( char *nick, char *uid )
{
	struct usernode *un;
	char *p2;

	if( !uid || *uid++ != '[' )
		return;

	p2 = strpbrk( uid, "\t " );
	if( !p2 )
		return;
	*p2++ = 0;

	// Ok, uid enthält nun den usernamen zum nick
	for( un = FIRSTNODE( &userlist ); NEXTNODE( un ); un = NEXTNODE( un ) )
	{
		if( MatchPatternNoCase( un->username, uid ) )
		{
			strcpy( un->nick, nick );

			if( findnick( un->nick, TRUE ) )
			{
				if( un->autoop )
					sendmsg( "/OP %s", nick );
				if( un->comment[ 0 ] )
					sendmsg( "%s", un->comment );
			}
			return;
		}
	}
}

static int getuserpri( char *nick )
{
	struct usernode *un;

	for( un = FIRSTNODE( &userlist ); NEXTNODE( un ); un = NEXTNODE( un ) )
	{
		if( !stricmp( nick, un->nick ) )
			return( un->pri );
	}
	return( 0 );
}

static void killnick( char *nick )
{
	struct usernode *un;

	for( un = FIRSTNODE( &userlist ); NEXTNODE( un ); un = NEXTNODE( un ) )
	{
		if( !stricmp( nick, un->nick ) )
		{
			un->nick[ 0 ] = 0;
			return;
		}
	}
}

static void __stdargs answer( char *user, int priv, char *fmt, ... )
{
	char buffer[ 512 ];

	if( user )
	{
		if( priv )
		{
			sprintf( buffer, "/QMSG %s ", user );
		}
		else
		{
			sprintf( buffer, "%s: ", user );
		}
	}
	else
		buffer[ 0 ] = 0;

	RawDoFmt( fmt, &fmt + 1, (APTR)fmtfunc, strchr( buffer, 0 ) );
	sendmsg( buffer );
}

static void exec( char *fromuser, int priv, char *exec, char *parms )
{
	char xb[ 512 ];
	BPTR ofh;

	if( parms && strpbrk( parms, "><" ) )
		parms = NULL;

	sprintf( xb, exec, parms ? parms : "", "", "", "" );

	ofh = Open( "T:cbtmp", MODE_NEWFILE );

	SystemTags( xb,
		SYS_Output, ofh,
		TAG_DONE
	);

	Seek( ofh, 0, OFFSET_BEGINNING );
	Flush( ofh );

	while( FGets( ofh, xb, 255 ) )
	{
		clnl( xb );
		answer( fromuser, priv, "%s", xb );
		Delay( 25 );
	}
	Close( ofh );
	//DeleteFile( "T:cbtmp" );
}

static void handlecmd( char *fromuser, int priv, char *cmd )
{
	char *p = stpbrk( cmd, "  \t," );
	struct cmd *cmn;
	int pri = getuserpri( fromuser );

	if( p )
		*p++ = 0;

	if( !stricmp( cmd, "STATUS" ) )
	{
		answer( fromuser, priv, "Your status is %ld.", pri );
		return;
	}
	else if( !stricmp( cmd, "VERSION" ) || !stricmp( cmd, "*!VER" ) )
	{
		answer( fromuser, priv, "VERSION CLChat-ChatBot/AmigaOS " VERTAG, pri );
		return;
	}
	else if( !stricmp( cmd, "UPTIME" ) )
	{
		time_t t = time( NULL ) - starttime;

		answer( fromuser, priv, "I'm up for %ldd, %ldh, %ldm, %lds",
			t / ( 3600 * 24 ),
			( t / 3600 ) % 24,
			( t / 60 ) % 60,
			( t ) % 60
		);

	}
	else if( !stricmp( cmd, "SHUTUP" ) )
	{
		answer( fromuser, priv, "Ok, I'm quite now." );
		shutup = TRUE;
	}
	else if( !stricmp( cmd, "TALK" ) )
	{
		answer( fromuser, priv, "Ok, I'm talking again." );
		shutup = FALSE;
	}
	else if( !stricmp( cmd, "QUIT" ) && pri > 90 )
	{
		Done = TRUE;
		return;
	}
	else if( !stricmp( cmd, "RELOAD" ) && pri > 90 )
	{
		loadusers();
		loadcmd();
		loadpatterns();
		answer( fromuser, priv, "Reload of configuration complete!" );
		sendmsg( "/USERS" );
		return;
	}
	else if( !stricmp( cmd, "EMIT" ) && pri >= 99 )
	{
		sendmsg( "%s", p );
		return;
	}
	else if( !strnicmp( cmd, "*!PING", 6 ) && priv )
	{
		sendmsg( "/QMSG %s *!PONG%s", fromuser, &cmd[ 6 ] );
		return;
	}
	else if( !strnicmp( cmd, "*!-CLFTREQ", 10 ) && priv )
	{
		sendmsg( "/QMSG %s You can't send files to me!", fromuser );
		return;
	}
	else if( !strnicmp( cmd, "\001DCC", 4 ) && priv )
	{
		sendmsg( "/QMSG %s I can't accept DCC requests!", fromuser );
		return;
	}
	else if( !stricmp( cmd, "?" ) || !stricmp( cmd, "HELP" ) )
	{
		char replybuffer[ 512 ];

		strcpy( replybuffer, "Status, Version, Uptime" );
		if( pri > 90 )
			strcat( replybuffer, ", Quit, Reload, Shutup, Talk" );
		if( pri >= 99 )
			strcat( replybuffer, ", Emit" );

		answer( fromuser, priv, "Commands available for status %ld:", pri );

		for( cmn = FIRSTNODE( &cmdlist ); NEXTNODE( cmn ); cmn = NEXTNODE( cmn ) )
		{
			if( cmn->pri > pri )
				continue;
			if( replybuffer[ 0 ] )
				strcat( replybuffer, ", " );
			strcat( replybuffer, cmn->cmd );
			if( strlen( replybuffer ) > 256 )
			{
				answer( fromuser, priv, replybuffer );
				replybuffer[ 0 ] = 0;
			}
		}
		if( replybuffer[ 0 ] )
			answer( fromuser, priv, replybuffer );
	}
	else
	{
		cmn = findcmd( cmd );
		if( !cmn || cmn->pri > pri )
		{
			if( stricmp( fromuser, cmd ) )
			{
				answer( fromuser, priv, "Unknown command \"%s\". Try \"/MSG %s HELP\" for a list of commands.",
					cmd, mynick
				);
			}
			return;

		}

		// Kommando ausführen
		exec( fromuser, priv, cmn->exec, p );
	}
}

static void findpat( char *pat )
{
	struct pat *pn;

	if( shutup )
		return;

	for( pn = FIRSTNODE( &patlist ); NEXTNODE( pn ); pn = NEXTNODE( pn ) )
	{
		if( MatchPatternNoCase( pn->pat, pat ) )
		{
			time_t t;

			time( &t );

			if( t - pn->lastused > 10 )
			{
				exec( NULL, 0, pn->exec, pat );
				pn->lastused = time( NULL );
			}
			break;
		}
	}
}

void main( int argc, char **argv )
{
	char buffer[ 512 ], oldbuffer[ 512 ], *p, *p2, *p3;
	char hostname[ 64 ], ipcportname[ 128 ];
	static struct chatmsg *loginmsg;
	int c;

	NEWLIST( &nickscanlist );

	GetVar( "HOSTNAME", hostname, 64, 0 );

	if( argc < 3 )
	{
		PutStr( "Usage: CHATBOT <nick> <realname> [channel to join]\n" );
		exit( 20 );
	}

	sprintf( ipcportname, "CHATBOT_%s", argv[ 1 ] );
	strupr( ipcportname );

	for( c = 0; c < 30; c++ )
	{
		serverport = FindPort( CPNAME );
		if( serverport )
			break;
		Delay( 50 );
	}
	if( !serverport )
	{
		PutStr( "Unable to find Chat Server.\n" );
		exit( 20 );
	}

	myreplyport = CreateMsgPort();
	myport = CreateMsgPort();

	loginmsg = malloc( sizeof( *loginmsg ) );
	sprintf( loginmsg->data, "%s %s@%s %s", mynick = argv[ 1 ], mynick, hostname, argv[ 2 ] );
	loginmsg->id = myport;
	loginmsg->cmd = CM_LOGIN;
	loginmsg->m.mn_ReplyPort = myreplyport;
	PutMsg( serverport,  (struct Message*)loginmsg );
	WaitPort( myreplyport );
	GetMsg( myreplyport );
	if( loginmsg->cmd )
	{
		Printf( "Chat login failed: %ld\n", loginmsg->cmd );
		DeleteMsgPort( myreplyport );
		DeleteMsgPort( myport );
		exit( 10 );
	}
	myid = loginmsg->id;
	free( loginmsg );
	loadusers();
	loadcmd();
	loadpatterns();

	time( &starttime );

	if( argc > 3 )
		sendmsg( "/JOIN %s", argv[ 3 ] );

	ipcport = CreatePort( ipcportname, -32 );

	while( !Done )
	{
		struct ipcmsg *ipcmsg;

		while( loginmsg = (struct chatmsg*)GetMsg( myreplyport ) )
			free( loginmsg );

		while( ipcmsg = GetMsg( ipcport ) ) 
		{
			sendmsg( "%s", ipcmsg->data );
			ReplyMsg( ipcmsg );
		}

		if( !( loginmsg = (struct chatmsg*)GetMsg( myport ) ) )
		{
			Wait( ( 1L<<myport->mp_SigBit ) | ( 1L << ipcport->mp_SigBit ) | ( 1L << myreplyport->mp_SigBit ) );
			continue;
		}

		if( loginmsg->cmd != CM_DATA )
		{
			ReplyMsg( loginmsg );
			break;
		}
		strcpy( buffer, loginmsg->data );
		ReplyMsg( loginmsg );

		if( CheckSignal( SIGBREAKF_CTRL_C ) )
			break;

		Printf( "%s\n", buffer );
		//clnl( buffer );
		if( !buffer[ 0 ] )
			continue;	// Tick

		// Parsen
		if( buffer[ 0 ] == '[' )
		{
			p = strchr( buffer, ']' );
			if( !p )
				continue;
			*p++ = 0;
			p = stpblk( p );
			p2 = stpbrk( p, "\t " );
			if( p2 )
			{
				*p2++ = 0;
				p3 = stpbrk( p2, "\t " );
				if( p3 )
					*p3++ = 0;
			}

			// Server-Cmd in Buffer
			if( !stricmp( &buffer[ 1 ], "USERS" ) )
			{
				// WHOIS schicken, um Userliste upzudaten
				if( *p == '!' || *p == '@' || *p == '>' )
				{
					Delay( 20 );
					sendmsg( "/WHOIS %s", p + 1 );
					continue;
				}
			}
			else if( !stricmp( &buffer[ 1 ], "WHOIS" ) )
			{
				if( !stricmp( p, "Nick" ) )
				{
					scanuser( p2, p3 );
					Delay( 15 );
				}				
			}
			else if( !stricmp( &buffer[ 1 ], "JOIN" ) || !stricmp( &buffer[ 1 ], "ENTER" ) )
			{
				addnicktoscanlist( p );
				sendmsg( "/USERS" );
			}
			else if( !stricmp( &buffer[ 1 ], "EXIT" ) )
			{
				killnick( p );
			}
			else if( !stricmp( &buffer[ 1 ], "NICK" ) )
			{
				p3 = strrchr( p3, ' ' );
				if( !p3 )
					continue;
				*p3++ = 0;
				p2 = strchr( p3, '.' );
				if( p2 )
					*p2 = 0;

				sendmsg( "/WHOIS %s", p3 );
			}
		}
		else if( buffer[ 0 ] == '<' )	// Priv
		{
			p = strchr( buffer, '>' );
			if( !p )
				continue;
			*p++ = 0;
			handlecmd( &buffer[ 1 ], TRUE, stpblk( p ) );
		}
		else // Öffentlich, 
		{
			p = strchr( buffer, ':' );
			if( !p )
				continue;
			*p++ = 0;
			p = stpblk( p );

			strcpy( oldbuffer, p );

			if( !stricmp( p, "PING" ) )
			{
				sendmsg( "\002*PONG*" );
				continue;
			}

			p2 = stpbrk( p, "\t:,  " );
			if( !p2 )
			{
				if( strcmp( buffer, mynick ) )
					findpat( oldbuffer );
				continue;
			}
			*p2++ = 0;

			if( stricmp( p, mynick ) )
			{
				if( strcmp( buffer, mynick ) )
					findpat( oldbuffer );
				continue;
			}

			handlecmd( buffer, FALSE, stpblk( p2 ) );
		}
	}

	loginmsg = malloc( sizeof( *loginmsg ) );
	loginmsg->id = myid;
	loginmsg->cmd = CM_LOGOFF;
	loginmsg->m.mn_ReplyPort = myreplyport;
	strcpy( loginmsg->data, "Terminating..." );
	PutMsg( serverport,  (struct Message*)loginmsg ); 
	Delay( 50 );
	while( loginmsg = (struct chatmsg*)GetMsg( myport ) ) 
		ReplyMsg( loginmsg );
	DeleteMsgPort( myreplyport );
	DeleteMsgPort( myport );
	DeletePort( ipcport );
}
