package jp.sourceforge.shovel.device.impl;

import static jp.sourceforge.shovel.AvailabilityType.*;
import static org.jivesoftware.smack.Roster.SubscriptionMode.*;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.Map;

import javax.net.ssl.HandshakeCompletedEvent;
import javax.net.ssl.HandshakeCompletedListener;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.packet.Message;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.util.collections.ReferenceMap;
import org.jivesoftware.smackx.packet.ChatStateExtension;
import org.jivesoftware.smackx.packet.MessageEvent;
import org.seasar.framework.log.Logger;

import jp.sourceforge.shovel.DeviceType;
import jp.sourceforge.shovel.device.IXmppWrapper;
import jp.sourceforge.shovel.entity.IDevice;
import jp.sourceforge.shovel.entity.IUser;
import jp.sourceforge.shovel.exception.ApplicationException;

public class XmppWrapperImpl extends AbstractDelayExecutorImpl implements IXmppWrapper {
    static Logger logger = Logger.getLogger(XmppWrapperImpl.class);
    
    public String getHost() {
        return properties_.getProperty("host");
    }
    public String getUserId() {
        return properties_.getProperty("userId");
    }
    public String getPassword() {
        return properties_.getProperty("password");
    }
    public int getPort() {
        return Integer.parseInt(properties_.getProperty("port"));
    }
    public String getServiceName() {
        return properties_.getProperty("serviceName");
    }
    public boolean isSasl() {
        return Boolean.parseBoolean(properties_.getProperty("sasl"));
    }
    public boolean isSsl() {
        return Boolean.parseBoolean(properties_.getProperty("ssl"));
    }
    public String getExecutorId() {
        return properties_.getProperty("type");
    }
    public String getAddress() {
        StringBuilder address = new StringBuilder(getUserId());
        if (DeviceType.find(getExecutorId()).isJabber()) {
            address.append("@");
            address.append(getServiceName());
        }
        return address.toString();
    }
    
    /////
    
    final static String NS_MESSAGE_EVENT = "jabber:x:event";
    final static String NS_CHAT_STATE = "http://jabber.org/protocol/chatstates";
    
    Map<String, Chat> chatsByJabber_ = new ReferenceMap<String, Chat>(
            ReferenceMap.HARD, ReferenceMap.WEAK);
    XMPPConnection connection_;
    
    String splitJabberId(Chat chat) {
        String jabberId = chat.getParticipant();
        int endIndex = jabberId.lastIndexOf("/");
        if (endIndex == -1) {
            return jabberId;
        }
        return jabberId.substring(0, endIndex);
    }
    String splitClientId(Chat chat) {
        String jabberId = chat.getParticipant();
        int endIndex = jabberId.lastIndexOf("/");
        if (endIndex == -1) {
            return jabberId;
        }
        return jabberId.substring(endIndex);
    }
    public void chatCreated(Chat chat, boolean createdLocally) {
        chat.addMessageListener(this);
    }
    public void processMessage(Chat chat, Message message) {
        MessageEvent msgEvent = (MessageEvent)message.getExtension(NS_MESSAGE_EVENT);
        ChatStateExtension chatState = (ChatStateExtension)message.getExtension(NS_CHAT_STATE);
        
        try {
            //TODO メッセンジャーかと思ってたけどサーバ？、いずれにせよ癖があるようだ
            if (msgEvent == null || msgEvent.isComposing()) {
                if (chatState == null || chatState.getElementName() == "active") {
                    String address = splitJabberId(chat).toLowerCase();
                    chatsByJabber_.put(address, chat);
                    
                    this.receiveMessage(address, message.getBody());
                }
            }
        } catch (ApplicationException e) {
            //TODO 受信に失敗
        }
    }
    //（チャットできる人）名簿回り
    public void entriesAdded(Collection<String> addresses) {
    }
    public void entriesUpdated(Collection<String> addresses) {
    }
    public void entriesDeleted(Collection<String> addresses) {
    }
    public void presenceChanged(Presence presence) {
    }
    
    /////
    
