package org.iotivity.bridge.homeserver;

import android.provider.Settings;
import android.util.Log;

import org.iotivity.OCBridge;
import org.iotivity.OCCoreRes;
import org.iotivity.OCCred;
import org.iotivity.OCCredType;
import org.iotivity.OCCredUtil;
import org.iotivity.OCCreds;
import org.iotivity.OCDeviceInfo;
import org.iotivity.OCInterfaceMask;
import org.iotivity.OCIntrospection;
import org.iotivity.OCMain;
import org.iotivity.OCMethod;
import org.iotivity.bridge.homeserver.handler.BridgeInitHandler;
import org.iotivity.bridge.homeserver.handler.FactoryPresetsHandler;
import org.iotivity.bridge.homeserver.handler.server.GetLightRequestHandler;
import org.iotivity.bridge.homeserver.handler.server.GetRequestHandler;
import org.iotivity.bridge.homeserver.handler.server.PostLightRequestHandler;
import org.iotivity.bridge.homeserver.handler.server.PostRequestHandler;
import org.iotivity.bridge.homeserver.handler.server.PutLightRequestHandler;
import org.iotivity.bridge.homeserver.handler.server.PutRequestHandler;
import org.iotivity.bridge.homeserver.model.Bridge;
import org.iotivity.bridge.homeserver.model.Light;
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.OcBridge;
import org.iotivity.oc.OcPlatform;
import org.iotivity.OCResource;
import org.iotivity.oc.OcRemoteDevice;
import org.iotivity.oc.OcRemoteResource;
import org.iotivity.oc.OcUtils;
import org.iotivity.OCUuidUtil;
import org.iotivity.OCUuid;
import org.iotivity.OCVirtualDevice;
import org.iotivity.oc.OcVirtualDevice;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Properties;

public class BridgeServer {

    private static final String TAG = BridgeServer.class.getSimpleName();

    // Virtual OCF Device's unique identifier such as uuid, MEID, serial number, mac address
    public static final String LIGHT_UUID = "2b32e152-3756-4fb6-b3f2-d8db7aafe39f" ;
    public static final String LIGHT2_UUID = "645b5166-3efb-4021-6ba0-4db90d434838";
    public static final String CLIENT_UUID = "f2f0109f-ef7d-496a-9676-d3d87b38e52f";

    private OcPlatform ocPlatform;
    private OcBridge ocBridge ;
    private BridgeInitHandler initHandler ;

    private BridgeService service = null ;

    private Bridge bridge = null ;
    private Light light = null ;

    private HashMap<OcRemoteDevice, Long> pairwised_devices = new HashMap<OcRemoteDevice, Long>();
    private ArrayList<OCUuid> pairwised_credential_uuids = new ArrayList<OCUuid>();

    public BridgeServer(BridgeService service) {
        this.service = service;
        this.bridge = new Bridge("Energy Home Hub");

        // Note: If using a factory presets handler,
        // the factory presets handler must be set prior to calling systemInit().
        // The systemInit() function will cause the factory presets handler to
        // be called if it is set.
        OcUtils.setFactoryPresetsHandler(new FactoryPresetsHandler(this.service));

        ocPlatform = OcPlatform.getInstance();
        initHandler = new BridgeInitHandler(this.service, ocPlatform, bridge);
        ocPlatform.systemInit(initHandler);

        this.initBridgeServer();
    }

    public void initBridgeServer() {

        this.addLightVirtualOCFServer(BridgeServer.LIGHT_UUID, "Light Switch", "ocf");
        //this.addVirtualOCFClient(BridgeServer.CLIENT_UUID, "Virtual OCF Client", "ocf");

        this.loadPairwisedDeviceUUIDs();
    }

    public OCUuid getBridgeDeviceId() {
        return OCCoreRes.getDeviceId(Bridge.BRIDGE_INDEX);
    }
    public void shutdown() {
        ocPlatform.systemShutdown();
    }

    public void resetBridgeServer() {

        OCMain.resetDevice(Bridge.BRIDGE_INDEX);
        // or ocPlatform.reset();

        // this below logic has some issue for now so don't use
        //ocPlatform.reset();
        //ocPlatform.systemShutdown();
        //ocPlatform.systemInit(initHandler);
    }

