/*
 * 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 oracle.iot.client.StorageObject;
import oracle.iot.client.device.Data;
import oracle.iot.client.device.DirectlyConnectedDevice;
import oracle.iot.client.device.VirtualDevice;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FilenameFilter;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Date;
import java.util.Locale;

/**
 * This is a simulated motion activated camera sensor which uploads images/videos
 * using high level Virtualization API
 */
class MotionActivatedCameraSensor {
    private File[] images;
    private File[] videos;
    private String imageDir;
    private String videoDir;
    private int currentImageIndex = 0;
    private final DirectlyConnectedDevice directlyConnectedDevice;
    private final VirtualDevice virtualMotionActivatedCamera;
    private StorageObject.SyncCallback syncCallback;
    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;

        public MotionActivatedCameraSensor(DirectlyConnectedDevice directlyConnectedDevice,
                                       VirtualDevice virtualMotionActivatedCamera,
                                       MotionActivatedCameraSampleService.DisplayMessageInterface displayMessageInterface,
                                       String mediaDir) {

        this.directlyConnectedDevice = directlyConnectedDevice;
        this.virtualMotionActivatedCamera = virtualMotionActivatedCamera;
        this.displayMessageInterface = displayMessageInterface;
        setupSyncCallbackHandler();
        setupRecordActionHandler();
        setupErrorCallbackHandler();
        populateImagesAndVideos(mediaDir);
    }

    private void populateImagesAndVideos(String mediaDir) {
        imageDir = mediaDir + File.separator + "images";
        videoDir = mediaDir + File.separator + "videos";
        images = getFiles(imageDir, "jpeg");
        videos = getFiles(videoDir, "mp4");
    }

    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() {
        try {
            File image = images[currentImageIndex++ % images.length];
            StringBuilder consoleMessage = new StringBuilder(getSimpleDate())
                    .append(" : ")
                    .append(virtualMotionActivatedCamera.getEndpointId())
                    .append(" : Set : ")
                    .append(IMAGE_ATTRIBUTE)
                    .append("=")
                    .append(image.getName());
            try {

                final StorageObject storageObject =
                        directlyConnectedDevice.createStorageObject(
                            "motion_activated_camera_" +
                                    image.getName(),
                            "image/jpeg");
                storageObject.setOnSync(syncCallback);
                storageObject.setInputPath(image.getPath());
                virtualMotionActivatedCamera.update()
                        .set(IMAGE_ATTRIBUTE, storageObject)
                        .set(IMAGE_TIME_ATTRIBUTE, new Date())
                        .finish();

            } catch (FileNotFoundException fnfe) {
                displayException(fnfe);
            }

            displayMessageInterface.displayMessage(consoleMessage.toString());
            //
        } catch (Throwable e) {
            // catching Throwable, not Exception:
            // could be java.lang.NoClassDefFoundError
            // which is not Exception

            displayException(e);
        }
    }

    /** This callback will be set on a StorageObject when the client library synchronizes 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.
     **/
    void setupSyncCallbackHandler() {
        syncCallback =
                new StorageObject.SyncCallback() {
                    @Override
                    public void onSync(StorageObject.SyncEvent event) {
                        VirtualDevice virtualDevice = (VirtualDevice) event.getVirtualDevice();
                        final StorageObject storageObject = event.getSource();
                        StringBuilder msg =
                                new StringBuilder(getSimpleDate())
                                        .append(" : ")
                                        .append(virtualDevice.getEndpointId())
                                        .append(" : onSync : ")
                                        .append(storageObject.getName())
                                        .append("=\"")
                                        .append(storageObject.getSyncStatus())
                                        .append("\"");
                        displayMessageInterface.displayMessage(msg.toString());
                    }
                };

    }

    /**
     * Get files with given extension from a directory as an array.
     */
    private File[] getFiles(final String dir, final String ext) {

        final File directory = new File(dir);
        final File[] files = directory.isDirectory()
                ? directory.listFiles(new FilenameFilter() {
            @Override
            public boolean accept(File dir, String name) {
                return name.endsWith(ext);
            }
        })
                : null;

        if (files == null || files.length == 0) {
            String message = !directory.isDirectory()
                    ? "Could not find: " + directory.getPath()
                    : "Could not find images in path: " + directory.getPath();
            FileNotFoundException fileNotFoundException = new FileNotFoundException(message);
            displayException(fileNotFoundException);
            throw new RuntimeException(fileNotFoundException);
        }

        // Make sure numbered files are ordered correctly
        Arrays.sort(files, new Comparator<File>() {
            @Override
            public int compare(File o1, File o2) {
                final String name1 = o1.getName();
                final String name2 = o2.getName();
                int c = name1.length() - name2.length();
                if (c == 0) {
                    c = name1.compareTo(name2);
                }
                return c;
            }
        });

        return files;

    }

    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');
    }

    /**
     * Setup the record action handler to to upload recorded video to SCS.
     */
    void setupRecordActionHandler() {
        virtualMotionActivatedCamera.setCallable("record", new VirtualDevice.Callable<Integer>() {
            @Override
            public void call(final VirtualDevice virtualDevice, Integer data) {

                int d = data.intValue();
                if (d <= 0) return;

                // round to nearest increment of 15
                final int duration = (d + 14) / 15 * 15;
                // assumes videos.length > 0 and videos are 15 second increments.
                final File video = videos[Math.min(duration / 15 - 1, videos.length - 1)];

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

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

                        Date startTime = new Date();
                        int recordDuration = duration;

                        // 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 {
                            StorageObject storageObject =
                                    directlyConnectedDevice.createStorageObject(
                                            video.getName(),
                                            "video/mp4");
                            storageObject.setOnSync(syncCallback);
                            storageObject.setInputPath(video.getPath());

                            Data recording = virtualMotionActivatedCamera.createData(
                                    MOTION_ACTIVATED_CAMERA_MODEL_URN + ":recording");
                            recording.set("video", storageObject)
                                    .set("startTime", startTime)
                                    .set("duration", recordDuration)
                                    .submit();

                            StringBuilder consoleMessage = new StringBuilder(getSimpleDate())
                                    .append(" : ")
                                    .append(virtualMotionActivatedCamera.getEndpointId())
                                    .append(" : Set : \"")
                                    .append("recording")
                                    .append("\"=")
                                    .append(storageObject.getURI());


                            displayMessageInterface.displayMessage(consoleMessage.toString());

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

                        }
                    }
                }).start();
            }
        });
    }

    /**
     * Setup the error callback handler to to upload recorded video to SCS.
     */
    void setupErrorCallbackHandler() {
        virtualMotionActivatedCamera.setOnError(
                new VirtualDevice.ErrorCallback<VirtualDevice>() {
                    public void onError(VirtualDevice.ErrorEvent<VirtualDevice
                            > event) {
                        VirtualDevice device = event.getVirtualDevice();
                        displayMessageInterface.displayMessage(new Date().toString() + " : onError : " +
                                device.getEndpointId() +
                                " : \"" + event.getMessage() + "\"");
                    }
                });
    }
}
