package org.iotivity.bridge.homeserver;

import android.annotation.SuppressLint;
import android.app.AlarmManager;
import android.app.IntentService;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Message;
import android.os.Process;
import android.os.RemoteCallbackList;
import android.os.RemoteException;
import android.support.annotation.Nullable;
import android.support.v4.app.NotificationCompat;
import android.util.Log;
import android.widget.Toast;


import com.icontrols.service.control.ILightControlService;
import com.icontrols.service.control.ILightControlServiceCallback;

import org.iotivity.OCCoreRes;
import org.iotivity.OCDeviceInfo;
import org.iotivity.OCMain;
import org.iotivity.OCQos;
import org.iotivity.OCResource;
import org.iotivity.OCUuid;
import org.iotivity.OCUuidUtil;
import org.iotivity.bridge.homeserver.handler.client.DeviceDiscoveryHandler;
import org.iotivity.bridge.homeserver.handler.client.GetResponseHandler;
import org.iotivity.bridge.homeserver.handler.client.ObserveResponseHandler;
import org.iotivity.bridge.homeserver.handler.client.PostResponseHandler;
import org.iotivity.bridge.homeserver.handler.client.PutResponseHandler;
import org.iotivity.bridge.homeserver.model.Bridge;
import org.iotivity.bridge.homeserver.model.OCFDevice;
import org.iotivity.bridge.homeserver.model.VirtualOCFDevice;
import org.iotivity.bridge.homeserver.utils.Utils;
import org.iotivity.oc.OcCborEncoder;
import org.iotivity.oc.OcDevice;
import org.iotivity.oc.OcPlatform;
import org.iotivity.oc.OcRemoteDevice;
import org.iotivity.oc.OcRemoteResource;
import org.iotivity.oc.OcUtils;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONTokener;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Iterator;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

public class BridgeService extends IntentService {

    public static final String TAG = BridgeService.class.getName();

    public static Intent serviceIntent = null ;
    private final IBinder lBinder = new LocalServiceBinder();
    private BridgeServer bridgeServer = null ;

    private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

    // Message Handler MSG TYPEs
    public static final int MSG_RESULT = 1 ;
    public static final int MSG_STRING = 2;
    public static final int MSG_LINE = 3;
    public static final int MSG_DEVICE_DISCOVERED = 4 ;
    public static final int MSG_LIGHT_RESULT = 5 ;

    // IBridgeService RESULT TYPEs
    public enum ResultType {
        OK,
        FAIL,
        ERROR
    }

    // IBridgeService RESULT RESPONSE TYPEs
    public enum ResponseType{
        GET,
        OBSERVE,
        OBSERVE_STOP,
        POST,
        PUT
    }

    public BridgeService() {
        super(BridgeService.class.getName());
    }

    private BridgeService getService() {
        return this ;
    }
    public BridgeServer getBridgeServer() {
        return bridgeServer ;
    }

    @Override
    public void onCreate() {
        super.onCreate();

        Log.d(TAG, "onCreate()");
        // start first time only
        bridgeServer = new BridgeServer(this);

        scheduledTaskInit();

        // TODO : should remove this test function after connected to Light Control Service of Wallpad
        bindLightServiceForTest();
    }

    /*
     * BridgeService can be binded with Local Activity and Remote Application
     * through RemoteInterface and ServiceBinder
     */
    @Override
    public IBinder onBind(Intent intent) {

        Log.d(TAG, "onBind() action = " + intent.getAction());
        if (IBridgeService.class.getName().equals(intent.getAction())) {
            return rBinder; // return Remote Binder
        } else if (ILightControlService.class.getName().equals(intent.getAction())) {
            return lightBinder; // return Remote Binder
        }
        return lBinder; // return Local Binder
    }

    /*
     * This is to bind Local Activity like Local Service
     */
    public class LocalServiceBinder extends Binder {
        public BridgeService getService() {
            return BridgeService.this ;
        }
        public int getPid() {
            return Process.myPid();
        }
    }

    /**
     * The IBridgeService is defined through IDL
     */
    private final IBridgeServiceImpl rBinder = new IBridgeServiceImpl() ;
    class IBridgeServiceImpl extends IBridgeService.Stub {

        final RemoteCallbackList<IBridgeActivityCallback> aCallbacks
                = new RemoteCallbackList<IBridgeActivityCallback>();
        final RemoteCallbackList<IBridgeServiceCallback> sCallbacks
                = new RemoteCallbackList<IBridgeServiceCallback>();

        @Override
        public void registerActivityCallback(IBridgeActivityCallback cb) {
            if (cb != null) aCallbacks.register(cb);

            mHandler.sendMessage(mHandler.obtainMessage(MSG_STRING, "Activity Client Callback was registered."));
            mHandler.sendEmptyMessage(MSG_LINE);

        }
        @Override
        public void unregisterActivityCallback(IBridgeActivityCallback cb) {
            if (cb != null) aCallbacks.unregister(cb);
        }

