/*
 * 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;

import android.content.res.AssetManager;
import com.oracle.iot.client.StorageObject;
import com.oracle.iot.client.device.DirectlyConnectedDevice;
import com.oracle.iot.client.device.util.RequestDispatcher;
import com.oracle.iot.client.device.util.RequestHandler;
import com.oracle.iot.client.message.DataMessage;
import com.oracle.iot.client.message.RequestMessage;
import com.oracle.iot.client.message.ResponseMessage;
import com.oracle.iot.client.message.StatusCode;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.*;
import java.net.URI;
import java.security.GeneralSecurityException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * This is a simulated motion activated camera sensor which uploads images/videos
 * using messaging Media API.
 */
class MotionActivatedCameraSensor {
    private final DirectlyConnectedDevice directlyConnectedDevice;
    private final RequestDispatcher requestDispatcher;
    private static final String IMAGE_ATTRIBUTE = "image";
    private static final String IMAGE_TIME_ATTRIBUTE = "imageTime";
    private static final String MOTION_ACTIVATED_CAMERA_MODEL_URN =
            "urn:com:oracle:iot:device:motion_activated_camera";
    MotionActivatedCameraSampleService.DisplayMessageInterface displayMessageInterface;
    String images[];
    String videos[];
    int currentImageIndex = 0;
    AssetManager assetManager;
    private static final long SENSOR_POLLING_INTERVAL =
            Long.getLong("com.oracle.iot.sample.sensor_polling_interval", 5000);

    public MotionActivatedCameraSensor(DirectlyConnectedDevice directlyConnectedDevice,
                                       MotionActivatedCameraSampleService.DisplayMessageInterface displayMessageInterface,
                                       AssetManager assetManager,
                                       String images[],
                                       String videos[]) {

        this.directlyConnectedDevice = directlyConnectedDevice;
        this.displayMessageInterface = displayMessageInterface;
        this.assetManager = assetManager;
        this.images = images;
        this.videos = videos;
        requestDispatcher = RequestDispatcher.getInstance();
        setupRecordActionHandler();
    }

    /**
     * Setup the record action handler to to upload recorded video to SCS.
     */
    private void setupRecordActionHandler() {
        // Register handler for the "record" action of the motion_activated_camera device model.
        requestDispatcher.registerRequestHandler(directlyConnectedDevice.getEndpointId(),
                "deviceModels/" + MOTION_ACTIVATED_CAMERA_MODEL_URN + "/actions",
                new RequestHandler() {
                    public ResponseMessage handleRequest(RequestMessage requestMessage) throws IOException, JSONException {
                        return handleRecordAction(requestMessage, directlyConnectedDevice, videos);
                    }
                }
        );
    }

    /**
     * Helper function to return date/time in simple format.
     */
    private String getSimpleDate() {
        return new SimpleDateFormat("HH:mm:ss", Locale.ROOT).format(new Date()).toString();
    }

    /**
     * Upload the next image file from the images array
     * and send a data message with the URL.
     */
    public void uploadNextImage() throws IOException {
        String currentImage = images[currentImageIndex++ % images.length];
        String imagePath = "images/" + currentImage;
        // Create an InputStream for the image file.
        InputStream stream = assetManager.open(imagePath);
        try {

            // Create storage object.
            final StorageObject storageObject =
                    directlyConnectedDevice.createStorageObject(
                            currentImage,
                            "image/jpeg");

            // set the input stream for image.
            storageObject.setInputStream(stream);
            // Blocking call. wait till the image is uploaded.
            storageObject.sync();

            // Create a data message rom the URI of the image uploaded.
            final DataMessage dataMessage = new DataMessage.Builder()
                    .source(directlyConnectedDevice.getEndpointId())
                    .format(MOTION_ACTIVATED_CAMERA_MODEL_URN + ":attributes")
                    .dataItem(IMAGE_ATTRIBUTE, storageObject.getURI())
                    .dataItem(IMAGE_TIME_ATTRIBUTE, new Date().getTime())
                    .build();

            // send the message.
            directlyConnectedDevice.send(dataMessage);

            // Create the console message to be displayed.
            StringBuilder consoleMessage = new StringBuilder(getSimpleDate())
                    .append(" : ")
                    .append(directlyConnectedDevice.getEndpointId())
                    .append(" : Data : \"")
                    .append(IMAGE_ATTRIBUTE)
                    .append("\"=")
                    .append(currentImage);

            // display the console message.
            displayMessageInterface.displayMessage(consoleMessage.toString());

        } catch (FileNotFoundException fnfe) {
            displayException(fnfe);
        } catch (Throwable e) {
            // catching Throwable, not Exception:
            // could be java.lang.NoClassDefFoundError
            // which is not Exception

            displayException(e);
        }
    }

