/**
 * 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.oscar;

import java.util.Date;
import java.util.List;

import net.kano.joscar.ByteBlock;
import net.kano.joscar.LEBinaryTools;
import net.kano.joscar.OscarTools;
import net.kano.joscar.flap.FlapCommand;
import net.kano.joscar.flap.FlapPacketEvent;
import net.kano.joscar.flapcmd.CloseFlapCmd;
import net.kano.joscar.flapcmd.SnacCommand;
import net.kano.joscar.net.ClientConn;
import net.kano.joscar.net.ClientConnEvent;
import net.kano.joscar.net.ConnDescriptor;
import net.kano.joscar.snac.SnacPacketEvent;
import net.kano.joscar.snac.SnacResponseEvent;
import net.kano.joscar.snaccmd.InfoData;
import net.kano.joscar.snaccmd.conn.MyInfoRequest;
import net.kano.joscar.snaccmd.conn.ServerReadyCmd;
import net.kano.joscar.snaccmd.conn.ServiceRedirect;
import net.kano.joscar.snaccmd.error.SnacError;
import net.kano.joscar.snaccmd.icbm.ParamInfo;
import net.kano.joscar.snaccmd.icbm.ParamInfoCmd;
import net.kano.joscar.snaccmd.icbm.ParamInfoRequest;
import net.kano.joscar.snaccmd.icbm.SetParamInfoCmd;
import net.kano.joscar.snaccmd.icq.MetaShortInfoCmd;
import net.kano.joscar.snaccmd.icq.OfflineMsgDoneCmd;
import net.kano.joscar.snaccmd.icq.OfflineMsgIcqAckCmd;
import net.kano.joscar.snaccmd.icq.OfflineMsgIcqCmd;
import net.kano.joscar.snaccmd.loc.LocRightsCmd;
import net.kano.joscar.snaccmd.loc.LocRightsRequest;
import net.kano.joscar.snaccmd.loc.SetInfoCmd;
import net.kano.joscar.snaccmd.ssi.ActivateSsiCmd;
import net.kano.joscar.snaccmd.ssi.AuthFutureCmd;
import net.kano.joscar.snaccmd.ssi.AuthReplyCmd;
import net.kano.joscar.snaccmd.ssi.BuddyAddedYouCmd;
import net.kano.joscar.snaccmd.ssi.BuddyAuthRequest;
import net.kano.joscar.snaccmd.ssi.ModifyItemsCmd;
import net.kano.joscar.snaccmd.ssi.SsiDataCmd;
import net.kano.joscar.snaccmd.ssi.SsiDataRequest;
import net.kano.joscar.snaccmd.ssi.SsiItem;
import net.kano.joscar.snaccmd.ssi.SsiRightsRequest;
import net.kano.joscar.ssiitem.BuddyItem;
import net.kano.joscar.ssiitem.DefaultSsiItemObjFactory;
import net.kano.joscar.ssiitem.GroupItem;
import net.kano.joscar.ssiitem.IconItem;
import net.kano.joscar.ssiitem.SsiItemObj;
import net.kano.joscar.ssiitem.SsiItemObjectFactory;
import net.kano.joscar.ssiitem.VisibilityItem;
import net.sf.kraken.type.ConnectionFailureReason;
import net.sf.kraken.type.TransportLoginStatus;

import org.apache.log4j.Logger;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.StringUtils;
import org.xmpp.packet.Presence;

/**
 * Handles BOS related packets.
 *
 * @author Daniel Henninger
 * Heavily inspired by joscardemo from the joscar project.
 */
public class BOSConnection extends BasicFlapConnection {

    static Logger Log = Logger.getLogger(BOSConnection.class);

    protected SsiItemObjectFactory itemFactory = new DefaultSsiItemObjFactory();

    public BOSConnection(ConnDescriptor cd, OSCARSession mainSession, ByteBlock cookie) {
        super(cd, mainSession, cookie); // Hand off to BasicFlapConnection
    }

    @Override
    protected void clientReady() {
        super.clientReady();
        startKeepAlive();
    }

    @Override
    protected void handleStateChange(ClientConnEvent e) {
        Log.debug("OSCAR bos service state change from "+e.getOldState()+" to "+e.getNewState()+" Reason: "+e.getReason()+" User:"+getMainSession().getJID());
//        if (e.getNewState() == ClientFlapConn.STATE_NOT_CONNECTED && e.getOldState() == ClientFlapConn.STATE_CONNECTED && getMainSession().isLoggedIn()) {
//            getMainSession().sessionDisconnected(LocaleUtils.getLocalizedString("gateway.oscar.disconnected", "kraken"));
//        }
        // TODO: Evaulate whether we should check reason and triggered reconnect if possible
        if (e.getNewState().equals(ClientConn.STATE_NOT_CONNECTED) && e.getReason() != null) {
            Log.info ("OSCAR bos disconnected with Reason! call sessionDisconnectedNoReconnect for User:" + getMainSession().getJID());
            if (getMainSession()!= null) {
                getMainSession().sessionDisconnectedNoReconnect(LocaleUtils.getLocalizedString("gateway.oscar.disconnected", "kraken"));
            }
        }
    }