        @Override
        public void registerBridgeServiceCallback(IBridgeServiceCallback cb) {
            if (cb != null) sCallbacks.register(cb);

            mHandler.sendMessage(mHandler.obtainMessage(MSG_STRING, "Service Client Callback was registered."));
            mHandler.sendEmptyMessage(MSG_LINE);

        }
        @Override
        public void unregisterBridgeServiceCallback(IBridgeServiceCallback cb) {
            if (cb != null) sCallbacks.unregister(cb);
        }

        @Override
        public int getPid() throws RemoteException {
            return Process.myPid();
        }

        @Override
        public String retrieveAllDevices() throws RemoteException {
            return getAllDiscoveredDevices();
        }

        @Override
        public String retrieveAllResources(String device_uuid) throws RemoteException {
            return getDeviceResources(device_uuid);
        }

        @Override
        public boolean requestGetProperties(String device_uuid, String resource_uri) throws RemoteException {
            return doGetDeviceResource(device_uuid, resource_uri) ;
        }

        @Override
        public boolean requestPostProperties(String device_uuid, String resource_uri, String prop_data) throws RemoteException {
            return doPostDeviceResource(device_uuid, resource_uri, prop_data);
        }

        @Override
        public boolean requestPutProperties(String device_uuid, String resource_uri, String prop_data) throws RemoteException {
            return doPutDeviceResource(device_uuid, resource_uri, prop_data);
        }

        @Override
        public boolean requestObserveResource(String device_uuid, String resource_uri) throws RemoteException {
            return doObserveResource(device_uuid, resource_uri) ;
        }

        @Override
        public boolean requestStopObserveResource(String device_uuid, String resource_uri) throws RemoteException {
            return stopObserveResource(device_uuid, resource_uri);
        }

        @Override
        public boolean requestObserveDeviceSpecificResources(String device_uuid) throws RemoteException {
            return doObserveDeviceSpecificResources(device_uuid);
        }

        @Override
        public boolean requestStopObserveDeviceSpecificResources(String device_uuid) throws RemoteException {
            return stopObserveDeviceSpecificResources(device_uuid);
        }

        public void killCallbacks() {
            aCallbacks.kill();
            sCallbacks.kill();
        }

        public void broadcastResultToClients(int result_code, String result_data) {
            // Broadcast to all clients the new value.
            final int count = sCallbacks.beginBroadcast();
            Log.i(TAG, "ServiceClientCallback count=" + count);
            for (int index=0; index<count; index++) {
                try {
                    sCallbacks.getBroadcastItem(index).onResultReceived(result_code, result_data);
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing
                    // the dead object for us.
                    Log.e(TAG, "onReceiveResult() RemoteException :", e);
                }
            }
            sCallbacks.finishBroadcast();
        }

        public void broadcastLogPrintLineToClients() {
            final int count = aCallbacks.beginBroadcast();
            for (int index=0; index<count; index++) {
                try {
                    aCallbacks.getBroadcastItem(index).onLogPrintLineReceived();
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing
                    // the dead object for us.
                    Log.e(TAG, "RemoteException :", e);
                }
            }
            aCallbacks.finishBroadcast();
        }

        public void broadcastLogMessageToClients(String message) {
            final int count = aCallbacks.beginBroadcast();
            for (int index=0; index<count; index++) {
                try {
                    aCallbacks.getBroadcastItem(index).onLogMessageReceived(message);
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing
                    // the dead object for us.
                    Log.e(TAG, "RemoteException :", e);
                }
            }
            aCallbacks.finishBroadcast();
        }

        public void broadcastDeviceDiscoveredToClients() {

            final int count = aCallbacks.beginBroadcast();
            for (int index=0; index<count; index++) {
                try {
                    aCallbacks.getBroadcastItem(index).onDeviceDiscovered();
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing
                    // the dead object for us.
                    Log.e(TAG, "RemoteException :", e);
                }
            }
            aCallbacks.finishBroadcast();
        }
    };

    private ILightControlService lightControlBinder = null ;        // Light Control Service Binder for test
    private void bindLightServiceForTest() {

        // Light Control Service Remote Bind
        LightControlServiceConnection lightConn = new LightControlServiceConnection();
        Intent lIntent = new Intent();
        lIntent.setClassName("org.iotivity.bridge.homeserver", "org.iotivity.bridge.homeserver.BridgeService");
        lIntent.setAction(ILightControlService.class.getName());
        bindService(lIntent, lightConn, Context.BIND_AUTO_CREATE);
    }

    class LightControlServiceConnection implements ServiceConnection {

