package com.ntels.onecable.admin.common.websocket;

import com.ntels.onecable.admin.R;
import com.ntels.onecable.admin.common.AppConfig;
import com.ntels.onecable.admin.common.Constants;
import com.ntels.onecable.admin.common.broadcast.KneetMessageLocalBroadCast;
import com.ntels.onecable.admin.common.util.Dlog;
import com.ntels.onecable.admin.common.util.LogUtil;
import com.ntels.onecable.admin.common.util.Util;

import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_10;
import org.java_websocket.handshake.ServerHandshake;
import org.json.JSONException;
import org.json.JSONObject;

import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Binder;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;

import java.net.URI;
import java.net.URISyntaxException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import static com.ntels.onecable.admin.common.broadcast.DefaultLocalBroadCast.INTENT_NAME_TYPE;

/**
 * <pre>
 * 1. ClassName :   WebSocketService
 * 2. FileName  :   WebSocketService.java
 * 3. Package   :   com.ntels.onecable.admin.common.websocket
 * 4. Date      :   2016-12-06
 * 5. Author    :   aaron
 * 6. Comment   :   Websocket request service
 * </pre>
 */
public class WebSocketService extends Service {
    public static final String ACTION_NETWORK_STATE_CHANGED = "networkStateChanged";
    public static final int WEBSOCKET_RECEIVED_SUCESS = 0;
    public static final int WEBSOCKET_RECEIVED_ERROR = 1;
    public static final int WEBSOCKET_CONNECT_TIMEOUT = 0;
    public static final int WEBSOCKET_CLIENT_CONNECT_AUTH_INSTALLER = 1;
    private static final String TAG = WebSocketService.class.getSimpleName();
    private static final int TIMEOUT = 20;
    private final IBinder binder = new WebSocketBinder();
    private WebSocketClient mWebSocket = null;
    //    private ArrayList<OnWebSocketResponse> webSocketResponses = new ArrayList<OnWebSocketResponse>();
    Handler handler = new Handler(new Handler.Callback() {
        @Override
        public boolean handleMessage(Message msg) {
            switch (msg.what) {
                case WEBSOCKET_CONNECT_TIMEOUT:
                    LogUtil.i(TAG, "WebSocket Time out");
                    break;
                case WEBSOCKET_CLIENT_CONNECT_AUTH_INSTALLER:
                    send(Constants.WEBSOCKET_SEND_CONNECT_AUTH_INSTALLER);
                    break;
            }
            return false;
        }
    });
    private Util mUtil = new Util();
    private AppConfig mAppConfig;
    private int iTempApiId = 0;
    private HashMap<String, Object> hmTempParam;
    private boolean bFromUser = false;
    private BroadcastReceiver mMessageReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {

            boolean networkIsOn = intent.getBooleanExtra(ACTION_NETWORK_STATE_CHANGED, false);
            Dlog.i(TAG, "mMessageReceiver networkIsOn =" + networkIsOn);
            if (networkIsOn) {
                Dlog.i(TAG, "mMessageReceiver networkIsOn =" + networkIsOn);
                webSocketConnect();
            } else {
                Dlog.i(TAG, "mMessageReceiver networkIsOn =" + networkIsOn);
                webSocketDisconnect();
            }
        }
    };

    public WebSocketService() {
    }

    @Override
    public void onCreate() {
        super.onCreate();
        mAppConfig = (AppConfig) this.getApplicationContext();
    }

