package it.gotoandplay.smartfoxclient.http;

import it.gotoandplay.smartfoxclient.SFSEventDispatcher;
import it.gotoandplay.smartfoxclient.SmartFoxClient;
import it.gotoandplay.smartfoxclient.data.SFSObject;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLEncoder;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.logging.Level;
import java.util.logging.Logger;

public class HttpConnection extends SFSEventDispatcher
{
    private static final String HANDSHAKE = "connect";
    private static final String DISCONNECT  = "disconnect";
    private static final String CONN_LOST = "ERR#01";
    private static final String HTTP_POLL_REQUEST = "poll";

    public static final char HANDSHAKE_TOKEN = '#';

    private static final String SERVLET_URL = "BlueBox/HttpBox.do";
    private static final String PARAM_NAME = "sfsHttp";

    private static final int MIN_POLL_SPEED = 0;
    private static final int DEFAULT_POLL_SPEED = 750;
    private static final int MAX_POLL_SPEED = 10000;


    private static final Logger logger =
            Logger.getLogger(SmartFoxClient.class.getName());

    private String sessionId;
    private volatile boolean connected;
    private String ipAddr;
    private int port;
    private URL blueboxURL;
    private volatile int httpPollSpeed = DEFAULT_POLL_SPEED;

    private IHttpProtocolCodec codec;

    private volatile ExecutorService executorService;

    private Thread pollThread;

    private class BlueBoxPollThread extends Thread
    {
        private final String pollRequest;

        public BlueBoxPollThread(String pollRequest)
        {
            super("BlueBoxPoll");
            this.pollRequest = pollRequest;
        }

        @Override
        public void run()
        {
            try
            {
                while(true)
                {
                    sendHTTPMessage(pollRequest);
                    if(interrupted())
                    {
                        return;
                    }
                    sleep(httpPollSpeed);
                }
            }
            catch(IOException ex)
            {
                handleConnectionError(ex);
                return;
            }
            catch(InterruptedException ex)
            {
                return;
            }
        }
    }

    //TODO: To put in the proper place
    public HttpConnection()
    {
        connected = false;
        codec = new RawProtocolCodec();
    }

    public String getSessionId()
    {
        return this.sessionId;
    }

    public boolean isConnected()
    {
        return this.connected;
    }

    public int getHttpPollSpeed()
    {
        return httpPollSpeed;
    }

    public void setHttpPollSpeed(int httpPollSpeed)
    {
        // Acceptable values: 0 <= sp <= 10sec
        if(httpPollSpeed >= MIN_POLL_SPEED && httpPollSpeed <= MAX_POLL_SPEED)
        {
            this.httpPollSpeed = httpPollSpeed;
        }
    }

    public void connect(String addr)
    {
        connect(addr, 8080);
    }
    
    public void connect(String addr, int port)
    {
        try
        {
            this.ipAddr = addr;
            this.port = port;
            String webUrl = "http://" + this.ipAddr + ":" +
                    this.port + "/" + SERVLET_URL;
            this.blueboxURL = new URL(webUrl);
            this.sessionId = null;

            this.executorService =
                Executors.newSingleThreadExecutor();

            send(HANDSHAKE);
        }
        catch(MalformedURLException ex)
        {
            logger.log(Level.SEVERE,
                    "Malformed BlueBox URL: " + ex.getMessage(), ex);
        }
    }

    public void close()
    {
        if(connected)
        {
            send(DISCONNECT);
        }
        if(executorService != null)
        {
            executorService.shutdown();
            executorService = null;
        }
    }

    public void send(String message)
    {
        if (connected || (!connected && message.equals(HANDSHAKE)) || (!connected && message.equals("poll")))
        {				
            try
            {
                final String data = PARAM_NAME + "=" + URLEncoder.encode(codec.encode(this.sessionId, message), "UTF-8");

                if(!message.equals("poll"))
                {
                    logger.log(Level.INFO, "[ Send ]: " + data);
                }
                if(message.equals(HANDSHAKE))
                {
                    //The handshake message is send in the same thread as the
                    //socket connection to keep the behaviour consistent
                    sendHTTPMessage(data);
                }
                else
                {
                    this.executorService.execute(new Runnable() {
                        public void run() {
                            try
                            {
                                sendHTTPMessage(data);
                            }
                            catch(IOException ex)
                            {
                                handleConnectionError(ex);
                            }
                        }
                    });
                }
            }
            catch(IOException ex)
            {
                handleConnectionError(ex);
            }
        }
    }

    private void sendHTTPMessage(String data) throws IOException
    {
        HttpURLConnection connection = (HttpURLConnection) blueboxURL.openConnection();
        connection.setRequestMethod("POST");
        connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
        connection.setDoOutput(true);
        OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
        out.write(data);
        out.flush();
        out.close();

        BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
        char[] cbuf = new char[1];
        StringBuilder sb = new StringBuilder();
        while(in.read(cbuf) != -1)
        {
            sb.append(cbuf);
        }
        String response = sb.toString();

        if(response.length() > 0)
        {
            HttpEvent event;
            SFSObject params = new SFSObject();

            // handle handshake
            if(response.charAt(0) == HANDSHAKE_TOKEN)
            {
                // Init the sessionId
                if (sessionId == null)
                {
                    sessionId = codec.decode(response);
                    connected = true;

                    params.put("sessionId", this.sessionId);
                    params.put("success", true);

                    String pollRequest = PARAM_NAME + "=" +
                        URLEncoder.encode(codec.encode(
                            this.sessionId, HTTP_POLL_REQUEST), "UTF-8");
                    this.pollThread = new BlueBoxPollThread(pollRequest);
                    this.pollThread.start();

                    event = new HttpEvent(HttpEvent.onHttpConnect, params);
                    dispatchEvent(event);

                }
                else
                {
                    // Error, session already exist and cannot be redefined!
                    logger.log(Level.SEVERE,
                            "**ERROR** SessionId is being rewritten");
                }
            } // handle data
            else
            {
                // fire disconnection
                if(response.indexOf(CONN_LOST) == 0)
                {
                    this.pollThread.interrupt();
                    params.put("data", "");
                    event = new HttpEvent(HttpEvent.onHttpClose, params);
                } // fire onHttpData
                else
                {
                    params.put("data", response);
                    event = new HttpEvent(HttpEvent.onHttpData, params);
                }
                dispatchEvent(event);
            }
        }
    }

    private void handleConnectionError(Exception ex)
    {
        SFSObject params = new SFSObject();
        params.put("message", ex.getMessage());

        HttpEvent event = new HttpEvent(HttpEvent.onHttpError, params);

        dispatchEvent(event);
    }
}