        public static final String ECO_NAME = "rs-485" ;

        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {

            Log.d(TAG, "onServiceConnected ComponentName=" + name + ", IBinder=" + service.toString());
            if (service instanceof ILightControlService.Stub) {

                lightControlBinder = ILightControlService.Stub.asInterface(service);

                try {
                    lightControlBinder.registerLightControlServiceCallback(lightCallback);

                    String devices = lightControlBinder.retrieveAllDevices();
                    Log.d(TAG, "Light Control retrieveAllDevices() : "+ devices);
                    if(devices != null) {
                        JSONArray array = new JSONArray(devices);

                        for (int index = 0 ; index < array.length(); index++) {
                            JSONObject devs = array.getJSONObject(index);
                            Iterator<String> it_devs = devs.keys();
                            String n = null, sn = null, rt = null, state = null ;

                            while (it_devs.hasNext()) {
                                String key = it_devs.next();
                                Object value = devs.get(key);
                                if (key.equals("n") && value instanceof String) {
                                    n = (String)value;
                                    Log.d(TAG, "Light Device : n = " + n);
                                } else if (key.equals("sn") && value instanceof String) {
                                    sn = (String)value;
                                    Log.d(TAG, "Light Device : sn = " + sn);
                                } else if (key.equals("rt") && value instanceof JSONArray) {
                                    Log.d(TAG, "Light Device : rt(org) = " + ((JSONArray)value).toString());
                                    rt = ((JSONArray)value).getString(0);
                                    Log.d(TAG, "Light Device : rt = " + rt);
                                } else if (key.equals("state") && value instanceof String) {
                                    state = (String)value;
                                    Log.d(TAG, "Light Device : state = " + state);
                                }
                            }
                            // add device on ocf platform
                            VirtualOCFDevice device = bridgeServer.addVirtualOCFDevice(n, sn, rt, ECO_NAME);
                            if(device==null) continue;

                            String resources = lightControlBinder.retrieveAllResources(sn);
                            Log.d(TAG, "Light Control retrieveAllResources() : " + resources);
                            if(resources!=null) {
                                JSONArray resArr = new JSONArray(resources);
                                for (int loop = 0 ; loop < resArr.length(); loop++) {
                                    JSONObject res = resArr.getJSONObject(loop);
                                    Iterator<String> rKeys = res.keys();
                                    String rName = null, rUri = null, rRT = null, rIF = null ;

                                    while (rKeys.hasNext()) {
                                        String rKey = rKeys.next();
                                        Object rValue = res.get(rKey);
                                        if (rKey.equals("n") && rValue instanceof String) {
                                            rName = (String)rValue;
                                            Log.d(TAG, "Light Resource : n = " + rName);
                                        } else if (rKey.equals("uri") && rValue instanceof String) {
                                            rUri = (String)rValue;
                                            Log.d(TAG, "Light Resource : uri = " + rUri);
                                        } else if (rKey.equals("rt") && rValue instanceof JSONArray) {
                                            Log.d(TAG, "Light Resource : rt(org) = " + ((JSONArray)rValue).toString());
                                            rRT = ((JSONArray)rValue).getString(0);
                                            Log.d(TAG, "Light Resource : rt = " + rRT);
                                        } else if (rKey.equals("if") && rValue instanceof String) {
                                            rIF = (String)rValue;
                                            Log.d(TAG, "Light Resource : if = " + rIF);
                                        }
                                    }
                                    // add resource on the device
                                    bridgeServer.addVirtualDeviceResource(device, rName, rUri, rRT, rIF);

                                    // get resource properties
                                    String properites = lightControlBinder.doGetResourceProperties(sn, rUri);
                                    Log.d(TAG, "Light Control doGetResourceProperties("+sn+", "+rUri+") : " + properites);

                                    JSONObject props = new JSONObject(properites);
                                    Iterator<String> pKeys = props.keys();
                                    while(pKeys.hasNext()) {
                                        String pKey = pKeys.next();
                                        Object pValue = props.get(pKey);
                                        // update properties state
                                        device.addProperty(rUri, pKey, pValue);
                                    }

                                }
                            }
                        }
                    }
                } catch (RemoteException | JSONException ex) {
                    Log.e(TAG, "Register Callback", ex);
                    // In this case the service has crashed before we could even
                    // do anything with it; we can count on soon being
                    // disconnected (and then reconnected if it can be restarted)
                    // so there is no need to do anything here.
                }

                msg( "Light Control Remote Service Connected");
                printLine();
            }
        }

        @Override
        public void onServiceDisconnected(ComponentName name) {

            //lightControlBinder.unregisterLightControlServiceCallback(lightCallback);
            lightControlBinder = null;
            msg("Light Control Remote Service Disconnected");
            printLine();
        }
    }

    private ILightControlServiceCallback lightCallback = new ILightControlServiceCallback.Stub() {
        /**
         * This is called by the light control remote service regularly to tell us about
         * new values.  Note that IPC calls are dispatched through a thread
         * pool running in each process, so the code executing here will
         * NOT be running in our main thread like most other things -- so,
         * to update the UI, we need to use a Handler to hop over there.
         */

        @Override
        public void onResultReceived(int result_code, String result_data) throws RemoteException {
            msg("result_code="+result_code + ", result_data="+result_data );
            printLine();

            try {
                JSONObject obj = new JSONObject(result_data);
                if(obj != null) {
                    String tid = obj.getString("tid");              // Transaction Id
                    String sn = obj.getString("sn");                // Serial Number (Virtual Device Id)
                    String uri = obj.getString("uri");              // Resource URI
                    String res_type = obj.getString("res_type");    // GET, POST, PUT
                    String res_data = obj.getString("res_data");    // Properties

                    // Update Properties of the resource from result
                    bridgeServer.addOrUpdateVirtualOCFDeviceProperties(sn, uri, res_data);

                    // TODO : need to check // notify to Observers for changed properties's value
                    /*VirtualOCFDevice vod = bridgeServer.getVirtualOCFDevice(sn);
                    OCResource ocResource = OCCoreRes.getResourceByUri(uri, vod.getVODIndex());
                    OCMain.notifyObservers(ocResource);*/
                }
            } catch (JSONException e) {
                Log.e(TAG, "Light Control Service Remote Error : ", e);
            }
        }
    };