//    /**
//     * WebSocket Listener 추가.
//     *
//     * @param webSocketResponse WebSocket 데이터를 전달받을 리스너 객체
//     */
//    public void addWebSocketResponse(OnWebSocketResponse webSocketResponse) {
//        this.webSocketResponses.add(webSocketResponse);
//    }
//
//    /**
//     * WebSocket Listener 삭제.
//     *
//     * @param webSocketResponse 삭제할 Websocket 리스너 객체
//     */
//    public void removeWebSocketResponse(OnWebSocketResponse webSocketResponse) {
//        this.webSocketResponses.remove(webSocketResponse);
//    }

    @Override
    public IBinder onBind(Intent intent) {
        LogUtil.i(TAG, "onBind!!");
        LocalBroadcastManager.getInstance(this).registerReceiver(mMessageReceiver, new IntentFilter(WebSocketService
                .ACTION_NETWORK_STATE_CHANGED));
        webSocketConnect();
//        if (!isWebSocketConnected() && !mAppConfig.getCtrtGroupId().equals("")) {
//        if (!isWebSocketConnected()) {
//            LogUtil.i(TAG, "onBind1!!");
//            webSocketConnect();
//            LogUtil.i(TAG, "onBind2!!");
//        }
        return binder;
    }

    @Override
    public boolean onUnbind(Intent intent) {
        LogUtil.i(TAG, "onUnbind()!!");
        LocalBroadcastManager.getInstance(this).unregisterReceiver(mMessageReceiver);
//        webSocketDisconnect();
        return super.onUnbind(intent);
    }

    /**
     * WebSocket 연결.
     */
    public void webSocketConnect() {
        Dlog.i(TAG, "webSocketConnect()!!");
        if (mAppConfig.getIsWebSocketConn()) {
            Dlog.i(TAG, "webSocketConnect() return1!!");
            return;
        }

        if (isWebSocketConnected()) {
            Dlog.i(TAG, "webSocketConnect() return2!!");
            return;
        }

        mAppConfig.setIsWebSocketConn(true);
        Dlog.i(TAG, "webSocketConnect() start!!");

        URI uri;
        try {
            String url = mAppConfig.getServicehubWebsocketUrl() + mUtil.getString(getApplicationContext(), R.string.API_VERSION);
            uri = new URI(url);
        } catch (URISyntaxException e) {
            e.printStackTrace();
            return;
        }

        LogUtil.i(TAG, "WebSocket URL : " + uri);

        mWebSocket = new WebSocketClient(uri, new Draft_10(), getHeaders(), 0) {
            @Override
            public void onOpen(ServerHandshake serverHandshake) {
                mAppConfig.setIsWebSocketConn(false);
                Dlog.i(TAG, "webSocketConnect() onOpen!!");

                // 이미 Authorization token이 있는경우 auth를 진행하지않는다.
                if ("".equals(mAppConfig.getAuthorization())) {
                    // 로그인 성공시. 또는 자동 로그인시만  토큰 유지 관련 처리
                    if (mAppConfig.getIsLogin() || (!mAppConfig.getIsLogin() && mAppConfig.getIsAutoLogin())) {
                        handler.sendEmptyMessage(WEBSOCKET_CLIENT_CONNECT_AUTH_INSTALLER);
                    }
                }

            }


            @Override
            public void onMessage(final String message) {
                LogUtil.i(TAG, "WebSocket Response : " + message);
                if (checkBasicStatus(message)) {
                    return;
                }
                sendMessageReceivedEvent(message);

            }

            @Override
            public void onClose(int i, String s, boolean b) {
                mAppConfig.setIsWebSocketConn(false);
                Log.i("Websocket", "Closed " + s);
                Log.i("Websocket", "Closed bFromUser" + bFromUser);
                if (!bFromUser) {
                    LogUtil.i(TAG, "Websocket closed!!1");
//                    webSocketConnect();
                }
                bFromUser = false;
            }

            @Override
            public void onError(Exception ex) {
                mAppConfig.setIsWebSocketConn(false);
                Log.i("Websocket", "Error " + ex.getMessage());
                LogUtil.i(TAG, "jino2");
                ex.printStackTrace();
                sendMessageErrorReceivedEvent(Constants.WEBSOCKET_ERROR_TIMEOUT, mUtil.getString(getApplicationContext(), R.string
                        .WS_ERR_100));
                return;
            }
        };

        if (uri.getScheme().equals("wss")) {
            try {
                TrustManager[] trustAllCerts = new TrustManager[]{new X509TrustManager() {
                    public X509Certificate[] getAcceptedIssuers() {
                        X509Certificate[] myTrustedAnchors = new X509Certificate[0];
                        return myTrustedAnchors;
                    }

                    @Override
                    public void checkClientTrusted(X509Certificate[] certs, String authType) {
                    }

                    @Override
                    public void checkServerTrusted(X509Certificate[] certs, String authType) {
                    }
                }};
                SSLContext sslContext = SSLContext.getInstance("TLS");
                sslContext.init(null, trustAllCerts, new SecureRandom());
                SSLSocketFactory factory = sslContext.getSocketFactory();
                mWebSocket.setSocket(factory.createSocket());
                mWebSocket.connectBlocking();
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        try {
            mWebSocket.connect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * Websocket 종료.
     */
    public void webSocketClose() {
        LogUtil.i(TAG, "webSocketClose()!!");
        if (mWebSocket != null) {
            bFromUser = true;
            mWebSocket.close();
            mWebSocket = null;
//            webSocketResponses.clear();
            mAppConfig.setIsWebSocketConn(false);
            stopSelf();
        }
    }

    /**
     * Websocket 연결 해제.
     */
    public void webSocketDisconnect() {
        LogUtil.i(TAG, "webSocketDisconnect()!!");
        if (mWebSocket != null) {
            bFromUser = true;
            mWebSocket.close();
            mWebSocket = null;
            mAppConfig.setIsWebSocketConn(false);
        }
    }

    /**
     * device online/connect 상태 및 client 인증 결과는 리스너로 전달 없이 바로 값 저장.
     *
     * @param message WebSocket으로 전달받은 json 데이터 스트링
     *
     * @return 리스너로 결과 전달할지 말지 여부
     */
    private boolean checkBasicStatus(String message) {
        try {
            JSONObject jsonObject = new JSONObject(message);
            if (jsonObject.get(Constants.KEY_MESSAGE_TYPE).equals(Constants.WEBSOCKET_TYPE_DEVICE_CONN)) {
//                mAppConfig.setsSelect_Homegrp_Homehub_Conn_State(mUtil.null2string(jsonObject.get(Constants.KEY_DEVICE_CONN)));

                String oldDeviceConn = mUtil.null2string(mAppConfig.getDevicesList(
                        mUtil.null2string(jsonObject.get(Constants.KEY_DEVICE_ID))).get(Constants.KEY_DEVICE_CONN));
                String newDeviceConn = mUtil.null2string(jsonObject.get(Constants.KEY_DEVICE_CONN));
                if (!oldDeviceConn.equals(newDeviceConn)) {
                    mAppConfig.getDevicesList(mUtil.null2string(jsonObject.get(Constants.KEY_DEVICE_ID))).put(Constants.KEY_DEVICE_CONN,
                            mUtil.null2string(jsonObject.get(Constants.KEY_DEVICE_CONN)));
                    sendMessageHomehubStateChangEvent();
                }

                return true;
            } else if (jsonObject.get(Constants.KEY_MESSAGE_TYPE).equals(Constants.WEBSOCKET_TYPE_AUTH)) {
                mAppConfig.setIsWebSocketAuth(mUtil.null2string(jsonObject.get(Constants.KEY_RESULT_CODE)).equals("00") ? true : false);
//                if (webSocketResponses.size() > 0) {
//                    for (OnWebSocketResponse wsr : webSocketResponses) {
//                        wsr.onResponse(message);
//                    }
//                }

                sendMessageReceivedEvent(message);

                /*if ( iTempApiId > 0 && hmTempParam != null ) {
                    send(iTempApiId, hmTempParam);
                }*/
                return true;
            } /*else if ( jsonObject.get(Constants.KEY_MESSAGE_TYPE).equals(Constants.WEBSOCKET_TYPE_DEVICE_CONTENT) ) {
                String onlineStatus = ((JSONObject) jsonObject.get(Constants.KEY_CONTENT_VALUE)).getString(Constants
                .WEBSOCKET_KEY_ONLINE);
                if ( onlineStatus.equalsIgnoreCase("ON") ) {
                    mAppConfig.setsSelect_Homegrp_Homehub_Dongle_State("ON");
                    mAppConfig.setsSelect_Homegrp_Homehub_Conn_State("connect");
                } else {
                    mAppConfig.setsSelect_Homegrp_Homehub_Dongle_State("OFF");
                    mAppConfig.setsSelect_Homegrp_Homehub_Conn_State("connect");
                }
                return true;
            }*/
        } catch (JSONException e) {
            e.printStackTrace();
        }
        return false;
    }

    private Map<String, String> getHeaders() {

        if (null != mAppConfig.getAuthorization()) {
            HashMap<String, String> headers = new HashMap<>();
            headers.put(Constants.KEY_AUTHORIZATION, mAppConfig.getAuthorization());
            headers.put(Constants.KEY_LANG, mAppConfig.getLanguage_Id());
            return headers;
        }
        return null;
    }

    /**
     * WebSocket 연결 상태 체크.
     *
     * @return : true - 연결됨, false - 연결안됨
     */
    public boolean isWebSocketConnected() {
        if (mWebSocket != null) {
            // NOT_YET_CONNECTED, CONNECTING, OPEN, CLOSING, CLOSED;
            LogUtil.v(TAG, "mWebSocket.connect() : " + mWebSocket.getReadyState());
            if (WebSocket.READYSTATE.OPEN.equals(mWebSocket.getReadyState())) {
                return true;
            }
        }
        return false;
    }

    /**
     * Websocket request send.
     *
     * @param apiId Request type id
     */
    public void send(int apiId) {
        if (!isWebSocketConnected()) {
//            if (webSocketResponses.size() > 0) {
////                for (OnWebSocketResponse wsr : webSocketResponses) {
////                    wsr.onError(Constants.WEBSOCKET_ERROR_NOT_CONNECTED, "error");
////                }
//            }
            sendMessageErrorReceivedEvent(Constants.WEBSOCKET_ERROR_NOT_CONNECTED, "error");
            return;
        }

        WebSocketSender webSocketSender = new WebSocketSender(mWebSocket, handler);
        switch (apiId) {
            case Constants.WEBSOCKET_SEND_CONNECT_AUTH:
                webSocketSender.sendClientConnectAuth();
//                webSocketSender.sendWssTest();
                break;
        }
    }

    /**
     * Websocket request send.
     *
     * @param apiId    Request type id
     * @param hmParams Request parameter
     */
    public void send(int apiId, HashMap<String, Object> hmParams) {
        if (!isWebSocketConnected()) {
            webSocketConnect();
//            if (webSocketResponses.size() > 0) {
//                for (OnWebSocketResponse wsr : webSocketResponses) {
//                    wsr.onError(Constants.WEBSOCKET_ERROR_NOT_CONNECTED, "error");
//
//                }
//            }

            sendMessageErrorReceivedEvent(Constants.WEBSOCKET_ERROR_NOT_CONNECTED, "error");
            LogUtil.d("jino", "aaaaa1");
            return;
        }
        LogUtil.d("jino", "aaaaa1");

        WebSocketSender webSocketSender = new WebSocketSender(mWebSocket, handler);
        switch (apiId) {
            case Constants.WEBSOCKET_SEND_AUTH_INSTALLER:
            case Constants.WEBSOCKET_SEND_AUTH_INSTALLER_AUTO:
                webSocketSender.sendClient(hmParams);
                break;
//            case Constants.WEBSOCKET_SEND_AUTH:
//                // 임시
//                webSocketSender.sendClientAuth(hmParams);
//                break;
//            case Constants.WEBSOCKET_SEND_AUTH_AUTO:
//                // 임시
//                webSocketSender.sendClientAuthAuto(hmParams);
//                break;
            case Constants.WEBSOCKET_SEND_DEVICE_REGISTER:
                webSocketSender.sendDeviceRegister(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_DEVICE_DELETE:
                webSocketSender.sendDeviceDelete(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_NODE_COMMAND:
                webSocketSender.sendNodeCommand(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_DEVICE_COMMAND:
                Dlog.v("sendParams = " + hmParams);
                webSocketSender.sendDeviceCommand(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_NODE_RELOAD:
                webSocketSender.sendNodeReload(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_NODE_REGISTER:
                webSocketSender.sendNodeRegister(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_NODE_DELETE:
                webSocketSender.sendNodeDelete(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_DEVICE_ALIVE:
                webSocketSender.sendDeviceAlive(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_NODE_PROFILE_UPDATE:
                webSocketSender.sendNodeProfileUpdate(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_FIRMWARE_UPGRADE:
                webSocketSender.sendFirmwareUpgrade(hmParams);
                break;
            case Constants.WEBSOCKET_SEND_SOFTWARE_UPGRADE:
                webSocketSender.sendSoftwareUpgrade(hmParams);
                break;
        }

        if (iTempApiId != 0) {
            iTempApiId = 0;
            hmTempParam = null;
        }
    }

    /**
     * Websocket request send.
     *
     * @param apiId      Request type id
     * @param sJsonParam Request parameter
     */
    public void send(int apiId, String sJsonParam) {
        if (!isWebSocketConnected()) {
//            if (webSocketResponses.size() > 0) {
//                for (OnWebSocketResponse wsr : webSocketResponses) {
//                    wsr.onError(Constants.WEBSOCKET_ERROR_NOT_CONNECTED, "error");
//                }
//            }
            sendMessageErrorReceivedEvent(Constants.WEBSOCKET_ERROR_NOT_CONNECTED, "error");
            return;
        }

        WebSocketSender webSocketSender = new WebSocketSender(mWebSocket, handler);
        switch (apiId) {
            case Constants.WEBSOCKET_SEND_CONNECT_AUTH:
                webSocketSender.sendClientConnectAuth();
//                webSocketSender.sendWssTest();
                break;
        }
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        LogUtil.i(TAG, "onStartCommand!!");
        if (!isWebSocketConnected() && !mAppConfig.getCtrtGroupId().equals("")) {
            webSocketConnect();
        }
        return START_STICKY;
    }

    private void sendMessageReceivedEvent(String message) {
        LogUtil.i(TAG, "sendMessageReceivedEvent!!");
        Intent intent = new Intent(KneetMessageLocalBroadCast.INTENT_ACTION);
        intent.putExtra(INTENT_NAME_TYPE, KneetMessageLocalBroadCast.INTENT_ACTION_WEBSOCKET_MSG_RECEIVED);
        intent.putExtra(Constants.EXTRA_RESULT_STATE, WEBSOCKET_RECEIVED_SUCESS);
        intent.putExtra(Constants.EXTRA_RESULT, message);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    private void sendMessageErrorReceivedEvent(int errorcode, String message) {
        Intent intent = new Intent(KneetMessageLocalBroadCast.INTENT_ACTION);
        intent.putExtra(INTENT_NAME_TYPE, KneetMessageLocalBroadCast.INTENT_ACTION_WEBSOCKET_MSG_RECEIVED);
        intent.putExtra(Constants.EXTRA_RESULT_STATE, WEBSOCKET_RECEIVED_ERROR);
        intent.putExtra(Constants.EXTRA_ERROR_CODE, errorcode);
        intent.putExtra(Constants.EXTRA_RESULT, message);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    private void sendMessageHomehubStateChangEvent() {
        Intent intent = new Intent(KneetMessageLocalBroadCast.INTENT_ACTION);
        intent.putExtra(INTENT_NAME_TYPE, KneetMessageLocalBroadCast.INTENT_ACTION_HOMEHUB_STATE_CHANGE);
        LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
    }

    /**
     * Websocket binder class.
     *
     * @see #binder
     */
    public class WebSocketBinder extends Binder {
        public WebSocketService getService() {
            return WebSocketService.this;
        }
    }
}