    public void deleteVirtualOCFDevices() {
        int vod_count = OCBridge.getVirtualDeviceCount();
        Log.d(TAG, "Virtual OCF Device Count = [" + vod_count + "], Core Device Nums = ["+OCCoreRes.getNumDevices()+"] ");

        for (int index = 1; index <= vod_count; index++) {
            OCVirtualDevice vod = OCBridge.getVirtualDeviceInfo(index);
            if (vod != null) {
                OCBridge.removeVirtualDevice(vod.getIndex());  // Note : This removes vod in vods_list and shutdown connectivity
                OCBridge.deleteVirtualDevice(vod.getIndex()); //  Note : This removes piid, core device, vod map at index and OCMain.resetDevice(index);
                Log.d(TAG, "Virtual OCF Device [" + vod.getIndex() + "] was reset!");
            }
        }
        //TODO : need to check Memory data
        bridge.clearVirtualOCFDeviceList();
    }

    public void deleteVirtualOCFDevice(String device_sn) {
        int vod_count = OCBridge.getVirtualDeviceCount();
        Log.d(TAG, "Virtual OCF Device Count = [" + vod_count + "], Core Device Nums = ["+OCCoreRes.getNumDevices()+"] ");
        for (int index = 1; index <= vod_count; index++) {
            OCVirtualDevice vod = OCBridge.getVirtualDeviceInfo(index);
            if (vod != null && OCUuidUtil.bytesToString(vod.getId()).equalsIgnoreCase(device_sn)) {
                this.deleteVirtualOCFDevice((int)vod.getIndex());
                Log.d(TAG, "Virtual OCF Device [" + vod.getIndex() + "] was deleted!");
            }
        }
    }

    public void deleteVirtualOCFDevice(int vod_index) {
        OCBridge.removeVirtualDevice(vod_index);  // Note : This removes vod in vods_list and shutdown connectivity
        OCBridge.deleteVirtualDevice(vod_index); //  Note : This removes piid, core device, vod map at index and OCMain.resetDevice(index);

        //TODO : need to check Memory data
        bridge.removeVirtualOCFDevice(vod_index);
    }

    public void removeVirtualOCFDevices() {

        ArrayList<VirtualOCFDevice> vods = (ArrayList<VirtualOCFDevice>)bridge.getVirtualOCFDeviceList().clone();
        for(VirtualOCFDevice vod : vods) {
            this.removeVirtualOCFDevice(vod.getVODIndex());
        }
    }

    public void removeVirtualOCFDevice(int vod_index) {

        OCVirtualDevice vod_light = OCBridge.getVirtualDeviceInfo(vod_index);
        if(vod_light != null) {
            Log.d(TAG, "Virtual Server[" + vod_light.getIndex() + "] "
                    + ", DeviceId = " + OCUuidUtil.bytesToString(vod_light.getId())
                    + ", Econame = " + vod_light.getEconame() + ", Name = " + vod_light.getEconame()
                    + " will be removed");

            int ret = OCBridge.removeVirtualDevice(vod_index);
            if (ret >=0) {
                bridge.removeVirtualOCFDevice(vod_index);
            }
        }
    }

    public void resetVirtualOCFDevices() {

        int dev_count = (int)OCCoreRes.getNumDevices();
        Log.d(TAG, "VOD Nums = ["+OCBridge.getVirtualDeviceCount()+"], Core Res Nums = ["+OCCoreRes.getNumDevices()+"]");
        for (int index = 1 ; index <= dev_count; index++) {
            /*OCVirtualDevice vod = OCBridge.getVirtualDeviceInfo(index);
            if(vod!=null) continue;
            Log.d(TAG, "VODs Device [" + index + ", "+vod.getIndex()+", vod_id="+OCUuidUtil.bytesToString(vod.getId())+ "]");
*/
            OCDeviceInfo dev = OCCoreRes.getDeviceInfo(index);
            if(dev != null) {
                Log.d(TAG, "Core OCF Device [" + index +" piid="+OCUuidUtil.uuidToString(dev.getPiid())+ "]");
                if(dev.getName() != null) {
                    Log.d(TAG, "Core OCF Device [" + index + "] "+dev.getName()+" was reset!");
                    this.resetDevice(index);
                }
            }
        }
    }

    public void resetVirtualOCFDevice(OCUuid device_uuid) {
        int dev_count = (int)OCCoreRes.getNumDevices();
        Log.d(TAG, "Core Device Nums = ["+dev_count+"] ");
        for (int index = 1; index <= dev_count; index++) {
            OCDeviceInfo dev = OCCoreRes.getDeviceInfo(index);
            if (dev != null && OCUuidUtil.isEqual(dev.getDi(),device_uuid)) {
                this.resetDevice(index);
                Log.d(TAG, "Core OCF Device [" + index + "] "+dev.getName()+" was reset!");
            }
        }
    }