    /**
     * The IBridgeService is defined through IDL
     */
    private final ILightControlServiceImpl lightBinder = new ILightControlServiceImpl() ;
    class ILightControlServiceImpl extends ILightControlService.Stub {

        final RemoteCallbackList<ILightControlServiceCallback> callbacks
                = new RemoteCallbackList<ILightControlServiceCallback>();

        @Override
        public void registerLightControlServiceCallback(ILightControlServiceCallback cb) throws RemoteException {
            if (cb != null) callbacks.register(cb);

            mHandler.sendMessage(mHandler.obtainMessage(MSG_STRING, "Light Control Client Callback was registered."));
            mHandler.sendEmptyMessage(MSG_LINE);
        }

        @Override
        public void unregisterLightControlServiceCallback(ILightControlServiceCallback cb) throws RemoteException {
            if (cb != null) callbacks.unregister(cb);
        }

        @Override
        public String retrieveAllDevices() throws RemoteException {
            return "[{\"n\":\"Light Control\", \"rt\":[\"oic.d.light\"], \"sn\":\""+BridgeServer.LIGHT2_UUID+"\", \"state\":\"on-line\"}]";
        }

        @Override
        public String retrieveAllResources(String device_sn) throws RemoteException {
            if(BridgeServer.LIGHT2_UUID.equalsIgnoreCase(device_sn)) {
                return "[{\"n\":\"Light Switch\", \"uri\":\"/light\", \"rt\":[\"oic.r.switch.binary\"], \"if\":\"RW\"},\n" +
                        "{\"n\":\"Dimming Control\",\"uri\":\"/dimming\",\"rt\":[\"oic.r.light.dimming\"], \"if\":\"RW\"},\n" +
                        "{\"n\":\"Energy Consumption\", \"uri\":\"/EnergyConsumptionResURI\", \"rt\": [\"oic.r.energy.consumption\"], \"if\":\"S\"},\n" +
                        "{\"n\":\"Color Temperature\", \"uri\":\"/ColourTemperatureResURI\", \"rt\": [\"oic.r.colour.colourtemperature\"], \"if\":\"A\"}]";
            }
            return null ;
        }

        @Override
        public String doGetResourceProperties(String device_sn, String resource_uri) throws RemoteException {
            if(BridgeServer.LIGHT2_UUID.equalsIgnoreCase(device_sn)) {
                    if("/light".equalsIgnoreCase(resource_uri)) {
                        String props = bridgeServer.getVirtualOCFDeviceProperties(device_sn, resource_uri) ;
                        if(props != null) return props ;
                        else return "{\"n\":\"Light Switch\", \"value\":true}" ;
                    } else if("/dimming".equalsIgnoreCase(resource_uri)) {
                        String props = bridgeServer.getVirtualOCFDeviceProperties(device_sn, resource_uri) ;
                        if(props != null) return props ;
                        else return "{\"n\":\"Dimming Control\",\"dimmingSetting\":100,\"range\":100,\"step\":10}";
                    } else if("/EnergyConsumptionResURI".equalsIgnoreCase(resource_uri)) {
                        String props = bridgeServer.getVirtualOCFDeviceProperties(device_sn, resource_uri) ;
                        if(props != null) return props ;
                        else return "{\"n\":\"Energy Consumption\",\"power\":200.1,\"energy\":3500.4}" ;
                    } else if("/ColourTemperatureResURI".equalsIgnoreCase(resource_uri)) {
                        String props = bridgeServer.getVirtualOCFDeviceProperties(device_sn, resource_uri) ;
                        if(props != null) return props ;
                        else return "{\"n\":\"Color Temperature\", \"ct\":457, \"range\":1000, \"step\":1}" ;
                    }
            }
            return null;
        }