    @Override
    protected void handleFlapPacket(FlapPacketEvent e) {
//        Log.debug("OSCAR bos flap packet received: "+e);
        FlapCommand cmd = e.getFlapCommand();

        if (cmd instanceof CloseFlapCmd) {
            CloseFlapCmd cfc = (CloseFlapCmd)cmd;
            if (cfc.getCode() == CloseFlapCmd.CODE_LOGGED_IN_ELSEWHERE) {
                getMainSession().setFailureStatus(ConnectionFailureReason.LOCKED_OUT);
                getMainSession().sessionDisconnectedNoReconnect(LocaleUtils.getLocalizedString("gateway.oscar.multilogin", "kraken"));
            }
            else {
                getMainSession().setFailureStatus(ConnectionFailureReason.UNKNOWN);                
                getMainSession().sessionDisconnected(LocaleUtils.getLocalizedString("gateway.oscar.disconnected", "kraken"));
            }
        }
        super.handleFlapPacket(e);
    }

    @Override
    protected void handleSnacPacket(SnacPacketEvent e) {
//        Log.debug("OSCAR bos snac packet received: "+e);
        super.handleSnacPacket(e);

        SnacCommand cmd = e.getSnacCommand();

        if (cmd instanceof ServerReadyCmd) {
            request(new ParamInfoRequest());
            request(new LocRightsRequest());
            request(new SsiRightsRequest());
            request(new SsiDataRequest());
        }
        else if (cmd instanceof BuddyAddedYouCmd) {
            BuddyAddedYouCmd bay = (BuddyAddedYouCmd)cmd;

            Presence p = new Presence();
            p.setType(Presence.Type.subscribe);
            p.setTo(getMainSession().getJID());
            p.setFrom(getMainSession().getTransport().convertIDToJID(bay.getUin()));
            getMainSession().getTransport().sendPacket(p);
        }
        else if (cmd instanceof BuddyAuthRequest) {
            BuddyAuthRequest bar = (BuddyAuthRequest)cmd;

            Presence p = new Presence();
            p.setType(Presence.Type.subscribe);
            p.setTo(getMainSession().getJID());
            p.setFrom(getMainSession().getTransport().convertIDToJID(bar.getScreenname()));
            getMainSession().getTransport().sendPacket(p);

            // Auto-accept auth request. (for now)
            // TODO: Evaluate handling this in a non-automated fashion.
            request(new AuthReplyCmd(bar.getScreenname(), null, true));
        }
        else if (cmd instanceof AuthReplyCmd) {
            AuthReplyCmd ar = (AuthReplyCmd)cmd;

            if (ar.isAccepted()) {
                Presence p = new Presence();
                p.setType(Presence.Type.subscribed);
                p.setTo(getMainSession().getJID());
                p.setFrom(getMainSession().getTransport().convertIDToJID(ar.getSender()));
                getMainSession().getTransport().sendPacket(p);
            }
            else {
                Presence p = new Presence();
                p.setType(Presence.Type.unsubscribed);
                p.setTo(getMainSession().getJID());
                p.setFrom(getMainSession().getTransport().convertIDToJID(ar.getSender()));
                getMainSession().getTransport().sendPacket(p);
            }
        }
        else if (cmd instanceof ModifyItemsCmd) {
            ModifyItemsCmd mic = (ModifyItemsCmd)cmd;

            List<SsiItem> items = mic.getItems();
            for (SsiItem item : items) {
                SsiItemObj obj = itemFactory.getItemObj(item);
                if (obj instanceof BuddyItem) {
                    BuddyItem bi = (BuddyItem)obj;
                    Log.debug("AIM got buddy item " + bi);
                    getMainSession().getSsiHierarchy().gotBuddy(bi);
                }
                else if (obj instanceof GroupItem) {
                    GroupItem gi = (GroupItem)obj;
                    Log.debug("AIM got group item " + gi);
                    getMainSession().getSsiHierarchy().gotGroup(gi);
                }
            }
        }
    }