    public void resetVirtualOCFDevice(String device_uuid) {
        int dev_count = (int)OCCoreRes.getNumDevices();
        Log.d(TAG, "Core Device Nums = ["+dev_count+"] ");
        for (int index = 1; index <= dev_count; index++) {
            OCDeviceInfo dev = OCCoreRes.getDeviceInfo(index);
            if (dev != null && OCUuidUtil.uuidToString(dev.getDi()).equalsIgnoreCase(device_uuid)) {
                this.resetDevice(index);
                Log.d(TAG, "Core OCF Device [" + index + "] "+dev.getName()+" was reset!");
            }
        }
    }

    public void resetVirtualOCFDeviceByDevicePiid(String device_sn) {
        int dev_count = (int)OCCoreRes.getNumDevices();
        Log.d(TAG, "Core Device Nums = ["+dev_count+"] ");
        for (int index = 1; index <= dev_count; index++) {
            OCDeviceInfo dev = OCCoreRes.getDeviceInfo(index);
            if (dev != null && OCUuidUtil.uuidToString(dev.getPiid()).equalsIgnoreCase(device_sn)) {
                this.resetDevice(index);
                Log.d(TAG, "Core OCF Device [" + index + "] "+dev.getName()+" was reset!");
            }
        }
    }

    public void resetDevice(int device_index) {

        if(device_index < 0) return ;

        OCDeviceInfo info = OCCoreRes.getDeviceInfo(device_index);
        if(info == null) return ;
        if(device_index == Bridge.BRIDGE_INDEX) {
            Log.d(TAG, "Bridge Server[" + Bridge.BRIDGE_INDEX + "] DeviceId = " + OCUuidUtil.uuidToString(info.getDi())
                    + " will reset");
            this.resetBridgeServer();

        } else if(device_index > 0) {
            OCVirtualDevice vod = OCBridge.getVirtualDeviceInfo(device_index);
            if (vod != null) {
                Log.d(TAG, "Virtual OCF Server/Client[" + vod.getIndex() + "] DeviceId = " + OCUuidUtil.uuidToString(info.getDi())
                        + ", echoname = " + vod.getEconame() + ", name = "+info.getName()+" will be reset");
            }
            //this.resetVirtualOCFDevice(device_index);
            OCMain.resetDevice(device_index);
        }
    }

    /*public void loadVirtualOCFDevices() {
        int vod_count = OCBridge.getVirtualDeviceCount();
        Log.d(TAG, "Virtual OCF Device Count = [" + vod_count + "], Core Device Nums = ["+OCCoreRes.getNumDevices()+"] ");

        for(int index = 1; index <= vod_count; index++) {
            OCVirtualDevice vod = OCBridge.getVirtualDeviceInfo(index);
            if(vod != null) {
                String uuid = OCUuidUtil.bytesToString(vod.getId());

                Log.d(TAG,"OCVirtualDevice["+index+"] econame = " + vod.getEconame() +", DI = " + uuid);

                // Add VOD Data & State on memory of BridgeServer
                VirtualOCFDevice vod_data = new VirtualOCFDevice(index, vod.getName(), vod.getEconame(), uuid);
                if(OCMain.isOwnedDevice(index)) vod_data.setOwned(true);
                bridge.addOrReplaceVODList(vod_data);
            }
        }
    }*/

    public boolean existVirtualOCFDevice(String virtual_di, String econame) {

        for(VirtualOCFDevice vod : bridge.getVirtualOCFDeviceList()){
            if(vod.getVirtualDeviceId().equals(virtual_di) && vod.getEcoName().equals(econame)) {
                return true ;
            }
        }
        return false ;
    }

