/*
 * Copyright (c) 2017 Oracle and/or its affiliates.  All rights reserved.
 *
 * This software is dual-licensed to you under the MIT License (MIT) and
 * the Universal Permissive License (UPL).  See the LICENSE file in the root
 * directory for license terms.  You may choose either license, or both.
 */

package com.oracle.iot.sample.enterpriseclientsample;

import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Environment;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import com.oracle.iot.sample.R;
import oracle.iot.client.AbstractVirtualDevice;
import oracle.iot.client.DeviceModel;
import oracle.iot.client.ExternalObject;
import oracle.iot.client.StorageObject;
import oracle.iot.client.enterprise.Device;
import oracle.iot.client.enterprise.EnterpriseClient;
import oracle.iot.client.enterprise.VirtualDevice;

import java.io.FileNotFoundException;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

public class MotionActivatedCameraSensorActivity extends Activity {
    private static final String ERROR_TAG = "IOT_ERROR";
    private static final String MSG_TAG = "IOT";
    private static final String IMAGE_ATTRIBUTE_NAME = "image";
    private static final String IMAGE_TIME_ATTRIBUTE_NAME = "imageTime";
    private static final String MOTION_ACTIVATED_CAMERA_MODEL_URN =
            "urn:com:oracle:iot:device:motion_activated_camera";
    private static final String MOTION_ACTIVATED_CAMERA_MODEL_RECORDING_URN =
            MOTION_ACTIVATED_CAMERA_MODEL_URN + ":recording";
    private static String MEDIA_DIR = Environment.getExternalStorageDirectory()
            + "/media_downloads/";
    private VirtualDevice vDevice;
    TextView currentImage;
    TextView currentImageTime;
    TextView manufacturer;
    TextView modelNumber;
    TextView recordingAlert;
    Context context;
    String deviceId;
    Button recordButton;
    EditText newRecordingIntervalText;