    @Override
    protected void handleSnacResponse(SnacResponseEvent e) {
        super.handleSnacResponse(e);

//        Log.debug("OSCAR bos snac response received: "+e);

        SnacCommand cmd = e.getSnacCommand();

        if (cmd instanceof LocRightsCmd) {
            request(new SetInfoCmd(new InfoData("oscargateway",
                    null, getMainSession().getCapabilities(), null)));
            request(new MyInfoRequest());
        }
        else if (cmd instanceof ParamInfoCmd) {
            ParamInfoCmd pic = (ParamInfoCmd) cmd;

            ParamInfo info = pic.getParamInfo();

            request(new SetParamInfoCmd(new ParamInfo(0,
                    info.getFlags() | ParamInfo.FLAG_TYPING_NOTIFICATION, 8000,
                    info.getMaxSenderWarning(), info.getMaxReceiverWarning(),
                    0)));
        }
        else if (cmd instanceof ServiceRedirect) {
            ServiceRedirect sr = (ServiceRedirect) cmd;

            getMainSession().connectToService(sr.getSnacFamily(), sr.getRedirectHost(),
                    sr.getCookie());

        }
        else if (cmd instanceof SsiDataCmd) {
            SsiDataCmd sdc = (SsiDataCmd) cmd;

            List<SsiItem> items = sdc.getItems();
            for (SsiItem item : items) {
                SsiItemObj obj = itemFactory.getItemObj(item);
                if (obj instanceof BuddyItem) {
                    BuddyItem bi = (BuddyItem)obj;
                    Log.debug("OSCAR: got buddy item " + bi);
                    getMainSession().getSsiHierarchy().gotBuddy(bi);
                }
                else if (obj instanceof GroupItem) {
                    GroupItem gi = (GroupItem)obj;
                    Log.debug("OSCAR: got group item " + gi);
                    getMainSession().getSsiHierarchy().gotGroup(gi);
                }
                else if (obj instanceof IconItem) {
                    IconItem ii = (IconItem)obj;
                    Log.debug("OSCAR: got icon item " + ii);
                    getMainSession().getSsiHierarchy().gotIconItem(ii);
                }
                else if (obj instanceof VisibilityItem) {
                    VisibilityItem vi = (VisibilityItem)obj;
                    Log.debug("OSCAR: got visibility item " + vi);
                    getMainSession().getSsiHierarchy().gotVisibilityItem(vi);
                }
                else {
                    Log.debug("OSCAR: got item we're not handling " + obj);
                }
            }

            if (sdc.getLastModDate() != 0) {
                request(new ActivateSsiCmd());
                clientReady();

                getMainSession().setLoginStatus(TransportLoginStatus.LOGGED_IN);
                getMainSession().gotCompleteSSI();
            }
        }
        else if (cmd instanceof OfflineMsgIcqCmd) {
            OfflineMsgIcqCmd omic = (OfflineMsgIcqCmd)cmd;

            String sn = String.valueOf(omic.getFromUIN());
            Date whenSent = omic.getDate();
            ByteBlock block = omic.getIcqData();
            final int len = LEBinaryTools.getUShort(block, 12) - 1;
            String msg = OscarTools.getString(block.subBlock(14, len), null);
            msg = StringUtils.unescapeFromXML(OscarTools.stripHtml(msg));

            // TODO: Translate offline message note
            getMainSession().getTransport().sendOfflineMessage(
                    getMainSession().getJID(),
                    getMainSession().getTransport().convertIDToJID(sn),
                    msg,
                    whenSent,
                    "Offline Message"
            );
        }
        else if (cmd instanceof OfflineMsgDoneCmd) {
            request(new OfflineMsgIcqAckCmd(getMainSession().getUIN(), (int)getMainSession().nextIcqId()));
        }
        else if (cmd instanceof MetaShortInfoCmd) {
//            MetaShortInfoCmd msic = (MetaShortInfoCmd)cmd;
//            Log.debug("RECEIVED META SHORT INFO: "+msic);
//            getMainSession().updateRosterNickname(String.valueOf(msic.getUIN()), msic.getNickname());
        }
        else if (cmd instanceof AuthReplyCmd) {
            AuthReplyCmd ar = (AuthReplyCmd)cmd;

            if (ar.isAccepted()) {
                Presence p = new Presence();
                p.setType(Presence.Type.subscribed);
                p.setTo(getMainSession().getJID());
                p.setFrom(getMainSession().getTransport().convertIDToJID(ar.getSender()));
                getMainSession().getTransport().sendPacket(p);
            }
            else {
                Presence p = new Presence();
                p.setType(Presence.Type.unsubscribed);
                p.setTo(getMainSession().getJID());
                p.setFrom(getMainSession().getTransport().convertIDToJID(ar.getSender()));
                getMainSession().getTransport().sendPacket(p);
            }
        }
        else if (cmd instanceof AuthFutureCmd) {
            AuthFutureCmd af = (AuthFutureCmd)cmd;

            Presence p = new Presence();
            p.setType(Presence.Type.subscribe);
            p.setTo(getMainSession().getJID());
            p.setFrom(getMainSession().getTransport().convertIDToJID(af.getUin()));
            getMainSession().getTransport().sendPacket(p);
        }
        else if (cmd instanceof SnacError) {
            SnacError se = (SnacError)cmd;
            if (se.getErrorCode() == SnacError.CODE_REFUSED_BY_CLIENT) {
                getMainSession().getTransport().sendMessage(
                        getMainSession().getJID(),
                        getMainSession().getTransport().getJID(),
                        LocaleUtils.getLocalizedString("gateway.aim.msgrefused","kraken")
                );
            }
            //TODO: Tons more errors that can be caught.  Gotta catch 'em all!  =)  (please don't sue me Nintendo)
        }
    }
}