    public VirtualOCFDevice addVirtualOCFDevice(String device_name, String virtual_di, String device_type, String eco_name) {

        // Add Virtual Device for Light
        Log.d(TAG, "virtual_di = " + virtual_di);
        OCUuid uuid = OCUuidUtil.stringToUuid(virtual_di);
        Log.d(TAG, "virtual_di = " + OCUuidUtil.uuidToString(uuid));
        int vod_index = OCBridge.addVirtualDevice(uuid.getId(),
                eco_name, "/oic/d", device_type,  // "oic.d.virtual" automatically added by internal api implements
                device_name, "ocf.2.2.0", "ocf.res.1.3.0,ocf.sh.1.3.0");

        if (vod_index != 0) {
            Log.d(TAG, "VOD Index = " + vod_index + ", VOD Count = " + OCBridge.getVirtualDeviceCount());

            OCDeviceInfo dev_info = OCCoreRes.getDeviceInfo(vod_index);
            OCVirtualDevice vod_info = OCBridge.getVirtualDeviceInfo(vod_index);
            Log.d(TAG, "VOD Info = " + vod_info);
            if (vod_info != null) {
                Log.d(TAG, "Virtual Server [" + vod_info.getIndex() + "] "
                        + " Virtual Device Id = " + OCUuidUtil.bytesToString(vod_info.getId())
                        + ", Econame = " + vod_info.getEconame() + ", Name = " + dev_info.getName()
                        + " was added");


                // Add VOD Data
                VirtualOCFDevice vod = new VirtualOCFDevice(vod_index, device_name, eco_name,
                        OCUuidUtil.bytesToString(vod_info.getId()), device_type);
                vod.setDiscovered(true);
                vod.setAddedToBridge(true);
                bridge.addOrReplaceVirtualOCFDevice(vod);

                // set immutable device id for Platform Independent Identifier (piid)
                OCMain.setImmutableDeviceIdentifier(vod_index, OCUuidUtil.stringToUuid(virtual_di));
                Log.d(TAG, "Virtual Server [" + vod_info.getIndex() + "] "
                        + " Virtual Device Id = " + virtual_di
                        + ", Device Id = " + OCUuidUtil.uuidToString(dev_info.getDi())
                        + ", Device Piid = " + OCUuidUtil.uuidToString(dev_info.getPiid()));

                // Add OCF Virtual Device IDD
                //byte[] idd_bytes = Utils.getFileBytes("idds/virtual_ocf_device_light_IDD.cbor", activity); // example : "dummy_bridge_bridge_device_IDD.cbor"
                //OCIntrospection.setIntrospectionData(vod_index, idd_bytes);

                return vod ;
            }
        }
        return null ;
    }

    public void addVirtualDeviceResource(VirtualOCFDevice device, String resource_name, String resource_uri, String resource_type, String resource_if) {

        device.addResource(resource_name, resource_uri, resource_type, resource_if);
        int interfaceMask = Utils.interfacesStringToMask(resource_if);

        // Add Device Resource and Handler
        OCResource resource = OCMain.newResource(resource_name, resource_uri, (short)1, (long)device.getVODIndex());
        OCMain.resourceBindResourceType(resource, resource_type);
        OCMain.resourceBindResourceInterface(resource, interfaceMask);
        OCMain.resourceSetDefaultInterface(resource, interfaceMask);
        OCMain.resourceSetDiscoverable(resource, true);
        OCMain.resourceSetObservable(resource, true);
        //TODO : check if it is needed for notifying to client
        //OCMain.resourceSetPeriodicObservable(resource, 60); // seconds
        OCMain.resourceSetRequestHandler(resource, OCMethod.OC_GET, new GetRequestHandler(service, device));
        OCMain.resourceSetRequestHandler(resource, OCMethod.OC_POST, new PostRequestHandler(service, device));
        OCMain.resourceSetRequestHandler(resource, OCMethod.OC_PUT, new PutRequestHandler(service, device));
        OCMain.addResource(resource);

        device.addOCResource(resource);
    }