    /**
     * XMPPサーバーに接続
     * Google talkはしょっぱなからSSL/TSLでの通信を要求する
     * 環境によりポートが5223だったり443だったりする理由はいまいち
     */
    public void login() throws ApplicationException {
        ConnectionConfiguration config;
        if (getHost() != null && getPort() > 0) {
            if (getServiceName() == null) {
                config = new ConnectionConfiguration(getHost(), getPort());
            } else {
                config = new ConnectionConfiguration(getHost(), getPort(), getServiceName());
            }
        } else {
            config = new ConnectionConfiguration(getServiceName());
        }
        
        try {
            if (isSasl()) {
                config.setSASLAuthenticationEnabled(true);
            }
            if (isSsl()) {
                config.setSocketFactory(new MySSLSocketFactory());
            }
            connection_ = new XMPPConnection(config);
            //connection_.DEBUG_ENABLED = true;
            
            connection_.connect();
            connection_.login(getUserId(), getPassword());
        } catch (XMPPException e) {
            //isConnectedがtrueを返すので。。
            throw new ApplicationException("");
        }
        
        ChatManager chatManager = connection_.getChatManager();
        chatManager.addChatListener(this);
        
        Roster roster = connection_.getRoster();
        roster.setSubscriptionMode(accept_all);
    }
    /**
     * XMPPサーバーとの接続の切断
     */
    public void logout() {
        //呼ぶと次回起動時にsocket closedで失敗する。。
//      connection_.disconnect();
        connection_ = null;
    }
    public boolean isConnected() {
        return connection_ != null && connection_.isConnected();
    }
    void sendMessage(IUser receiver, String body) throws ApplicationException {
        IDevice device = receiver.getDevice();
        if (getExecutorId().compareToIgnoreCase(device.getType()) == 0 &&
            device.getAvailabilityType() == ON) {
            String address = receiver.getAddress();
            if (address == null || address.length() <= 0) {
                return;
            }

            try {
                Message message = new Message();
                message.setTo(address);
                message.setBody(body);
                
                String to = message.getTo();
                Chat chat = chatsByJabber_.get(to);
                if (chat == null) {
                    ChatManager chatManager = connection_.getChatManager();
                    chat = chatManager.createChat(to, this);
                }
                chat.sendMessage(message);
            } catch (XMPPException e) {
                throw new ApplicationException("");
            }
        }
    }
    
    /////
    
    //Jetiのパクリ
    public static class MySSLSocketFactory extends SSLSocketFactory {
        SSLSocketFactory factory;
        public MySSLSocketFactory() {
            try {
                SSLContext sslcontent = SSLContext.getInstance("TLS");
                sslcontent.init(null, // KeyManager not required
                                new TrustManager[] {new MyTrustManager()},
                                new java.security.SecureRandom());
                factory = sslcontent.getSocketFactory();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (KeyManagementException e) {
                e.printStackTrace();
            }
        }
        public Socket createSocket(Socket socket, String s, int i,boolean flag) throws IOException {
            return factory.createSocket(socket, s, i, flag);
        }
        public Socket createSocket(InetAddress inaddr, int i,InetAddress inaddr2, int j) throws IOException {
            return factory.createSocket(inaddr, i, inaddr2, j);
        }
        public Socket createSocket(InetAddress inaddr, int i) throws IOException {
            return factory.createSocket(inaddr, i);
        }
        public Socket createSocket(String s, int i, InetAddress inaddr, int j)  throws IOException {
            return factory.createSocket(s, i, inaddr, j);
        }
        public Socket createSocket(String s, int i) throws IOException {
            Socket so = factory.createSocket(s, i);
             ((SSLSocket)so).addHandshakeCompletedListener(
                new HandshakeCompletedListener() {
                   public void handshakeCompleted(
                      HandshakeCompletedEvent event) {
                      System.out.println("Handshake finished!");
                      System.out.println(
                      "\t CipherSuite:" + event.getCipherSuite());
                      System.out.println(
                      "\t SessionId " + event.getSession());
                      System.out.println(
                      "\t PeerHost " + event.getSession().getPeerHost());
                   }
                }
             );
            return so;
        }
        public String[] getDefaultCipherSuites() {
            return factory.getSupportedCipherSuites();
        }
        public String[] getSupportedCipherSuites() {
            return factory.getSupportedCipherSuites();
        }
    }
    static class MyTrustManager implements X509TrustManager {
        public void checkClientTrusted(X509Certificate[] chain, String authType) {  }
        public void checkServerTrusted(X509Certificate[] chain, String authType)  {
            try {
                chain[0].checkValidity();
            } catch (CertificateExpiredException e) {
            } catch (CertificateNotYetValidException e) {
            }
        }
        public X509Certificate[] getAcceptedIssuers() {
            return new X509Certificate[0];
        }
    }
}