    public void handleRequestMessages() throws IOException, GeneralSecurityException {
        long receiveTimeout = SENSOR_POLLING_INTERVAL;
        RequestMessage requestMessage = directlyConnectedDevice.receive(receiveTimeout);

        if (requestMessage != null) {
            ResponseMessage responseMessage =
                    requestDispatcher.dispatch(requestMessage);
            directlyConnectedDevice.send(responseMessage);
        }
    }


    private void displayException(Throwable e) {
        StringBuilder sb = new StringBuilder(e.getMessage() == null ?
                e.getClass().getName() : e.getMessage());
        if (e.getCause() != null) {
            sb.append(".\n\tCaused by: ");
            sb.append(e.getCause());
        }
        displayMessageInterface.displayErrorMessage('\n' + sb.toString() + '\n');
    }

    /**
     * Action Handler for record action to start recording.
     */
    private ResponseMessage handleRecordAction(RequestMessage requestMessage,
                                                      final DirectlyConnectedDevice directlyConnectedDevice,
                                                      String[] videos) throws JSONException, IOException {

        JSONObject jsonObject = new JSONObject(requestMessage.getBodyString());
        int d = jsonObject.getInt("value");
        if (d <= 0) {
            return new ResponseMessage.Builder(requestMessage)
                    .statusCode(StatusCode.BAD_REQUEST)
                    .build();
        }

        // round to nearest increment of 15
        final int duration = (d + 14) / 15 * 15;
        // assumes videos.length > 0 and videos are 15 second increments.
        final String videoName = "videos/" + videos[Math.min(duration/15-1,videos.length-1)];
        final InputStream videoStream = assetManager.open(videoName);

        final StringBuilder msg =
                new StringBuilder(new Date().toString())
                        .append(" : ")
                        .append(directlyConnectedDevice.getEndpointId())
                        .append(" : Call : record ")
                        .append(d);
        displayMessageInterface.displayMessage(msg.toString());

        new Thread(new Runnable() {
            @Override
            public void run() {

                final Date startTime = new Date();

                // Simulate the time it takes to record the video by sleeping
                try {
                    Thread.sleep(duration);
                } catch (InterruptedException e) {
                    // Restore the interrupted status
                    Thread.currentThread().interrupt();
                }

                try {

                    final StorageObject storageObject =
                            directlyConnectedDevice.createStorageObject(
                                            videoName,
                                    "video/mp4");

                    storageObject.setInputStream(videoStream);

                    // Blocking!
                    storageObject.sync();

                    final StringBuilder consoleMessage = new StringBuilder(new Date().toString())
                            .append(" : ")
                            .append(directlyConnectedDevice.getEndpointId())
                            .append(" : Data : \"")
                            .append("recording")
                            .append("\"=")
                            .append(storageObject.getURI());

                    final DataMessage dataMessage = new DataMessage.Builder()
                            .source(directlyConnectedDevice.getEndpointId())
                            .format(MOTION_ACTIVATED_CAMERA_MODEL_URN+":recording")
                            .dataItem("video", storageObject.getURI())
                            .dataItem("startTime", startTime.getTime())
                            .dataItem("duration", duration)
                            .build();

                    displayMessageInterface.displayMessage(consoleMessage.toString());

                    // Blocking!
                    directlyConnectedDevice.send(dataMessage);

                } catch(IOException ioe) {
                    displayException(ioe);
                } catch (GeneralSecurityException gse) {
                    displayException(gse);
                }

            }
        }).start();

        return new ResponseMessage.Builder(requestMessage)
                .statusCode(StatusCode.OK)
                .build();
    }

}
