/*
 * Copyright 2006-2011 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.
 *
 * Borrowed from fbgc, http://code.google.com/p/fbgc/
 */

package net.sf.kraken.protocols.xmpp.mechanisms;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Map;

import javax.security.auth.callback.CallbackHandler;
import javax.security.sasl.Sasl;

import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.sasl.SASLMechanism;
import org.jivesoftware.smack.util.Base64;

/**
 */
public class FacebookConnectSASLMechanism extends SASLMechanism {

    private String accessToken = "";
    private String appSecret = "";
    private String apiKey = "";
    
    public FacebookConnectSASLMechanism(SASLAuthentication saslAuthentication) {
        super(saslAuthentication);
    }

    // protected void authenticate() throws IOException, XMPPException {
    // String[] mechanisms = { getName() };
    // Map<String, String> props = new HashMap<String, String>();
    // sc = Sasl.createSaslClient(mechanisms, null, "xmpp", hostname, props,
    // this);
    //
    // super.authenticate();
    // }

    protected void authenticate() throws IOException, XMPPException {
        StringBuilder stanza = new StringBuilder();
        stanza.append("<auth mechanism=\"").append(getName());
        stanza.append("\" xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
        stanza.append("</auth>");

        // Send the authentication to the server
        getSASLAuthentication().send(new AuthMechanism(getName(), null));
    }

    public void authenticate(String apiKeyAndAppSecret, String host, String accessToken)
            throws IOException, XMPPException {

        if(apiKeyAndAppSecret==null || accessToken==null)
            throw new IllegalStateException("Invalid parameters!");
        
        String[] keyArray = apiKeyAndAppSecret.split("\\|");
        
        if(keyArray==null || keyArray.length != 2)
            throw new IllegalStateException("Api key or access token is not present!");
        
        this.apiKey = keyArray[0];
        this.appSecret = keyArray[1];
        this.accessToken = accessToken;
        
        this.authenticationId = accessToken;
        this.password = accessToken;
        this.hostname = host;

        String[] mechanisms = { "DIGEST-MD5" };
        Map<String, String> props = new HashMap<String, String>();
        sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, this);
        authenticate();
    }

    public void authenticate(String username, String host, CallbackHandler cbh)
            throws IOException, XMPPException {
        String[] mechanisms = { "DIGEST-MD5" };
        Map<String, String> props = new HashMap<String, String>();
        sc = Sasl.createSaslClient(mechanisms, null, "xmpp", host, props, cbh);
        authenticate();
    }

    protected String getName() {
        return "X-FACEBOOK-PLATFORM";
    }

    public void challengeReceived(String challenge) throws IOException {
        // Build the challenge response stanza encoding the response text
        StringBuilder stanza = new StringBuilder();

        byte response[] = null;
        if (challenge != null) {
            String decodedResponse = new String(Base64.decode(challenge));
            Map<String, String> parameters = getQueryMap(decodedResponse);

            String version = "1.0";
            String nonce = parameters.get("nonce");
            String method = parameters.get("method");
            
            Long callId = new GregorianCalendar().getTimeInMillis()/1000;
            
            String sig = "api_key="+apiKey
                            +"call_id="+callId
                            +"method="+method
                            +"nonce="+nonce
                            +"access_token="+accessToken
                            +"v="+version
                            +appSecret;
            
            try {
                sig = MD5(sig);
            } catch (NoSuchAlgorithmException e) {
                throw new IllegalStateException(e);
            }
            
            String composedResponse = "api_key="+apiKey+"&"
                                        +"call_id="+callId+"&"
                                        +"method="+method+"&"
                                        +"nonce="+nonce+"&"
                                        +"access_token="+accessToken+"&"
                                        +"v="+version+"&"
                                        +"sig="+sig;

            response = composedResponse.getBytes();
        }

        String authenticationText="";

        if (response != null) {
            authenticationText = Base64.encodeBytes(response, Base64.DONT_BREAK_LINES);
        }

        stanza.append("<response xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
        stanza.append(authenticationText);
        stanza.append("</response>");

        // Send the authentication to the server
        getSASLAuthentication().send(new Response(authenticationText));
    }

    private Map<String, String> getQueryMap(String query) {
        String[] params = query.split("&");
        Map<String, String> map = new HashMap<String, String>();
        for (String param : params) {
            String name = param.split("=")[0];
            String value = param.split("=")[1];
            map.put(name, value);
        }
        return map;
    }
    
    private String convertToHex(byte[] data) {
        StringBuffer buf = new StringBuffer();
        for (int i = 0; i < data.length; i++) {
            int halfbyte = (data[i] >>> 4) & 0x0F;
            int two_halfs = 0;
            do {
                if ((0 <= halfbyte) && (halfbyte <= 9))
                    buf.append((char) ('0' + halfbyte));
                else
                    buf.append((char) ('a' + (halfbyte - 10)));
                halfbyte = data[i] & 0x0F;
            } while(two_halfs++ < 1);
        }
        return buf.toString();
    }
 
    public String MD5(String text) throws NoSuchAlgorithmException, UnsupportedEncodingException  {
        MessageDigest md;
        md = MessageDigest.getInstance("MD5");
        byte[] md5hash = new byte[32];
        md.update(text.getBytes("iso-8859-1"), 0, text.length());
        md5hash = md.digest();
        return convertToHex(md5hash);
    }
}