    public boolean addLightVirtualOCFServer(String virtual_di, String devicename, String econame) {

        // Add Virtual Device for Light
        OCUuid uuid = OCUuidUtil.stringToUuid(virtual_di) ;
        int vod_index = OCBridge.addVirtualDevice(uuid.getId(),
                econame, "/oic/d", "oic.d.light",  // "oic.d.virtual" automatically added by internal api implements
                devicename, "ocf.2.2.0", "ocf.res.1.3.0,ocf.sh.1.3.0");

        if (vod_index != 0) {
            Log.d(TAG, "VOD Index = "+ vod_index + ", VOD Count = " + OCBridge.getVirtualDeviceCount());

            light = new Light(devicename);
            OCDeviceInfo dev_info = OCCoreRes.getDeviceInfo(vod_index);
            OCVirtualDevice vod_info = OCBridge.getVirtualDeviceInfo(vod_index);
            Log.d(TAG, "VOD Info = "+ vod_info);
            if(vod_info != null) {
                Log.d(TAG, "Virtual Server [" + vod_info.getIndex() + "] "
                        + " Virtual Device Id = " + OCUuidUtil.bytesToString(vod_info.getId())
                        + ", Econame = " + vod_info.getEconame() + ", Name = " + dev_info.getName()
                        + " was added");

                // Add VOD Data
                VirtualOCFDevice vod = new VirtualOCFDevice(vod_index, devicename, econame,
                        OCUuidUtil.bytesToString(vod_info.getId()), "oic.d.light");
                vod.setDiscovered(true);
                vod.setAddedToBridge(true);
                bridge.addOrReplaceVirtualOCFDevice(vod);

                // Add Light Device Resources and Handler
                OCResource res_light = OCMain.newResource("Light Switch", "/light", (short)1, (long)vod_index);
                OCMain.resourceBindResourceType(res_light, "oic.r.switch.binary");  // BinarySwitch Resource Type
                OCMain.resourceBindResourceInterface(res_light, OCInterfaceMask.RW);
                OCMain.resourceSetDefaultInterface(res_light, OCInterfaceMask.RW);
                OCMain.resourceSetDiscoverable(res_light, true);
                OCMain.resourceSetObservable(res_light, true);
                //TODO : check if it is needed for notifying to client
                //OCMain.resourceSetPeriodicObservable(res_light, 60); // seconds
                OCMain.resourceSetRequestHandler(res_light, OCMethod.OC_GET, new GetLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_light, OCMethod.OC_POST, new PostLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_light, OCMethod.OC_PUT, new PutLightRequestHandler(service, light));
                OCMain.addResource(res_light);
                vod.addOCResource(res_light);

                // Add Dimming Resource
                OCResource res_dimming = OCMain.newResource("Dimming Control", "/dimming", (short) 1, (long)vod_index);
                OCMain.resourceBindResourceType(res_dimming, "oic.r.light.dimming");
                OCMain.resourceBindResourceInterface(res_dimming, OCInterfaceMask.RW);
                OCMain.resourceSetDefaultInterface(res_dimming, OCInterfaceMask.RW);
                OCMain.resourceSetDiscoverable(res_dimming, true);
                OCMain.resourceSetObservable(res_dimming, true);
                //TODO : check if it is needed for notifying to client
                //OCMain.resourceSetPeriodicObservable(res_dimming, 60); // seconds
                OCMain.resourceSetRequestHandler(res_dimming, OCMethod.OC_GET, new GetLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_dimming, OCMethod.OC_POST, new PostLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_dimming, OCMethod.OC_PUT, new PutLightRequestHandler(service, light));
                OCMain.addResource(res_dimming);
                vod.addOCResource(res_dimming);

                // Add ColorTemperature Resource
                OCResource res_colortemp = OCMain.newResource(Light.RESOURCE_NAME_COLORTMEP, Light.URI_COLORTMEP, (short) 1, (long)vod_index);
                OCMain.resourceBindResourceType(res_colortemp, Light.RT_COLORTEMP);
                OCMain.resourceBindResourceInterface(res_colortemp, OCInterfaceMask.A);
                OCMain.resourceSetDefaultInterface(res_colortemp, OCInterfaceMask.A);
                OCMain.resourceSetDiscoverable(res_colortemp, true);
                OCMain.resourceSetObservable(res_colortemp, true);
                //TODO : check if it is needed for notifying to client
                //OCMain.resourceSetPeriodicObservable(res_dimming, 60); // seconds
                OCMain.resourceSetRequestHandler(res_colortemp, OCMethod.OC_GET, new GetLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_colortemp, OCMethod.OC_POST, new PostLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_colortemp, OCMethod.OC_PUT, new PutLightRequestHandler(service, light));
                OCMain.addResource(res_colortemp);
                vod.addOCResource(res_colortemp);

                // Add EnergyConsumption Resource
                OCResource res_energy = OCMain.newResource(Light.RESOURCE_NAME_ENERGY, Light.URI_ENERGY, (short)1, (long)vod_index);
                OCMain.resourceBindResourceType(res_energy, Light.RT_ENERGY);
                OCMain.resourceBindResourceInterface(res_energy, OCInterfaceMask.A);
                OCMain.resourceSetDefaultInterface(res_energy, OCInterfaceMask.A);
                OCMain.resourceSetDiscoverable(res_energy, true);
                OCMain.resourceSetObservable(res_energy, true);
                //TODO : check if it is needed for notifying to client
                //OCMain.resourceSetPeriodicObservable(res_dimming, 60); // seconds
                OCMain.resourceSetRequestHandler(res_energy, OCMethod.OC_GET, new GetLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_energy, OCMethod.OC_POST, new PostLightRequestHandler(service, light));
                OCMain.resourceSetRequestHandler(res_energy, OCMethod.OC_PUT, new PutLightRequestHandler(service, light));
                OCMain.addResource(res_energy);
                vod.addOCResource(res_energy);

                // set immutable device id for Platform Independent Identifier (piid)
                OCMain.setImmutableDeviceIdentifier(vod_index, OCUuidUtil.stringToUuid(virtual_di));
                Log.d(TAG, "Virtual Device [" + vod_info.getIndex() + "] "
                        + " Virtual Device Id = " + virtual_di
                        + ", Device Id = " + OCUuidUtil.uuidToString(dev_info.getDi())
                        + ", Device Piid = " + OCUuidUtil.uuidToString(dev_info.getPiid()));

                // Add Light OCF Device IDD
                byte[] idd_bytes = getFileBytes("idds/virtual_ocf_device_light_IDD.cbor"); // example : "virtual_ocf_device_light_IDD.cbor"
                OCIntrospection.setIntrospectionData(vod_index, idd_bytes);

                return true ;
            }
        }
        return false ;
    }

