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

#include <string.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;
	}

	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 ([parameters count] == 2) {
		[_ui.client sendPresenceWithStatus: show];
		return;
	}

	OFString *message = [[parameters
	    arrayByRemovingObject: [parameters firstObject]]
		componentsJoinedByString: @" "];

	[_ui.client sendPresenceWithStatus: show
				      text: message];
}
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

BEGINCLICOMMAND(JubCLISubsCommand, @":subs",
    @"[list | ack <who> | nak <who>]",
    @"Lists, acknowledges or denies subscription requests")
{
	if ([parameters count] < 1) {
		[of_stdout writeLine: @"Syntax: ':subs "
		    @"[list | ack <who> | nak <who>]'"];
		return;
	}

	OFString *action = parameters[0];
	if (![action isEqual: @"list"] && ([parameters count] != 2)) {
		[of_stdout writeLine: @"Syntax: ':subs "
		    @"[list | ack <who> | nak <who>]'"];
		return;
	}

	if ([action isEqual: @"list"]) {
		for (OFString *request in _ui.subRequests)
			[of_stdout writeLine: request];
		return;
	}

	XMPPJID *JID = [XMPPJID JIDWithString: parameters[1]];

	if ([action isEqual: @"ack"])
		[_ui.client.contactManager sendSubscribedToJID: JID];
	else if ([action isEqual: @"nak"])
		[_ui.client.contactManager sendUnsubscribedToJID: JID];

	[_ui.subRequests removeObject: parameters[1]];
}
ENDCLICOMMAND

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

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

	@try {
		_commands =  [OFMutableDictionary new];
		_subRequests =  [OFMutableSet 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]];
		[self addCommand: [[[JubCLISubsCommand alloc]
				       initWithCLIUI: self] autorelease]];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

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

static JubCLIUI *completionData;
static void completionCallback(OFString *buf, OFList *lc)
{
	if ([buf length] < 3)
		return;

	if (![buf hasPrefix: @":s "] && ![buf hasPrefix: @":m "] &&
	    ![buf hasPrefix: @":t "])
		return;

	if ([buf hasPrefix: @":t"]) {
		OFString *options[] = {
			@":t available",
			@":t away",
			@":t dnd",
			@":t xa",
			@":t chat",
			@":t unavailable"
		};

		for (int i = 0; i < 6; i++) {
			if (![options[i] hasPrefix: buf])
				continue;
			[lc appendObject: options[i]];
		}

		return;
	}

	OFString *command = [buf substringWithRange: of_range(0, 3)];
	OFString *query = [buf substringWithRange: of_range(3, [buf length]-3)];
	OFDictionary *contacts = completionData.client.contactManager.contacts;
	for (OFString *key in contacts) {
		if (![key  hasPrefix: query])
			continue;
		[lc appendObject: [command stringByAppendingString: key]];
	}
}

- (void)startUIThread
{
	completionData = self;

	[[OFThread threadWithThreadBlock: ^(void) {
		OFString *line;
		Linenoise *reader = [Linenoise sharedLinenoise];
		reader.multiline = true;
		reader.completionCallback = completionCallback;

		while ((line = [reader readInputWithPrompt: @"> "]) != nil) @autoreleasepool {
			[self Jub_userInputWithStream: nil
						 line: line
					    exception: nil];
			if ([line length] != 0)
				[reader addHistoryItem: line];
		}
		[self Jub_userInputWithStream: nil
					 line: nil
				    exception: nil];

		return nil;
	}] start];
}

-      (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 || exception != nil)
		[OFApplication terminate];

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

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

		[_sink send: line];

		return true;
	}

	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 true;
	}

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

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

		return true;
	}

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

	return true;
}

- (void)contactManager: (XMPPContactManager*)manager
	 didAddContact: (XMPPContact*)contact
{
	XMPPRosterItem *rosterItem = contact.rosterItem;
	OFString *subscription = rosterItem.subscription;
	OFString *bareJID = [rosterItem.JID bareJID];

	if ([subscription isEqual: @"from"] || [subscription isEqual: @"both"])
		[_subRequests removeObject: bareJID];
}

-          (void)contactManager: (XMPPContactManager*)manager
  didReceiveSubscriptionRequest: (XMPPPresence*)presence
{
	OFString *bareJID = [presence.from bareJID];
	[_subRequests addObject: bareJID];
	[of_stdout writeFormat: @"\r" BOLD("%@") @" send a request to "
	    @"subscribe to your presence\r\n"
	    @"Use the :subs command to answer it\n",
	    bareJID];
	[[Linenoise sharedLinenoise] refreshLine];
}

-            (void)contact: (XMPPContact*)contact
  willUpdateWithRosterItem: (XMPPRosterItem*)rosterItem
{
	OFString *subscription = rosterItem.subscription;
	OFString *bareJID = [rosterItem.JID bareJID];

	if ([subscription isEqual: @"from"] || [subscription isEqual: @"both"])
		[_subRequests removeObject: bareJID];
}

-  (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: @"\r"];
	[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"];

	[[Linenoise sharedLinenoise] refreshLine];
}
@end
