#import "JubCLIChatUI.h"
#import "JubChatClient.h"
#import "JubCLIColor.h"
#import "JubCLICommand.h"
#import "JubCLIUI.h"

BEGINCLICOMMAND(JubCLIReplyCommand, @":r", nil,
    @"Sets the sender of the last incomming message as the default recipient")
{
	if (_ui.lastIn == nil) {
		[of_stdout writeLine: @"No message has been received yet"];
		return;
	}

	_ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: _ui.lastIn];
	[of_stdout writeFormat: @"Set sink to %@\n",
	    [_ui.lastIn.rosterItem.JID bareJID]];
}
ENDCLICOMMAND

BEGINCLICOMMAND(JubCLISetSinkCommand, @":s", @"<who>",
    @"Selects <who> as the default recipient")
{
	if ([parameters count] != 1) {
		[of_stdout writeLine: @"Syntax: ':s <who>'"];
		return;
	}

	OFString *param = parameters[0];
	XMPPContact *contact = _ui.client.contactManager.contacts[param];

	if (contact == nil) {
		[of_stdout writeFormat: @"Contact '%@' not found in your "
		    @"roster\n", param];
		return;
	}

	_ui.sink = (JubCLIChatUI*)[_ui.client chatForContact: contact];

	[of_stdout writeFormat: @"Set sink to %@\n", param];
}
ENDCLICOMMAND

BEGINCLICOMMAND(JubCLIMessageCommand, @":m", @"<who> <message>",
    @"Sends a single message to <who>")
{
	if ([parameters count] < 2) {
		[of_stdout writeLine: @"Syntax: ':m <who> <message>'"];
		return;
	}

	XMPPContact *contact =
	    _ui.client.contactManager.contacts[parameters[0]];

	if (contact == nil) {
		[of_stdout writeFormat: @"Contact %@ not found in your "
		    @"roster\n", parameters[0]];
		return;
	}

	JubCLIChatUI *chat =
	    (JubCLIChatUI*)[_ui.client chatForContact: contact];

	OFArray *message =
	    [parameters arrayByRemovingObject: [parameters firstObject]];

	[chat send: [message componentsJoinedByString: @" "]];
}
ENDCLICOMMAND

BEGINCLICOMMAND(JubCLIPresenceCommand, @":t", @"<status> [<message>]",
    @"Changes your presence")
{
	if ([parameters count] < 1) {
		[of_stdout writeLine:
		    @"Syntax: ':t <status> [<message>]'"];
		return;
	}

	XMPPPresence *presence;
	OFString *show = parameters[0];

	if (![@[ @"available", @"away", @"dnd", @"xa", @"chat", @"unavailable" ]
	    containsObject: show]) {
		[of_stdout writeLine: @"<status> must be one of:"
		    @"available, away, dnd, xa, chat, unavailable"];
		return;
	}

	if ([show isEqual: @"unavailable"])
		presence = [XMPPPresence presenceWithType: show];
	else
		presence = [XMPPPresence presence];

	if (![@[ @"available", @"unavailable" ] containsObject: show])
		presence.show = show;

	if ([parameters count] == 2) {
		[_ui.client.connection sendStanza: presence];
		return;
	}

	OFArray *message =
	    [parameters arrayByRemovingObject: [parameters firstObject]];
	presence.status = [message componentsJoinedByString: @" "];

	[_ui.client.connection sendStanza: presence];
}
ENDCLICOMMAND

BEGINCLICOMMAND(JubCLIRosterCommand, @":roster", nil, @"Shows your roster")
{
	OFDictionary *contacts = _ui.client.contactManager.contacts;
	for (OFString *key in contacts) {
		XMPPContact *contact = contacts[key];
		OFString *name = contact.rosterItem.name;
		XMPPPresence *presence =
		    [[[contact.presences allObjects] sortedArray] firstObject];

		if (name != nil)
			[of_stdout writeFormat: @"%@ <%@> (", name, key];
		else
			[of_stdout writeFormat: @"%@ (", key];

		if (presence == nil)
			[of_stdout writeFormat: COL_OFFLINE(@"offline")];
		else if ([presence.show isEqual: @"chat"])
			[of_stdout writeFormat: COL_CHAT(@"free for chat")];
		else if ([presence.show isEqual: @"away"])
			[of_stdout writeFormat: COL_AWAY(@"away")];
		else if ([presence.show isEqual: @"xa"])
			[of_stdout writeFormat: COL_XA(@"extended away")];
		else if ([presence.show isEqual: @"dnd"])
			[of_stdout writeFormat: COL_DND(@"do not disturb")];
		else
			[of_stdout writeFormat: COL_ONLINE(@"online")];

		[of_stdout writeString: @")\n"];
	}
}
ENDCLICOMMAND