    public void addVirtualOCFClient(String device_uuid, String devicename, String econame) {

        // Add Virtual Device for Legarcy Client like such as zibee coodinater
        OCUuid uuid = OCUuidUtil.stringToUuid(device_uuid) ;
        int vod_index = OCBridge.addVirtualDevice(uuid.getId(),
                econame, "/oic/d", "oic.d.virtualclient",  // "oic.d.virtual" automatically added by internal api implements
                devicename, "ocf.2.2.0", "ocf.res.1.3.0,ocf.sh.1.3.0");

        if(vod_index != 0) {
            OCDeviceInfo dev_info = OCCoreRes.getDeviceInfo(vod_index);
            OCVirtualDevice vod_client = OCBridge.getVirtualDeviceInfo(vod_index);
            if(vod_client != null){
                Log.d(TAG, "Virtual Client [" + vod_client.getIndex() + "] "
                        + " Virtual Device Id = " + OCUuidUtil.bytesToString(vod_client.getId())
                        + ", Econame = " + vod_client.getEconame() + ", Name = " + dev_info.getName()
                        + " was added");

                // Add VOD Data
                VirtualOCFDevice vod = new VirtualOCFDevice(vod_index, dev_info.getName(), vod_client.getEconame(),
                        OCUuidUtil.bytesToString(vod_client.getId()), "oic.d.virtualclient");
                vod.setDiscovered(true);
                vod.setAddedToBridge(true);
                bridge.addOrReplaceVirtualOCFDevice(vod);

                // set immutable device id for Platform Independent Identifier (piid)
                OCMain.setImmutableDeviceIdentifier(vod_index, uuid);
                Log.d(TAG, "Virtual Device [" + vod_client.getIndex() + "] "
                        + " Virtual Device Id = " + device_uuid
                        + ", Device Id = " + OCUuidUtil.uuidToString(dev_info.getDi())
                        + ", Device Piid = " + OCUuidUtil.uuidToString(dev_info.getPiid()));

            }
        }

    }

    public ArrayList<VirtualOCFDevice> getVirtualOCFDeviceList() {
        return bridge.getVirtualOCFDeviceList();
    }

    public VirtualOCFDevice getVirtualOCFDevice(int vod_index) {
        return bridge.getVirtualOCFDevice(vod_index);
    }

    public VirtualOCFDevice getVirtualOCFDevice(String vod_id) {
        return bridge.getVirtualOCFDevice(vod_id);
    }

    public boolean isOwnedVirtualOCFDevice(int vod_index) {
        return OCMain.isOwnedDevice(vod_index);
    }

    public boolean addOrUpdateVirtualOCFDeviceProperties (String device_sn, String uri, String props) {
        VirtualOCFDevice device = getVirtualOCFDevice(device_sn);
        if(device != null && props!=null) {
            device.addProperties(uri, props);
            return true ;
        }
        return false ;
    }

    public String getVirtualOCFDeviceProperties(String device_sn, String uri) {
        VirtualOCFDevice device = getVirtualOCFDevice(device_sn);
        if(device != null) {
            return device.getProperties(uri);
        }
        return null ;
    }

    public boolean compareVirtualOCFDeviceProperties(String device_sn, String uri, String props) {
        VirtualOCFDevice device = getVirtualOCFDevice(device_sn);
        if(device != null) {
            return device.compareProperties(uri, props);
        }
        return false ;
    }

    public boolean existVirtualOCFDevice(String device_sn) {  // Virtual OCF ID (Serial Number or Piid)
        VirtualOCFDevice device = getVirtualOCFDevice(device_sn);
        if(device != null) {
            return existVirtualOCFDevice(device.getVirtualDeviceId(), device.getEcoName());
        }
        return false ;
    }

