/*
 * 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.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.AssetManager;
import android.os.*;
import android.util.Log;
import android.widget.Toast;

import java.io.*;
import java.security.GeneralSecurityException;

import com.oracle.iot.client.message.RequestMessage;
import com.oracle.iot.client.message.ResponseMessage;
import oracle.iot.client.DeviceModel;
import com.oracle.iot.client.device.DirectlyConnectedDevice;


/**
 * This class implements a service to upload camera images and record video
 * using motion activated camera sensor. Because this service in a single thread, the sample explicitly disables
 * long polling.
 **/
public class MotionActivatedCameraSampleService extends Service {
    private static final String ERROR_TAG = "IOT_ERROR";
    private static final String MSG_TAG = "IOT";

    // This service runs in a single thread, and long polling should be disabled.
    static {
        System.setProperty("com.oracle.iot.client.disable_long_polling", "true");
    }

    private static final String MOTION_ACTIVATED_CAMERA_MODEL_URN =
            "urn:com:oracle:iot:device:motion_activated_camera";

    interface DisplayMessageInterface {
        void displayMessage(String message);
        void displayErrorMessage(String message);
    }

    /**
     * This interface is used by sensor to display progress and error messages in main activity.
     */
    DisplayMessageInterface displayMessageInterface = new DisplayMessageInterface() {
        @Override
        public void displayMessage(String message) {
            display(message);
        }

        @Override
        public void displayErrorMessage(String message) {
            displayError(message);
        }
    };

    /**
     * sensor polling interval before sending next readings.
     * Could be configured using {@code com.oracle.iot.sample.sensor_polling_interval} property
     */
    private static final long SENSOR_POLLING_INTERVAL =
            Long.getLong("com.oracle.iot.sample.sensor_polling_interval", 5000);

    private Context context;
    private String messageToBeSent = "";
    PowerManager.WakeLock wakeLock;
    private DirectlyConnectedDevice directlyConnectedDevice;
    private MotionActivatedCameraSensor sensor;
    private String[] videos;
    private String[] images;

    /**
     * This is a list of callbacks that have been registered with the
     * service.  Note that this is package scoped (instead of private) so
     * that it can be accessed more efficiently from inner classes.
     */
    final RemoteCallbackList<IDirectlyConnectedDeviceSampleServiceCallback> mCallbacks
            = new RemoteCallbackList<IDirectlyConnectedDeviceSampleServiceCallback>();

    NotificationManager mNM;

    @Override
    public void onCreate() {
        mNM = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        // Display a notification about us starting.
        showNotification(true);
    }

    @Override
    public IBinder onBind(Intent intent) {
        // Select the interface to return.
        if (IDirectlyConnectedDeviceSampleService.class.getName().equals(intent.getAction())) {
            return mBinder;
        }
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        PowerManager powerManager = (PowerManager) getSystemService(POWER_SERVICE);
        wakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
                "IoTWakeLock");
        // power management: Keep the CPU on so we can continue to send messages back to the server
        wakeLock.acquire();
        context = this;
        Toast.makeText(this, "Service started.", Toast.LENGTH_LONG).show();
        new Thread() {
            @Override
            public void run() {
                runSample();
            }
        }.start();