@implementation JubCLIUI
@synthesize client = _client;
@synthesize lastIn = _lastIn;
@synthesize sink = _sink;

- initWithClient: (JubChatClient*)client
{
	self = [super init];

	@try {
		_commands =  [OFMutableDictionary new];
		_client = [client retain];
		_contactManager = client.contactManager;
		[_contactManager addDelegate: self];

		[self addCommand: [[[JubCLIReplyCommand alloc]
				       initWithCLIUI: self] autorelease]];

		[self addCommand: [[[JubCLISetSinkCommand alloc]
				       initWithCLIUI: self] autorelease]];

		[self addCommand: [[[JubCLIMessageCommand alloc]
				       initWithCLIUI: self] autorelease]];

		[self addCommand: [[[JubCLIPresenceCommand alloc]
				       initWithCLIUI: self] autorelease]];

		[self addCommand: [[[JubCLIRosterCommand alloc]
				       initWithCLIUI: self] autorelease]];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[_contactManager removeDelegate: self];
	[_client release];
	[_commands release];
	[super dealloc];
}

- (void)startUIThread
{
	[of_stdin asyncReadLineWithTarget: self
				 selector: @selector(Jub_userInputWithStream:
						     line:exception:)];
}

-      (void)client: (JubChatClient*)client
  didChangePresence: (XMPPPresence*)presence
{
}

- (Class)chatUIClass
{
	return [JubCLIChatUI class];
}

- (void)addCommand: (id<JubCLICommand>)command
{
	[_commands setObject: command
		      forKey: command.command];
}

- (BOOL)Jub_userInputWithStream: (OFStream*)stream
			   line: (OFString*)line
		      exception: (OFException*)exception
{
	if (line == nil)
		[OFApplication terminate];

	if ([line length] == 0)
		return YES;

	if ([line characterAtIndex: 0] != ':') {
		if (_sink == nil)
			[of_stdout writeLine: @"No default sink selected, "
			    @"type `:h` for help"];

		[_sink send: line];

		return YES;
	}

	line = [line stringByDeletingTrailingWhitespaces];

	OFArray *input= [line componentsSeparatedByString: @" "];

	if ([input[0] isEqual: @":h"]) {
		__block size_t longest = 0;

		[_commands enumerateKeysAndObjectsUsingBlock:
		    ^(OFString *key, id<JubCLICommand> command, BOOL *stop) {
			size_t length = [command.command length] +
			    (command.params == nil ? 0 :
				(1 + [command.params length]));

			if (length > longest)
				longest = length;
		}];

		for (OFString *key in [[_commands allKeys] sortedArray]) {
			id<JubCLICommand> command = _commands[key];
			size_t length = [command.command length] +
			    (command.params == nil ? 0 :
				(1 + [command.params length]));

			if (command.params == nil)
				[of_stdout writeFormat: @"`%@`",
				    command.command];
			else
				[of_stdout writeFormat: @"`%@ %@`",
				    command.command, command.params];

			// This is NOT distributive due to integer arithmetic
			size_t padding = (longest + 2)/8 - (length + 2)/8;

			for (size_t i = 0; i <= padding; i++)
				[of_stdout writeString: @"\t"];

			[of_stdout writeFormat: @"- %@\n", command.help];
		};

		return YES;
	}

	id<JubCLICommand> command = _commands[input[0]];

	if (command) {
		[command callWithParameters:
		    [input arrayByRemovingObject: [input firstObject]]];

		return YES;
	}

	[of_stdout writeLine: @"Invalid command, type `:h` for help"];

	return YES;
}

-  (void)contact: (XMPPContact*)contact
  didSendMessage: (XMPPMessage*)message
{
	if (message.body == nil || ![message.type isEqual: @"chat"])
		return;

	_lastIn =  contact;
}

-   (void)contact: (XMPPContact*)contact
  didSendPresence: (XMPPPresence*)presence
{
	[of_stdout writeFormat: BOLD("%@") @" is now in state ", presence.from];

	if ([presence.type isEqual: @"unavailable"])
		[of_stdout writeFormat: COL_OFFLINE(@"offline")];
	else if ([presence.show isEqual: @"chat"])
		[of_stdout writeFormat: COL_CHAT(@"free for chat")];
	else if ([presence.show isEqual: @"away"])
		[of_stdout writeFormat: COL_AWAY(@"away")];
	else if ([presence.show isEqual: @"xa"])
		[of_stdout writeFormat: COL_XA(@"extended away")];
	else if ([presence.show isEqual: @"dnd"])
		[of_stdout writeFormat: COL_DND(@"do not disturb")];
	else
		[of_stdout writeFormat: COL_ONLINE(@"online")];

	if (presence.status != nil)
		[of_stdout writeFormat: @": %@", presence.status];

	[of_stdout writeString: @"\n"];
}
@end