    public void disconnectVirtualDevice(int vod_index) {

        Log.d(TAG, "disconnectVirtualDevice vod_index = "+vod_index+") called.");
        VirtualOCFDevice vod = bridge.getVirtualOCFDevice(vod_index);
        if (vod != null) {
            vod.setDiscovered(false);
            vod.setAddedToBridge(false);

            OCDeviceInfo info = OCCoreRes.getDeviceInfo(vod_index);
            if(OCBridge.removeVirtualDevice(vod_index) == 0) {
                Log.d(TAG, "Virtual OCF Server/Client [" + vod.getVODIndex() + "] DeviceId = " + OCUuidUtil.uuidToString(info.getDi())
                        + ", echoname = " + vod.getEcoName() + " was removed from the bridge");
            } else {
                Log.d(TAG, "Failed to remove Virtual OCF Server/Client["+vod_index+"] "+info.getName()+" from the bridge");
            }
        }
    }

    public void disconnectAllVirtualDevices() {

        for (VirtualOCFDevice vod : bridge.getVirtualOCFDeviceList()) {
            Log.d(TAG, "disconnect All Virtual Devices.");
            if (vod != null) {

                vod.setDiscovered(false);
                vod.setAddedToBridge(false);

                OCDeviceInfo info = OCCoreRes.getDeviceInfo(vod.getVODIndex());
                if (OCBridge.removeVirtualDevice(vod.getVODIndex()) == 0) {
                    Log.d(TAG, "Virtual OCF Server/Client [" + vod.getVODIndex() + "] DeviceId = " + OCUuidUtil.uuidToString(info.getDi())
                            + ", echoname = " + vod.getEcoName() + " was removed from the bridge");
                } else {
                    Log.d(TAG, "Failed to remove Virtual OCF Server/Client[" + vod.getVODIndex() + "] " + info.getName() + " from the bridge");
                }
            }
        }
    }

    /* These APIs are related to online pairwised devices */
    public ArrayList<OcRemoteDevice> getPairwisedDevices() {
        ArrayList<OcRemoteDevice> pDevices = new ArrayList<>();
        Iterator<OcRemoteDevice> it = pairwised_devices.keySet().iterator();
        while(it.hasNext()){
            pDevices.add(it.next());
        }
        return pDevices.size() > 0 ? pDevices : null;
    }

    public void clearPairwisedDevices() {
        pairwised_devices.clear();
    }

    public boolean addOrReplacePairwisedDevice(OcRemoteDevice new_dev) {

        Iterator<OcRemoteDevice> it = pairwised_devices.keySet().iterator();
        while(it.hasNext()){
            OcRemoteDevice dev = it.next();
            if(OCUuidUtil.isEqual(dev.getDeviceId(), new_dev.getDeviceId())) {
                removePairwisedDevices(dev);
                return this.pairwised_devices.put(new_dev, Long.valueOf(System.currentTimeMillis())) == null ? false : true ;
            }
        }
        return this.pairwised_devices.put(new_dev, Long.valueOf(System.currentTimeMillis())) == null ? false : true ;

        /*ArrayList<OcRemoteDevice> clones = (ArrayList<OcRemoteDevice>)pairwised_devices.clone();
        for(OcRemoteDevice dev : clones) {
            if(OCUuidUtil.isEqual(dev.getDeviceId(), new_dev.getDeviceId())) {
                removePairwisedDevices(dev);
                return this.pairwised_devices.add(new_dev) ;
            }
        }
        return this.pairwised_devices.add(new_dev);*/
    }

    public boolean removePairwisedDevices(OcRemoteDevice dev) {
        return this.pairwised_devices.remove(dev) > 0;
    }

/*
    public boolean removePairwisedDevices(int index) {
        return pairwised_devices.remove(pairwised_devices.get(index));
    }

    public OcRemoteDevice getPairwisedDevice(int index) {
        return pairwised_devices.get(index);
    }
*/

    public OcRemoteDevice getPairwisedDevice(String device_uuid) {
        OCUuid uuid = OCUuidUtil.stringToUuid(device_uuid);
        Iterator<OcRemoteDevice> it = pairwised_devices.keySet().iterator();
        while(it.hasNext()){
            OcRemoteDevice dev = it.next();
            if(OCUuidUtil.isEqual(dev.getDeviceId(), uuid)) {
                return dev ;
            }
        }
        /*for(OcRemoteDevice device : pairwised_devices) {
            if(OCUuidUtil.isEqual(device.getDeviceId(), uuid)) {
                return device ;
            }
        }*/
        return null ;
    }

    public long getPairwisedDeviceTimeStamp(String device_uuid) {
        OCUuid uuid = OCUuidUtil.stringToUuid(device_uuid);
        Iterator<OcRemoteDevice> it = pairwised_devices.keySet().iterator();
        while(it.hasNext()){
            OcRemoteDevice dev = it.next();
            if(OCUuidUtil.isEqual(dev.getDeviceId(), uuid)) {
                return pairwised_devices.get(dev) ;
            }
        }
        return 0 ;
    }