    // Map model format URN to its name
    private static final Map<String,String> formatNames = new HashMap<String, String>() {
        {
            put("urn:com:oracle:iot:device:motion_activated_camera:recording", "recordingAlert");
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_camera_sensor);
        EnterpriseClientUtil util = EnterpriseClientUtil.getInstance(this);
        util.setCurrentContext(this);
        deviceId = util.getSelectedDeviceToMonitor().device.getId();
        util.getSelectedDeviceToMonitor().device.getValue(Device.Field.MANUFACTURER);
        TextView deviceNameTV = (TextView)findViewById(R.id.deviceTitle);
        deviceNameTV.setText(deviceId);
        currentImage = (TextView)findViewById(R.id.imageValue);
        currentImageTime = (TextView)findViewById(R.id.imageTimeValue);
        newRecordingIntervalText = (EditText)findViewById(R.id.newRecordingIntervalValue);
        manufacturer = (TextView)findViewById(R.id.manufacturer);
        modelNumber= (TextView)findViewById(R.id.modelNumber);
        manufacturer.setText("Manufacturer: "+util.getSelectedDeviceToMonitor().device.getValue(Device.Field.MANUFACTURER));
        modelNumber.setText( "Model Number: "+util.getSelectedDeviceToMonitor().device.getValue(Device.Field.MODEL_NUMBER));
        recordingAlert = (TextView)findViewById(R.id.recordingAlert);
        recordingAlert.setVisibility(View.INVISIBLE);
        recordButton = (Button)findViewById(R.id.startRecordingButton);
        ContextWrapper contextWrapper = new ContextWrapper(getApplicationContext());

        recordButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                startRecording();
            }
        });
        newRecordingIntervalText.setOnKeyListener(new View.OnKeyListener() {
            public boolean onKey(View v, int keyCode, KeyEvent event) {
                // If the event is a key-down event on the "enter" button
                if ((event.getAction() == KeyEvent.ACTION_DOWN) &&
                        (keyCode == KeyEvent.KEYCODE_ENTER)) {
                    // Perform action on key press
                    startRecording();
                    return true;
                }
                return false;
            }
        });
        final SensorOperation sensorThread = new SensorOperation(getApplicationContext());
        sensorThread.execute(this);
    }

    private void startRecording(){
        try {

            Log.d(MSG_TAG, "Setting New Recording Interval -> " + newRecordingIntervalText.getText().toString());
            final String recordIntervalText = newRecordingIntervalText.getText().toString();
            new Thread()
            {
                @Override
                public void run()
                {
                    int recordInterval = Integer.parseInt(recordIntervalText);
                    vDevice.call("record", recordInterval);
                    super.run();
                }
            }.start();
        }catch(Exception e){
            Log.d(ERROR_TAG, e.getMessage());
        }
    }
    private class SensorOperation extends AsyncTask<Context, String, Void> {
        DeviceListItem dli = EnterpriseClientUtil.getInstance(null).getSelectedDeviceToMonitor();
        EnterpriseClient client;
        String image;
        DeviceModel deviceModel = null;
        String modelUrn = null;
        private boolean publishFinish;

        public SensorOperation (Context mContext){
            context = mContext;
            image = "";
        }

        @Override
        protected Void doInBackground(Context... params) {
            try {
                client = EnterpriseClientUtil.getInstance(null).getECL();
            }catch(Exception e){
                vDevice = null;
                publishProgress("No valid device Id!");
                return null;
            }
            if (deviceId == null) {
                vDevice = null;
                publishProgress("No valid device Id!");
                return null;
            }

            try {
                modelUrn = EnterpriseClientUtil.getInstance(null).getSelectedDeviceToMonitor().deviceModel.urn;
                deviceModel = client.getDeviceModel(modelUrn);
                vDevice = client.createVirtualDevice(deviceId, deviceModel);
                publishFinish = false;
                publishProgress("Init " + modelUrn);
                while (publishFinish == false) {
                    Thread.currentThread().sleep(200);
                }
                setupHandlers(vDevice);
            } catch (Exception e) {
                Log.d(ERROR_TAG, e.getMessage());
            }

            return null;
        }

        @Override
            protected void onProgressUpdate(String... s) {
            if(vDevice == null) {
                recordingAlert.setText(s[0]);
                recordingAlert.setVisibility(View.VISIBLE);
                return;
            }
            if(s[0].contains("Init")) {
                String img = getImageURI(vDevice.get(IMAGE_ATTRIBUTE_NAME));
                String imgTime = vDevice.get(IMAGE_TIME_ATTRIBUTE_NAME).toString();
                //String imgTime = vDevice.get(IMAGE_TIME_ATTRIBUTE_NAME);
                currentImage.setText(img.trim());
                currentImageTime.setText(imgTime);
                publishFinish = true;
                return;
            }
            try {
                if(s[0].contains("onChange")) {
                    image = getImageURI(vDevice.get(IMAGE_ATTRIBUTE_NAME));
                    currentImage.setText(image.trim());
                    String imgTime = vDevice.get(IMAGE_TIME_ATTRIBUTE_NAME).toString();
                    imgTime = imgTime.replaceAll("GMT.*","");
                    currentImageTime.setText(imgTime);
                    Log.d(MSG_TAG, "Image -> " + image);
                } else if(s[0].contains("onRecord") && s[0].contains(".mp4")) {
                    String parts[] = s[0].split(":");
                    String name = parts[1];
                    recordingAlert.setText("Recorded Video: " + name);
                    recordingAlert.setVisibility(View.VISIBLE);
                }

            } catch (Exception e) {
                Log.d(ERROR_TAG, e.getMessage());
            }
        }

        private void setupHandlers(VirtualDevice device) {

            setupHandlers(device, false);
        }

        AtomicBoolean breakWaitLoop = new AtomicBoolean(false);

        private void setupHandlers(VirtualDevice device,
                                   final boolean displayOnlyThreshold) {
            device.setOnChange(new VirtualDevice.ChangeCallback<VirtualDevice>() {
                public void onChange(VirtualDevice.ChangeEvent<VirtualDevice> event) {
                    VirtualDevice virtualDevice = event.getVirtualDevice();

                    StringBuilder msg = new StringBuilder(new Date().toString());
                    msg.append(": ");
                    msg.append(virtualDevice.getEndpointId());
                    msg.append(" : onChange : ");
                    boolean first = true;
                    VirtualDevice.NamedValue<?> namedValue = event.getNamedValue();
                    while (namedValue != null) {
                        String attributeName = namedValue.getName();
                        Object attributeValue= namedValue.getValue();
                        if (attributeValue instanceof StorageObject) {
                            // The client library gives control to the application to decide
                            // whether and where to download content from the Storage Cloud Service.
                            final StorageObject storageObject = (StorageObject)attributeValue;
                            try {
                                String path =  MEDIA_DIR + storageObject.getName();
                                storageObject.setOutputPath(path);
                                // sync happens in the background and calls the syncCallback when done
                                storageObject.setOnSync(syncCallback);
                                storageObject.sync();
                                publishProgress("onChange: "+ storageObject.getName());
                            } catch (FileNotFoundException e) {
                                //displayException(e);
                            }
                        }

                        if (!first) {
                            msg.append(',');
                        } else {
                            first = false;
                        }

                        //maxThresholdChanged |= IMAGE_ATTRIBUTE_NAME.equals(attributeName);
                        msg.append('\"');
                        msg.append(attributeName);
                        msg.append("\"=");
                        msg.append(String.valueOf(attributeValue));

                        namedValue = namedValue.next();
                    }

                    if (displayOnlyThreshold) {
                        msg.insert(0, System.getProperty("line.separator"));
                    }

                    Log.d(MSG_TAG, msg.toString());

                    //publishProgress(msg.toString());
                }
            });
            // this callback will  be invoked when data is received for the format
            // urn:com:oracle:iot:device:motion_activated_camera:recording
            device.setOnData(MOTION_ACTIVATED_CAMERA_MODEL_RECORDING_URN,
                    new VirtualDevice.DataCallback() {
                @Override
                public void onData(VirtualDevice.DataEvent event) {

                        VirtualDevice device = event.getVirtualDevice();
                    final StringBuilder msg =
                            new StringBuilder(new Date().toString())
                                    .append(" : ")
                                    .append(device.getEndpointId())
                                    .append(" : onData : ");
                    VirtualDevice.NamedValue namedValue = event.getNamedValue();

                    boolean first = true;
                    while (namedValue != null) {
                        String attributeName = namedValue.getName();
                        Object attributeValue = namedValue.getValue();
                        if (attributeValue instanceof StorageObject) {
                            // The client library gives control to the application to decide
                            // whether and where to download content from the Storage Cloud Service.
                            final StorageObject storageObject = (StorageObject)attributeValue;
                            try {
                                storageObject.setOutputPath(MEDIA_DIR + storageObject.getName());
                                // sync happens in the background and calls the syncCallback when done
                                storageObject.setOnSync(syncCallback);
                                storageObject.sync();
                                publishProgress("onRecord:"+storageObject.getName());
                            } catch (FileNotFoundException e) {
                                //displayException(e);
                            }
                        }

                        if (first) first = false;
                        else msg.append(',');

                        msg.append('\"');
                        msg.append(attributeName);
                        msg.append("\"=");
                        msg.append(String.valueOf(attributeValue));

                        namedValue = namedValue.next();
                    }
                    publishProgress(msg.toString());
                }
            });
            device.setOnError(
                    new VirtualDevice.ErrorCallback<VirtualDevice>() {
                        public void onError(VirtualDevice.ErrorEvent event) {
                            VirtualDevice device = (VirtualDevice)event.getVirtualDevice();
                            StringBuilder msg = new StringBuilder(new Date().toString() + " : onError : " +
                                    device.getEndpointId() + " : \"" + event.getMessage());

                            publishProgress(msg.toString());
                        }
                    });
        }
    }
    private String getImageURI(Object value) {
        ExternalObject externalObject = (ExternalObject) value;
        Uri uri = Uri.parse(externalObject.getURI());
        String imagename = uri.getLastPathSegment();
        //final int mid = imagename.length() / 2; //get the middle of the String
        //String[] parts = {imagename.substring(0, mid),imagename.substring(mid)};
        //imagename = parts[0] + "\n" + parts[1];
        return imagename;
    }


    // This callback will be invoked when the client library receives an alert on an attribute
    // of a device the application is monitoring. The callback simply prints out the
    // attributes and values that have been alerted. The same callback is used for all of the
    // VirtualDevice instances that are being monitored.
    private final static VirtualDevice.ErrorCallback<VirtualDevice> errorCallback=
            new VirtualDevice.ErrorCallback<VirtualDevice>() {

                public void onError(VirtualDevice.ErrorEvent<VirtualDevice> event) {
                    VirtualDevice virtualDevice = event.getVirtualDevice();
                    StringBuilder msg =
                            new StringBuilder(new Date().toString())
                                    .append(" : ")
                                    .append(virtualDevice.getEndpointId())
                                    .append(" : onError : \"")
                                    .append(event.getMessage())
                                    .append("\"");
                /*
                 * Normally some error processing would go here,
                 * but since this a sample, notify the main thread
                 * to end the sample.
                 */
                    //display(msg.toString());
                }
            };
    private static void display(String msg) {
        Log.d(MSG_TAG,msg);
    }

    // This callback will be set on a StorageObject when this application receives an
    // onChange event containing a StorageObject. The callback simply prints out the
    // status of synchronizing the content with the Oracle Storage Cloud Service.
    // The same callback is used for all of the StorageObject instances.
    private static final StorageObject.SyncCallback syncCallback =
            new StorageObject.SyncCallback() {
                @Override
                public void onSync(StorageObject.SyncEvent event) {
                    VirtualDevice virtualDevice = (VirtualDevice)event.getVirtualDevice();
                    StorageObject storageObject = event.getSource();
                    StringBuilder msg =
                            new StringBuilder(new Date().toString())
                                    .append(" : ")
                                    .append(virtualDevice.getEndpointId())
                                    .append(" : onSync : ")
                                    .append(storageObject.getName())
                                    .append("=\"")
                                    .append(storageObject.getSyncStatus())
                                    .append("\"");

                    if (storageObject.getSyncStatus() == StorageObject.SyncStatus.IN_SYNC) {
                        msg.append(" (")
                                .append(storageObject.getLength())
                                .append(" bytes)");
                    }
                    display(msg.toString());
                }
            };

}