        return START_NOT_STICKY;
    }

    /**
     * The IDirectlyConnectedDeviceSampleService Interface is defined through IDL
     */
    private final IDirectlyConnectedDeviceSampleService.Stub mBinder =
            new IDirectlyConnectedDeviceSampleService.Stub() {
        public void registerCallback(IDirectlyConnectedDeviceSampleServiceCallback cb) {
            if (cb != null) mCallbacks.register(cb);
        }
        public void unregisterCallback(IDirectlyConnectedDeviceSampleServiceCallback cb) {
            if (cb != null) mCallbacks.unregister(cb);
        }
    };

    @Override
    public void onTaskRemoved(Intent rootIntent) {
        Toast.makeText(this, "Task removed: " + rootIntent, Toast.LENGTH_LONG).show();
    }

    private static final int REPORT_MSG = 1;
    private static final int ERROR_MSG = 4;

    /**
     * Message handler used to execute operations on the main thread.  This is used
     * to display progress and error messages in the main activity.
     */
    private final Handler mHandler = new Handler() {
        @Override public void handleMessage(Message msg) {
            switch (msg.what) {

                case REPORT_MSG: {
                    // Broadcast to all clients the new value.
                    final int N = mCallbacks.beginBroadcast();
                    for (int i=0; i<N; i++) {
                        try {
                            mCallbacks.getBroadcastItem(i).valueChanged(messageToBeSent);
                        } catch (RemoteException e) {
                            // The RemoteCallbackList will take care of removing
                            // the dead object for us.
                        }
                    }
                    mCallbacks.finishBroadcast();

                } break;
                case ERROR_MSG: {
                    // Broadcast to all clients the new value.
                    final int N = mCallbacks.beginBroadcast();
                    for (int i = 0; i < N; i++) {
                        try {
                            mCallbacks.getBroadcastItem(i).errorMessage(messageToBeSent);
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                    mCallbacks.finishBroadcast();
                } break;
                default:
                    super.handleMessage(msg);
            }
        }
    };

    /**
     * This is the main function that activates the IoT device and starts the sensor thread
     */
    public void runSample() {

        try {
            SharedPreferences mSharedPref = getApplicationContext().getSharedPreferences(
                    getString(R.string.preference_file_key), Context.MODE_PRIVATE);

            // Create the directly-connected device instance
            if (mSharedPref.getBoolean(getString(R.string.use_provided_bks), false)) {
                directlyConnectedDevice = new DirectlyConnectedDevice(context);
            } else {
                SharedPreferences pref = getApplicationContext().getSharedPreferences(
                    getString(R.string.preference_file_key), Context.MODE_PRIVATE);
                try (FileOutputStream fos = openFileOutput(getString(R.string.last_known), Context.MODE_PRIVATE)) {
                    fos.write(pref.getString(getString(R.string.ta_file_path), "").getBytes());
                    Log.d(ERROR_TAG, "Write file " + pref.getString(getString(R.string.ta_file_path), ""));
                } catch (IOException ex) {
                    Log.d(ERROR_TAG, "Can not safe to internal storage");
                }
                directlyConnectedDevice = new DirectlyConnectedDevice(mSharedPref.getString(getString(R.string.ta_file_path),""), mSharedPref.getString(getString(R.string.ta_password),""));
            }


            if (!directlyConnectedDevice.isActivated()) {
                directlyConnectedDevice.activate(MOTION_ACTIVATED_CAMERA_MODEL_URN);
            }

            // Get the device model instance.
            DeviceModel dcdModel =
                    directlyConnectedDevice.getDeviceModel(MOTION_ACTIVATED_CAMERA_MODEL_URN);

            // Get the images/videos in the assets bundle.
            getImageVideoNames();

            // create a new MotionActivatedCameraSensor sensor object.
            sensor = new MotionActivatedCameraSensor (directlyConnectedDevice,
                                                     displayMessageInterface,
                                                     context.getAssets(),
                                                     images,videos);

            Thread sensorThread = new Thread(
                    new Runnable () {
                        public void run() {
                            boolean bExiting = false;
                            // Loop all the available asset images one by one.
                            while (!bExiting) {
                                // upload the next image into the SCS.
                                try {
                                    sensor.uploadNextImage();
                                    sensor.handleRequestMessages();
                                    Thread.sleep(SENSOR_POLLING_INTERVAL);
                                } catch (InterruptedException e) {
                                    bExiting = true;
                                } catch (IOException e) {
                                    displayError(e.getMessage());
                                } catch (GeneralSecurityException e) {
                                    e.printStackTrace();
                                }
                            }
                        }
                    });

            sensorThread.setDaemon(true);
            sensorThread.start();
        } catch (Exception e) {
            e.printStackTrace();
            if(e.getMessage() != null) {
                // Tell the user
                displayError(e.getMessage());
            } else {
                // Tell the user
                displayError("Error. No message.");
            }
        }
    }

    private void getImageVideoNames() throws IOException {
        AssetManager assets = context.getAssets();
        images = assets.list("images");
        videos = assets.list("videos");
    }

    private void display(String string) {
        Log.d(MSG_TAG, string);
        messageToBeSent = string;
        mHandler.sendEmptyMessage(REPORT_MSG);
        try {
            Thread.sleep(1000);
        } catch(InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    private void displayError(String string) {
        Log.d(ERROR_TAG, "Error:" + string);
        messageToBeSent = string;
        mHandler.sendEmptyMessage(ERROR_MSG);
        try {
            Thread.sleep(8000);
        } catch(InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }

    @Override
    public void onDestroy() {
        // Cancel the persistent notification.
        mNM.cancelAll();
        showNotification(false);
        // power management: let the CPU go to sleep
        if (wakeLock != null) wakeLock.release();
        // Tell the user we stopped.
        Toast.makeText(this, R.string.dcd_service_stopped, Toast.LENGTH_SHORT).show();

        // Unregister all callbacks.
        mCallbacks.kill();

        mHandler.removeMessages(REPORT_MSG);
    }
    /**
     * Show a notification while this service is running.
     */

    private void showNotification(boolean serviceStarted) {
        // In this sample, we'll use the same text for the ticker and the expanded notification
        CharSequence text = serviceStarted ? getText(R.string.dcd_service_started) :
                getText(R.string.dcd_service_stopped);
        // The PendingIntent to launch our activity if the user selects this notification
        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
                new Intent(this, MainActivity.class), 0);
        // Set the info for the views that show in the notification panel.
        Notification notification = new Notification.Builder(this)
                .setSmallIcon(R.drawable.mini)  // the status icon
                .setTicker(text)  // the status text
                .setWhen(System.currentTimeMillis())  // the time stamp
                .setContentTitle(getText(R.string.dcd_service_label))  // the label of the entry
                .setContentText(text)  // the contents of the entry
                .setContentIntent(contentIntent)  // The intent to send when the entry is clicked
                .build();
        // Send the notification.
        // We use a string id because it is a unique number.  We use it later to cancel.
        mNM.notify(serviceStarted ? R.string.dcd_service_started :
                R.string.dcd_service_stopped, notification);
    }

}