    public boolean isPairwisedDeviceOnlineStatus(String device_uuid) {
        long current_time = System.currentTimeMillis() ;
        long timestamp = getPairwisedDeviceTimeStamp(device_uuid);
        Log.d(TAG, "isPairwisedDeviceOnlineStatus() : System.currentTimeMillis() = " + current_time + ", latest discovered time = " + timestamp);
        if((current_time - timestamp)
                < (BridgeService.PAIRWISED_DISCOVERY_PEROID * 1.8) * 1000L) {
            return true;
        }
        return false;
    }

    public OcRemoteResource getPairwisedResource(String device_uuid, String resource_uri) {
        OcRemoteDevice device = getPairwisedDevice(device_uuid);
        for(OcRemoteResource resource : device.getResources()) {
            if(resource.getUri().equals(resource_uri)) {
                return resource ;
            }
        }
        return null ;
    }

    /* These APIs are related to pairwised devices by OBT at least once */
    public ArrayList<OCUuid> getPairwisedCredentialUUIDs() {
        return pairwised_credential_uuids;
    }

    public void clearPairwisedCredentialUUIDs() {
        pairwised_credential_uuids.clear();
    }

    public boolean addOrReplacePairwisedCredentialUUIDs(OCUuid new_uuid) {
        for(OCUuid uuid : pairwised_credential_uuids) {
            if(OCUuidUtil.isEqual(uuid, new_uuid)) {
                return true ;
            }
        }
        return this.pairwised_credential_uuids.add(new_uuid);
    }

    public boolean removePairwisedCredentialUUIDs(OCUuid uuid) {
        return this.pairwised_credential_uuids.remove(uuid);
    }

    public boolean removePairwisedCredentialUUIDs(int index) {
        return pairwised_credential_uuids.remove(pairwised_credential_uuids.get(index));
    }

    public OCUuid getPairwisedCredentialUUIDs(int index) {
        return pairwised_credential_uuids.get(index);
    }

    public void loadPairwisedDeviceUUIDs() {

        OCCreds creds = OCCredUtil.getCreds(Bridge.BRIDGE_INDEX);
        OCCred cred = creds.getCredsListHead() ;
        while(cred!= null) {
            if(cred.getCredType() == OCCredType.OC_CREDTYPE_PSK && !cred.getOwnerCred()) {
                OCUuid uuid = cred.getSubjectUuid();
                this.addOrReplacePairwisedCredentialUUIDs(uuid);
            }
            cred = cred.getNext();
        }

        for(OCUuid uuid : getPairwisedCredentialUUIDs()) {
            Log.d(TAG, "loadPairwisedDevices : Subject UUID = " + OCUuidUtil.uuidToString(uuid));
        }
    }

    public boolean hasPairwisedDeviceUUIDs(OCUuid uuid) {
        for(OCUuid has : getPairwisedCredentialUUIDs()) {
            if(OCUuidUtil.isEqual(has, uuid)) {
                return true ;
            }
        }
        return false ;
    }

    private byte[] getFileBytes(String filepath) {

        ByteArrayOutputStream baos = null;
        try {
            InputStream is = service.getAssets().open(filepath);
            Log.d(TAG, "reading file " + filepath);

            try {
                byte[] buffer = new byte[8192];
                baos = new ByteArrayOutputStream();
                int length = 0;
                while ((length = is.read(buffer)) != -1) {
                    //Log.d(TAG, "bytes read = " + length);
                    baos.write(buffer, 0, length);
                }
            } catch (IOException e) {
                Log.e(TAG, "Error reading file " + filepath);
            } finally {
                try {
                    if (baos != null) {
                        baos.close();
                    }
                } catch (IOException e) {
                    // ignore
                }
                try {
                    if (is != null) {
                        is.close();
                    }
                } catch (IOException e) {
                    // ignore
                }
            }

        } catch (IOException e) {
            Log.e(TAG, "Failed to read " + filepath);
        }

        if(baos!=null) {
            Log.d(TAG, "bytes length = " + baos.toByteArray().length);
            return baos.toByteArray() ;

            /*int len = baos.toByteArray().length ;
            if(baos.toByteArray()[len-1] != (byte)0x00 ) {
                byte[] result = Arrays.copyOf(baos.toByteArray(), len + 1);
                result[len] = (byte) 0x00;
                Log.d(TAG, "bytes length = " + result.length);
                return result;
            } else {
                return baos.toByteArray() ;
            }*/
        }
        return null;
    }
}


