/**
 * Copyright 2006-2010 Daniel Henninger.  All rights reserved.
 *
 * This software is published under the terms of the GNU Public License (GPL),
 * a copy of which is included in this distribution.
 */
package net.sf.kraken.protocols.xmpp;

import net.sf.kraken.avatars.Avatar;
import net.sf.kraken.type.NameSpace;

import org.apache.log4j.Logger;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.DefaultPacketExtension;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.PacketExtension;
import org.jivesoftware.smackx.packet.VCard;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.NotFoundException;
import org.xmpp.packet.Presence;

/**
 * Presence packets are used for two purposes. First, to notify the server of
 * our the clients current presence status. Second, they are used to subscribe
 * and unsubscribe users from the roster. 
 * 
 * Instances of XMPPSubscriptionHandler
 * are responsible for handling events generated by receiving
 * both types of presence stanzas from the legacy domain.
 * 
 * @author Guus der Kinderen, guus.der.kinderen@gmail.com
 */
public class XMPPPresenceHandler implements PacketListener{

	final static Logger Log = Logger.getLogger(XMPPPresenceHandler.class);
	
	private final XMPPSession session;
	
	/**
	 * Instantiates a new Presence handler for the given session.
	 * 
	 * @param session
	 *            The session for which to handle presence stanzas.
	 */
	public XMPPPresenceHandler(XMPPSession session) {
		if (session == null) {
			throw new IllegalArgumentException(
					"Argument 'session' cannot be null.");
		}
		this.session = session;
	}
	
	/* (non-Javadoc)
	 * @see org.jivesoftware.smack.PacketListener#processPacket(org.jivesoftware.smack.packet.Packet)
	 */
	public void processPacket(Packet packet) {
		if (!(packet instanceof org.jivesoftware.smack.packet.Presence)) {
			throw new IllegalArgumentException(getClass().getName()
					+ " can only be used to handle presence packets. "
					+ "Please modify the caller code accordingly "
					+ "(use a appropriate PacketFilter).");
		}

		final org.jivesoftware.smack.packet.Presence presence = (org.jivesoftware.smack.packet.Presence) packet;
		final org.jivesoftware.smack.packet.Presence.Type type = presence.getType();
		if (type.equals(org.jivesoftware.smack.packet.Presence.Type.available) 
				|| type.equals(org.jivesoftware.smack.packet.Presence.Type.unavailable)) {
			handlePresenceMode(presence);
		} else {
			handlePresenceSubscription(presence);
		}
	}

	/**
	 * Handles incoming presence stanzas that relate to presence status / mode
	 * changes. Ignores others.
	 * 
	 * @param presence
	 *            the stanza
	 */
	private void handlePresenceMode(
			final org.jivesoftware.smack.packet.Presence presence) {
        if (!session.getBuddyManager().isActivated()) {
            session.getBuddyManager().storePendingStatus(session.getTransport().convertIDToJID(presence.getFrom()), ((XMPPTransport)session.getTransport()).convertXMPPStatusToGateway(presence.getType(), presence.getMode()), presence.getStatus());
        }
        else {
			// TODO: Need to handle resources and priorities!
			try {
			    final XMPPBuddy xmppBuddy = session.getBuddyManager().getBuddy(session.getTransport().convertIDToJID(presence.getFrom()));
			    Log.debug("XMPP: Presence changed detected type "+presence.getType()+" and mode "+presence.getMode()+" for "+presence.getFrom());
			    xmppBuddy.setPresenceAndStatus(
			            ((XMPPTransport)session.getTransport()).convertXMPPStatusToGateway(presence.getType(), presence.getMode()),
			            presence.getStatus()
			    );
			    if (JiveGlobals.getBooleanProperty("plugin.gateway."+session.getTransport().getType()+".avatars", true)) {
			        PacketExtension pe = presence.getExtension("x", NameSpace.VCARD_TEMP_X_UPDATE);
			        if (pe != null) {
			            DefaultPacketExtension dpe = (DefaultPacketExtension)pe;
			            String hash = dpe.getValue("photo");
			            final String from = presence.getFrom();
			            if (hash != null) {
			                Avatar curAvatar = xmppBuddy.getAvatar();
			                if (curAvatar == null || !curAvatar.getLegacyIdentifier().equals(hash)) {
			                    new Thread() {
			                        @Override
                                    public void run() {
			                            VCard vcard = new VCard();
			                            try {
			                                vcard.load(session.conn, from);
			                                xmppBuddy.setAvatar(new Avatar(xmppBuddy.getJID(), from, vcard.getAvatar()));
			                            }
			                            catch (XMPPException e) {
			                                Log.debug("XMPP: Failed to load XMPP avatar: ", e);
			                            }
			                            catch (IllegalArgumentException e) {
			                                Log.debug("XMPP: Got null avatar, ignoring.");
			                            }
			                        }
			                    }.start();
			                }
			            }
			        }
			    }
			}
			catch (NotFoundException e) {
			    Log.debug("XMPP: Received presence notification for contact that's not in the buddy manager of user " + session.getJID() + ". GTalk is known to do this occasionally: "+presence.getFrom());
			    // We cannot add this buddy to the buddy manager, as that would result into an auto-accept of the contact sending the data.
			}
		}
	}
	
	/**
	 * Handles incoming presence stanzas that relate to subscription status.
	 * Ignores others.
	 * 
	 * @param presence
	 *            the stanza
	 */
	private void handlePresenceSubscription(
			final org.jivesoftware.smack.packet.Presence presence) {
		final Presence p = new Presence();
		p.setTo(session.getJID());
		p.setFrom(session.getTransport().convertIDToJID(presence.getFrom()));

        
		switch (presence.getType()) {
		case subscribe:
			p.setType(Presence.Type.subscribe);
			break;

		case subscribed:
	        final XMPPBuddy buddy = new XMPPBuddy(session.getBuddyManager(), presence.getFrom());
	        session.getBuddyManager().storeBuddy(buddy);
			p.setType(Presence.Type.subscribed);
			break;

		case unsubscribe:
			p.setType(Presence.Type.unsubscribe);
			break;

		case unsubscribed:
			p.setType(Presence.Type.unsubscribed);
			break;

		case error:
			p.setType(Presence.Type.error);
			break;

		default:
			// don't send anything.
			return;
		}

		session.getTransport().sendPacket(p);
	}
}
