package com.icecondor.eaglet.api;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.UUID;
import java.util.concurrent.ExecutionException;

import org.json.JSONException;
import org.json.JSONObject;

import android.os.Handler;
import android.util.Base64;
import android.util.Log;

import com.icecondor.eaglet.Constants;
import com.koushikdutta.async.future.Future;
import com.koushikdutta.async.http.AsyncHttpClient;
import com.koushikdutta.async.http.AsyncHttpGet;
import com.koushikdutta.async.http.AsyncHttpRequest;
import com.koushikdutta.async.http.WebSocket;

public class Client implements ConnectCallbacks {

    private final URI apiUrl;
    private final AsyncHttpClient client;
    private final ClientActions actions;
    private boolean reconnect = true;
    private static final int RECONNECT_WAIT_MS = 5000;
    private int reconnects = 0;
    private final Handler handler;
    private Future<WebSocket> websocketFuture;
    private WebSocket websocket;

    public enum States { WAITING, CONNECTING, CONNECTED};
    private States state;

    public Client(String serverURL, ClientActions actions) throws URISyntaxException {
        this.apiUrl = new URI(serverURL);
        this.actions = actions;
        handler = new Handler();
        this.client = AsyncHttpClient.getDefaultInstance();
        KoushiSocket.disableSSLCheck(client);
        state = States.WAITING;
    }

    public void connect() {
        Log.d(Constants.APP_TAG, "client: connect(). state = "+state);
        if(state != States.CONNECTED) {
            state = States.CONNECTING;
            actions.onConnecting(apiUrl);
            // AndroidSync quirk, uses http urls
            String httpQuirkUrl = apiUrl.toString().replace("ws://", "http://").replace("wss://", "https://");
            AsyncHttpRequest get = new AsyncHttpGet(httpQuirkUrl);
            get.setTimeout(2500);
            websocketFuture = client.websocket(get, null, new KoushiSocket(this));
        } else {
            Log.d(Constants.APP_TAG, "client: ignoring connect(). connecting in progress.");
        }
    }

    public void startPersistentConnect() {
        reconnect = true;
        doConnect();
    }

    public States getState() {
        return state;
    }

    @Override
    public void onTimeout() {
        state = States.WAITING;
        actions.onTimeout();
        if(reconnect) {
            reconnects += 1;
            long waitMillis = exponentialBackoffTime(reconnects);
            Log.d(Constants.APP_TAG, "api.Client connect: onTimeout. reconnects = "+reconnects+". next try "+(waitMillis/1000)+"s.");
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {
                    doConnect();
                }
            }, waitMillis);
        }
    }

    private long exponentialBackoffTime(int reconnects) {
        return (long)Math.pow(RECONNECT_WAIT_MS/1000,reconnects);
    }

    private void doConnect() {
        connect();
    }

    /* ConnectCallbacks */
    @Override
    public void onMessage(String msg) {
        Log.d(Constants.APP_TAG, "Client onMessage: "+msg);
        try {
            JSONObject payload = new JSONObject(msg);
            actions.onMessage(payload);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onConnected() {
        Log.d(Constants.APP_TAG, "Client onConnected.");
        try {
            websocket = websocketFuture.get();
            state = States.CONNECTED;
            reconnects = 0;
            actions.onConnected();
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onDisconnected() {
        Log.d(Constants.APP_TAG, "Client onDisconnected.");
        state = States.WAITING;
        reconnects = 0;
        actions.onDisconnected();
        doConnect();
    }

    /* Track the call and its response */
    protected String apiCall(String method, JSONObject params) {
        String id = UUID.randomUUID().toString();
        JSONObject payload = new JSONObject();
        try {
            payload.put("id", id);
            payload.put("method", method);
            payload.put("params", params);
            websocket.send(payload.toString());
            return id;
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        }
    }

    /* API Calls */
    public String accountAuthEmail(String email, String deviceId) {
        JSONObject params = new JSONObject();
        try {
            params.put("email", email);
            params.put("device_id", deviceId);
            return apiCall("auth.email", params);
        } catch (JSONException e) {
            e.printStackTrace();
            return null;
        }
    }

    public String accountAuthSession(String token, String deviceId) {
        JSONObject params = new JSONObject();
        try {
            String deviceKey = sha256base64(token+deviceId);
            params.put("device_key", deviceKey);
            return apiCall("auth.session", params);
        } catch (JSONException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
        return null;
    }

    private String sha256base64(String text) throws NoSuchAlgorithmException{
        MessageDigest hasher = MessageDigest.getInstance("SHA-256");
        hasher.update(text.getBytes());
        String hash = Base64.encodeToString(hasher.digest(), Base64.NO_WRAP);
        return hash;
    }

    public String userDetail() {
        JSONObject params = new JSONObject();
        return apiCall("user.detail", params);
    }

}

@@ -4192,16 +4192,32 @@
 String()
+.substring(0, 7)
 ;%0A      
@@ -5148,22 +5148,16 @@
 e64(
-token+
 deviceId
 );%0A 
@@ -5144,32 +5144,38 @@
 6base64(deviceId
++token
 );%0A            p

put device key input in the right order