package ua.net.tokar.xmpp_daemon;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;

import org.jivesoftware.smack.Chat;
import org.jivesoftware.smack.ChatManager;
import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.MessageListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.RosterListener;
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.StringUtils;
import org.kohsuke.args4j.CmdLineException;
import org.kohsuke.args4j.CmdLineParser;
import org.kohsuke.args4j.Option;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class XmppBot {
    @Option( name="-xmpp_port",usage="Sets an XMPP connection port" )
    private Integer xmpp_port = 5222;
    
    @Option( name="-xmpp_host",usage="Sets an XMPP hostname" )
    private String xmpp_host = "talk.google.com";
    
    @Option( name="-xmpp_provider",usage="Sets an XMPP provider" )
    private String xmpp_provider = "gmail.com";
    
    @Option(name="-username",usage="Sets a username")
    private String username;
    
    @Option(name="-password",usage="Sets a password")
    private String password;
    
    private String resource = "XMPP bot";
    
    @Option(name="-port",usage="Sets a port listen to")
    private Integer port = 7777;
    
    @Option(name="-host",usage="Sets a port listen to")
    private String host = "localhost";
    
    private XMPPConnection conn;
    private Roster roster;
    private HashMap<String,LinkedList<String>> unsend_messages = new HashMap<String, LinkedList<String>>();
    private final Logger logger = LoggerFactory.getLogger( XmppBot.class );
    
    public void run() throws Throwable {
        InetAddress ip = InetAddress.getByName( this.host );
        logger.info( "Connected to {}", ip );
        
        ConnectionConfiguration configuration = new ConnectionConfiguration( this.xmpp_host, this.xmpp_port, this.xmpp_provider );
        configuration.setSASLAuthenticationEnabled( true );
        
        this.conn = new XMPPConnection( configuration );
        this.conn.connect();
        this.conn.login( this.username, this.password, this.resource );
        
        this.roster = this.conn.getRoster();
        this.roster.addRosterListener( new RosterListener() {
            public void entriesDeleted( Collection<String> addresses ) {}
            public void entriesUpdated( Collection<String> addresses ) {}
            public void entriesAdded( Collection<String> arg0 ) {}
            
            public void presenceChanged( Presence presence ) {
                String from = StringUtils.parseBareAddress( presence.getFrom() );
                
                if ( presence.isAvailable() ) {
                    // see if we have unsend messages for user
                    LinkedList<String> messages = XmppBot.this.unsend_messages.get( from );
                    while ( messages.size() > 0 ) {
                        String msg = messages.poll();
                        
                        XmppBot.this.sendMessage( from, msg );
                    }
                }
            }
        } );
        
        ServerSocket ss = new ServerSocket( this.port, 0, ip );
        while ( true ) {
            Socket s = ss.accept();
            logger.info( "Client accepted" );
            new Thread( new BSocketProcessor( s ) ).start();
        }
    }
    
    public boolean sendMessage( String to, String msg ) {
        if ( !this.roster.getPresence( to ).isAvailable() ) {
            try {
                this.roster.createEntry( to, to, null );
                
                if ( this.unsend_messages.get( to ) == null ) {
                    this.unsend_messages.put( to, new LinkedList<String>() );
                }
                this.unsend_messages.get( to ).offer( msg );
                
                return true;
                
            } catch ( XMPPException e ) {
                logger.error( "Couldn't add user {} to roster", to );
                
                return false;
            }
        } else {
            Message message = new Message( to );
            message.setBody( msg );
            
            this.logger.info( "Trying to send message '{}'", to + ":" + msg );
            
            ChatManager chatmanager = this.conn.getChatManager();
            Chat newChat = chatmanager.createChat( message.getTo(), new MessageListener() {
                public void processMessage( Chat chat, Message message ) {
                    //XmppBot.this.logger.info( "Received message \"{}\" from {} ", message.getBody(), message.getFrom() );
                }
            } );
    
            try {
                newChat.sendMessage( message );
            
                return true;
            } catch ( XMPPException e ) {
                this.logger.error( "Error Delivering block" );
                return false;
            }
        }
    }
    
    private class BSocketProcessor implements Runnable {
        private Socket s;
        public BSocketProcessor( Socket s ) {
            this.s = s;
        }

        public void run() {
            try {
                BufferedReader br = new BufferedReader( new InputStreamReader( this.s.getInputStream() ) );
                java.io.OutputStream os = this.s.getOutputStream();
                
                String msg = "";
                String to = br.readLine();
                while ( true ) {
                    String str = br.readLine();
                    
                    if ( str.equals( "<bot-end>" ) ) {
                        break;
                    }
                    
                    msg += str + "\n";
                }
                
                boolean is_success = sendMessage( to, msg );
                
                if ( is_success ) {
                    os.write( "success".getBytes() );
                } else {
                    os.write( "failed".getBytes() );
                }
                os.flush();
                
            } catch ( IOException e ) {
                XmppBot.this.logger.error( "Error while reading from input stream: {}", e.getMessage() );
                
            } finally {
                try {
                    this.s.close();
                } catch ( Throwable e ) {
                    XmppBot.this.logger.error( "Error while closing socket" );
                }
            }
            XmppBot.this.logger.info( "Client processing finished" );
        }
    }

    public static void main( String[] args ) throws Throwable {
        Logger logger = LoggerFactory.getLogger( XmppBot.class );
        logger.info( "run 1" );
        
        XmppBot bean = new XmppBot();
        
        CmdLineParser parser = new CmdLineParser(bean);
        try {
            parser.parseArgument( args );
            bean.run();
        } catch ( CmdLineException e ) {
            logger.error( e.getMessage() );
            parser.printUsage( System.err );
        }

    }

    
}