        @Override
        public boolean doPostResourceProperties(String device_sn, String resource_uri, String prop_data) throws RemoteException {

            Log.d(TAG, "doPostResourceProperties called : device_sn = " + device_sn);
            if(BridgeServer.LIGHT2_UUID.equalsIgnoreCase(device_sn)) {
                Log.d(TAG, "Light Control Service : doPostResourceProperties() uri = " + resource_uri+ ", properites = " + prop_data);
                if("/light".equalsIgnoreCase(resource_uri)) {
                    String res_data = makeResultData(device_sn, resource_uri, prop_data);
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_LIGHT_RESULT, ResultType.OK.ordinal(), 0, res_data));
                    return true ;
                } else if("/dimming".equalsIgnoreCase(resource_uri)) {
                    String res_data = makeResultData(device_sn, resource_uri, prop_data);
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_LIGHT_RESULT, ResultType.OK.ordinal(), 0, res_data));
                    return true ;
                } else if("/EnergyConsumptionResURI".equalsIgnoreCase(resource_uri)) {
                    String res_data = makeResultData(device_sn, resource_uri, prop_data) ;
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_LIGHT_RESULT, ResultType.OK.ordinal(), 0, res_data));
                    return true ;
                } else if("/ColourTemperatureResURI".equalsIgnoreCase(resource_uri)) {
                    String res_data = makeResultData(device_sn, resource_uri,  prop_data) ;
                    mHandler.sendMessage(mHandler.obtainMessage(MSG_LIGHT_RESULT, ResultType.OK.ordinal(), 0, res_data));
                    return true ;
                }
            }
            return false;
        }

        private String makeResultData(String device_sn, String uri, String data) {
            String res = "{\"tid\":\""+Utils.generateRandomID()+"\", " +
                    "\"sn\":\""+device_sn+"\", " +
                    "\"uri\":\""+uri+"\", " +
                    "\"res_type\":\"POST\", " +
                    "\"res_data\":"+data+"}";
            return res ;
        }

        public void killCallbacks() {
            callbacks.kill();
        }

        public void broadcastResultToClients(int result_code, String result_data) {
            // Broadcast to all clients the new value.
            final int count = callbacks.beginBroadcast();
            Log.i(TAG, "LigthControlServiceClientCallback count=" + count);
            for (int index=0; index<count; index++) {
                try {
                    callbacks.getBroadcastItem(index).onResultReceived(result_code, result_data);
                } catch (RemoteException e) {
                    // The RemoteCallbackList will take care of removing
                    // the dead object for us.
                    Log.e(TAG, "onReceiveResult() RemoteException :", e);
                }
            }
            callbacks.finishBroadcast();
        }
    };

    public String doGetLightControlResource(long device_index, String device_id, String resource_uri) {
        try {
            OCUuid piid = OCCoreRes.getDeviceInfo(device_index).getPiid();
            String device_sn = OCUuidUtil.uuidToString(piid); // same with vod_id(Virtual Device Id) of VirtualOCFDevice

            // doGetProperties with Light Control Service
            String properties = lightBinder.doGetResourceProperties(device_sn, resource_uri);
            return properties ;
        } catch (RemoteException e) {
            Log.e(TAG, "Light Control Service Remote Error : ", e);
        }
        return null ;
    }

    public void doGetLightControlResources() {

        Log.d(TAG, "SecheduledTaskInit : doGetLightControlResources() called.");
        if(bridgeServer != null && lightBinder != null) {
            ArrayList<VirtualOCFDevice> vods = bridgeServer.getVirtualOCFDeviceList();
            for (VirtualOCFDevice vod : vods) {
                if (vod.getEcoName().equals(LightControlServiceConnection.ECO_NAME) && vod.isDiscovered()
                        && bridgeServer.isOwnedVirtualOCFDevice(vod.getVODIndex())) {
                    String device_sn = vod.getVirtualDeviceId();
                    for (OCFDevice.Resource resource : vod.getResources()) {
                        try {
                            // doGetProperties with Light Control Service
                            String properies = lightBinder.doGetResourceProperties(device_sn, resource.getResourceUri());
                            bridgeServer.addOrUpdateVirtualOCFDeviceProperties(device_sn, resource.getResourceUri(), properies);

                            // TODO : need to check // notify to Observers for changed properties's value
                            /*Log.d(TAG, "OCCoreRes.getResourceByUri() before : " + vod.getDeviceName() +
                                    ", " +resource.getResourceUri()+", "+ vod.getVODIndex());
                            //OCDeviceInfo info = OCCoreRes.getDeviceInfo(vod.getVODIndex());
                            OCResource ocResource = OCCoreRes.getResourceByUri(resource.getResourceUri(), vod.getVODIndex());
                            if(ocResource != null && ocResource.getNumObservers() > 0) {
                                Log.d(TAG, "OCCoreRes.getResourceByUri() after : " + ocResource.getName() +
                                        ", " +ocResource.getUri() +", "+ ocResource.getNumObservers());
                                OCMain.notifyObservers(ocResource);
                            }*/
                        } catch (RemoteException e) {
                            Log.e(TAG, "Light Control Service Remote Error : ", e);
                        }
                    }
                }
            }
        }
    }

    public void doPostLightControlResource(long device_index, String device_id, String resource_uri, String prop_data) {
        try {
            OCUuid piid = OCCoreRes.getDeviceInfo(device_index).getPiid();
            String device_sn = OCUuidUtil.uuidToString(piid); // same with vod_id(Virtual Device Id) of VirtualOCFDevice

            Log.d(TAG, "doPostLightControlResource() called.");
            lightBinder.doPostResourceProperties(device_sn, resource_uri, prop_data);

        } catch (RemoteException e) {
            Log.e(TAG, "Light Control Service Remote Error : ", e);
        }
    }

    public void msg(String msg) {
        mHandler.sendMessage(mHandler.obtainMessage(MSG_STRING, msg));
    }

    public void printLine() {
        mHandler.sendEmptyMessage(MSG_LINE);
    }

    public void broadcastResult(int result_code, String result_data) {
        mHandler.sendMessage(mHandler.obtainMessage(MSG_RESULT,result_code, 0, result_data));
    }

    private final Handler mHandler = new Handler() {
        @SuppressLint("HandlerLeak")
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {
                case MSG_LINE: {
                    rBinder.broadcastLogPrintLineToClients();
                } break;
                case MSG_STRING : {
                    rBinder.broadcastLogMessageToClients((String)msg.obj);
                } break ;
                case MSG_DEVICE_DISCOVERED: {
                    rBinder.broadcastDeviceDiscoveredToClients();
                } break ;
                case MSG_RESULT: {
                    rBinder.broadcastResultToClients(msg.arg1, (String)msg.obj);
                } break ;
                case MSG_LIGHT_RESULT: {
                    lightBinder.broadcastResultToClients(msg.arg1, (String)msg.obj);
                } break ;
                default:
                    super.handleMessage(msg);
            }
        }
    };


    @Override
    protected void onHandleIntent(@Nullable Intent intent) {

        Log.d(TAG, "onHandleIntent() intent=" + intent.getAction());
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Log.d(TAG, "onStartCommand() intent=" + (intent!=null? intent.getAction() : null));
        serviceIntent = intent ;

        initializeNotification();

        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();

        Log.d(TAG, "onDestroy()");

        Log.d(TAG, "Calling Bridge Shutdown.");
        bridgeServer.shutdown();

        // Unregister all callbacks.
        rBinder.killCallbacks();

        // Bridge Service and Application Process Kill
        // android.os.Process.killProcess(Process.myPid());
        // or below action

        Log.d(TAG, "System.exit() will be called");
        System.runFinalizersOnExit(true);
        System.exit(0);
    }

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        super.onTaskRemoved(rootIntent);
        Log.d(TAG, "onTaskRemoved()");
    }

    @Override
    public void onStart(@Nullable Intent intent, int startId) {
        super.onStart(intent, startId);
        Log.d(TAG, "Bridge Service onStart()");
    }

    @Override
    public boolean onUnbind(Intent intent) {
        Log.d(TAG, "Bridge Service onUnbind()");
        return super.onUnbind(intent);
    }

    private void initializeNotification() {

        NotificationCompat.Builder builder = new NotificationCompat.Builder(this, "1");
        builder.setSmallIcon(R.mipmap.ic_launcher);
        NotificationCompat.BigTextStyle style = new NotificationCompat.BigTextStyle();
        style.bigText("Press this to see settings.");
        style.setBigContentTitle(null);
        style.setSummaryText("Bridge Service is Running.");
        builder.setContentText(null);
        builder.setContentTitle(null);
        builder.setOngoing(true);
        builder.setStyle(style);
        builder.setWhen(0);
        builder.setShowWhen(false);

        Intent notificationIntent = new Intent(this, BridgeServerActivity.class);
        PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0);
        builder.setContentIntent(pendingIntent);

        NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            manager.createNotificationChannel(new NotificationChannel("1", "bridge_service", NotificationManager.IMPORTANCE_NONE));
        }
        Notification notification = builder.build();
        startForeground(1, notification);
    }


    /* Becuase stopSrvice() on Destroy() will kill all application process of service, activity clearly
     * Do not use this function for undead foreground service. It's not meaningful */
    private void restartServiceByAlarmManager() {

        final Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(System.currentTimeMillis());
        calendar.add(Calendar.SECOND, 3);
        Intent intent = new Intent(this, AlarmReceiver.class);
        PendingIntent sender = PendingIntent.getBroadcast(this, 0,intent,0);
        AlarmManager alarmManager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
        alarmManager.set(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), sender);
    }

    public void scheduledTaskInit () {

        /* How to use ExecuterService like followings
         * executorService.schedule(Classname::someTask, delayInSeconds, TimeUnit.SECONDS);                 // once after a delay
         * executorService.scheduleAtFixedRate(Classname::someTask, 0, delayInSeconds, TimeUnit.SECONDS);   // at fixed time interval
         */
        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // load pair-wised device UUIDs from Bridge Credentials
                loadPairwisedDeviceUUIDs();
                // clear all pair-wised device list discovered pre-time
                clearPairwisedDevices();

                if(OCMain.isOwnedDevice(Bridge.BRIDGE_INDEX)) {
                    // discover all pair-wised device except Bridge and OBT
                    OCUuid bridge_di = OCCoreRes.getDeviceId(Bridge.BRIDGE_INDEX);
                    if (!OcUtils.discoverAllDevices(new DeviceDiscoveryHandler(getService(), bridge_di))) {
                        final String msg = "Failed to discover devices";
                        Log.d(TAG, msg);
                    }
                    Log.d(TAG, "DeviceDiscovery ScheduledTask per 90sec was called.");
                }
            }
        }, 10, 90, TimeUnit.SECONDS); //90 sec interval after 10sec delay

        executorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                // Getting Properites of Light Control Service for resource monitoring
                Log.d(TAG, "LightControl Resources Retrieve ScheduledTask per 180sec was called.");
                doGetLightControlResources();
            }
        }, 30, 180, TimeUnit.SECONDS); // 30sec interval after 30sec delay

    }

    public void loadPairwisedDeviceUUIDs() {
        if(bridgeServer!=null) {
            bridgeServer.loadPairwisedDeviceUUIDs();
        }
    }

    public void clearPairwisedDevices() {
        if(bridgeServer!=null) {
            bridgeServer.clearPairwisedDevices();
        }
    }

    public void addPairwisedDevice(OcRemoteDevice device) {
        if(bridgeServer!=null) {
            bridgeServer.addOrReplacePairwisedDevice(device);
            mHandler.sendEmptyMessage(MSG_DEVICE_DISCOVERED);
        }
    }

    public boolean hasPairwisedDeviceUUIDs(OCUuid uuid) {
        if(bridgeServer!=null) {
            return bridgeServer.hasPairwisedDeviceUUIDs(uuid);
        }
        return false ;
    }

    public OcRemoteDevice findPairwisedRemoteDevice(String device_uuid) {
        if(bridgeServer!=null) {
            return bridgeServer.getPairwisedDevice(device_uuid);
        }
        return null ;
    }

    public String getAllDiscoveredDevices() {

        if(bridgeServer!=null) {
            ArrayList<OcRemoteDevice> devices = bridgeServer.getPairwisedDevices();
            try {
                JSONArray array = new JSONArray();
                for (OcRemoteDevice device : devices) {
                    JSONObject obj = new JSONObject();
                    obj.put("name", device.getName());
                    obj.put("di", OCUuidUtil.uuidToString(device.getDeviceId()));
                    obj.put("piid", device.getProtocolIndependentId());
                    obj.put("icv", device.getSpecVersion());
                    obj.put("dmv", device.getDataModelVersion());
                    array.put(obj);
                }
                return array.toString().replace("\\", "");
            } catch (JSONException ex) {
                Log.e(TAG, "JSON Error : ", ex);
            }
        }
        return null ;
    }

    public String getDeviceResources(String device_uuid) {
        if(bridgeServer!=null) {
            OcRemoteDevice device = bridgeServer.getPairwisedDevice(device_uuid);
            OcRemoteResource[] resources = device.getResources();
            try {
                JSONArray array = new JSONArray();
                for (OcRemoteResource resource : resources) {
                    if(Utils.isDeviceSpecificResource(resource.getTypes())) {
                        JSONObject obj = new JSONObject();
                        obj.put("anchor", resource.getAnchor());
                        obj.put("uri", resource.getUri());
                        obj.put("rt", Arrays.toString(resource.getTypes()));
                        obj.put("eps", Arrays.toString(resource.getEndpoints()));
                        obj.put("if", Utils.interfacesMaskToString(resource.getInterfaceMask()));
                        obj.put("pm", Utils.propertiesMaskToString(resource.getResourcePropertiesMask()));
                        array.put(obj);
                    }
                }
                return array.toString().replace("\\", "");
            } catch (JSONException ex) {
                Log.e(TAG, "JSON Error : ", ex);
            }
        }
        return null ;
    }

    public boolean doGetDeviceResource(String device_uuid, String resource_uri) {
        if(bridgeServer!=null) {

            OcRemoteResource resource = bridgeServer.getPairwisedResource(device_uuid, resource_uri);
            if(resource==null) return false ;

            GetResponseHandler responseHandler = new GetResponseHandler(getService(), resource);
            if(OcUtils.doGet(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()),
                    null, responseHandler, OCQos.LOW_QOS)) {
                return true ;
            }
        }
        return false ;
    }

    public boolean doGetDeviceSpecificResources(String device_uuid) {
            OcRemoteDevice remoteDevice = findPairwisedRemoteDevice(device_uuid);
            if (remoteDevice == null) return false ;

            int count = 0 ;
            for(OcRemoteResource resource : remoteDevice.getResources()) {
                if(Utils.isDeviceSpecificResource(resource.getTypes())) {
                    GetResponseHandler responseHandler = new GetResponseHandler(getService(), resource);
                    if(OcUtils.doGet(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()),
                            null, responseHandler, OCQos.LOW_QOS)) {
                        count++;
                    }
                }
            }

            if(count > 0 ) return true ;
            else return false ;
    }

    public boolean doPostDeviceResource(String device_uuid, String resource_uri, String prop_data)  {

        if(bridgeServer!=null) {

            OcRemoteResource resource = bridgeServer.getPairwisedResource(device_uuid, resource_uri);
            if(resource==null) return false ;

            Log.d(TAG,"Post Request called.");
            PostResponseHandler post = new PostResponseHandler(getService(), resource);
            if (OcUtils.initPost(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()), null, post, OCQos.LOW_QOS)) {

                try {
                    JSONObject json = new JSONObject(prop_data);
                    Iterator<String> it = json.keys();
                    OcCborEncoder root = OcCborEncoder.createOcCborEncoder(OcCborEncoder.EncoderType.ROOT);
                    while (it.hasNext()) {
                        String key = it.next();
                        Object value = json.get(key);

                        if (value instanceof Boolean) {
                            root.setBoolean(key, (Boolean)value);
                        } else if (value instanceof Integer) {
                            root.setLong(key, (Integer)value);
                        } else if (value instanceof Long) {
                            root.setLong(key, (Long)value);
                        } else if (value instanceof Double) {
                            root.setDouble(key, (Double)value);
                        } else if (value instanceof String) {
                            root.setTextString(key, (String)value);
                        }
                    }
                    root.done();
                } catch (JSONException ex) {
                    Log.d(TAG, "Json Parsing Error : ", ex);
                }

                if (OcUtils.doPost()) {
                    Log.d(TAG, "\tSent POST request");
                    return true ;
                } else {
                    Log.d(TAG, "\tCould not send POST request");
                    return false ;
                }
            } else {
                Log.d(TAG, "\tCould not init POST request");
                return false ;
            }
        }
        return false ;
    }

    public boolean doPutDeviceResource(String device_uuid, String resource_uri, String prop_data)  {

        if(bridgeServer!=null) {

            OcRemoteResource resource = bridgeServer.getPairwisedResource(device_uuid, resource_uri);
            if(resource==null) return false ;

            Log.d(TAG,"Put Request called.");
            PutResponseHandler put = new PutResponseHandler(getService(), resource);
            if (OcUtils.initPut(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()), null, put, OCQos.LOW_QOS)) {

                try {
                    JSONObject json = new JSONObject(prop_data);
                    Iterator<String> it = json.keys();
                    OcCborEncoder root = OcCborEncoder.createOcCborEncoder(OcCborEncoder.EncoderType.ROOT);
                    while (it.hasNext()) {
                        String key = it.next();
                        Object value = json.get(key);

                        if (value instanceof Boolean) {
                            root.setBoolean(key, (Boolean)value);
                        } else if (value instanceof Integer) {
                            root.setLong(key, (Integer)value);
                        } else if (value instanceof Long) {
                            root.setLong(key, (Long)value);
                        } else if (value instanceof Double) {
                            root.setDouble(key, (Double)value);
                        } else if (value instanceof String) {
                            root.setTextString(key, (String)value);
                        }
                    }
                    root.done();
                } catch (JSONException ex) {
                    Log.d(TAG, "Json Parsing Error : ", ex);
                }

                if (OcUtils.doPut()) {
                    Log.d(TAG, "\tSent PUT request");
                    return true ;
                } else {
                    Log.d(TAG, "\tCould not send PUT request");
                    return false ;
                }
            } else {
                Log.d(TAG, "\tCould not init PUT request");
                return false ;
            }
        }
        return false ;
    }

    public boolean doObserveDeviceSpecificResources(String device_uuid) {

        OcRemoteDevice remoteDevice = findPairwisedRemoteDevice(device_uuid);
        if (remoteDevice == null) return false ;

        /* Observe only device specfic resources until shutdown or until stop Observe.
         */
        int count = 0 ;
        for(OcRemoteResource resource : remoteDevice.getResources()) {
            if(Utils.isDeviceSpecificResource(resource.getTypes())) {
                ObserveResponseHandler observerHandler = new ObserveResponseHandler(getService(), resource);
                if(OcUtils.doObserve(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()), null, observerHandler, OCQos.LOW_QOS)) {
                    count++;
                }
            }
        }

        if(count > 0 ) return true ;
        else return false ;
    }

    public boolean doObserveResource(String device_uuid, String resource_uri) {

        OcRemoteDevice remoteDevice = findPairwisedRemoteDevice(device_uuid);
        if (remoteDevice == null) return false ;

        for(OcRemoteResource resource : remoteDevice.getResources()) {
            if(resource.getUri().equals(resource_uri)) {
                ObserveResponseHandler observerHandler = new ObserveResponseHandler(getService(), resource);
                if(OcUtils.doObserve(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()), null, observerHandler, OCQos.LOW_QOS)) {
                    return true;
                }
            }
        }
        return false ;
    }

    public boolean stopObserveDeviceSpecificResources(String device_uuid) {

        OcRemoteDevice remoteDevice = findPairwisedRemoteDevice(device_uuid);
        if (remoteDevice == null) return false ;

        int count = 0 ;
        for(OcRemoteResource resource : remoteDevice.getResources()) {
            if(Utils.isDeviceSpecificResource(resource.getTypes())) {
                if(OcUtils.stopObserve(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()))) {
                    count++;
                }
            }
        }

        if(count > 0 ) return true ;
        else return false ;
    }

    public boolean stopObserveResource(String device_uuid, String resource_uri) {

        OcRemoteDevice remoteDevice = findPairwisedRemoteDevice(device_uuid);
        if (remoteDevice == null) return false ;

        for(OcRemoteResource resource : remoteDevice.getResources()) {
            if (resource.getUri().equals(resource_uri)) {
                if(OcUtils.stopObserve(resource.getUri(), Utils.getCoapsEndpoint(resource.getEndpoints()))) {
                    return true;
                }
            }
        }
        return false ;
    }
}
