/*
 * Copyright (c) 2016 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.os.AsyncTask;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.widget.Button;
import android.widget.CompoundButton;
import android.widget.Switch;
import android.widget.TextView;

import com.oracle.iot.sample.R;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;

import oracle.iot.client.DeviceModel;
import oracle.iot.client.enterprise.Device;
import oracle.iot.client.enterprise.EnterpriseClient;
import oracle.iot.client.enterprise.VirtualDevice;

public class TemperatureSensorActivity extends Activity {
    private static final String ERROR_TAG = "IOT_ERROR";
    private static final String MSG_TAG = "IOT";

    TextView currentTemp;
    TextView minTemp;
    TextView maxTemp;
    TextView currentTempMinThres;
    TextView currentTempMaxThres;
    TextView lastON;
    Switch systemSwitch;
    TextView tempMaxAlert;
    TextView tempMinAlert;
    Activity context;
    TextView manufacturer;
    TextView modelNumber;
    VirtualDevice virtualDevice = null;
    Button setThresholdButton;
    Button resetButton;
    TextView newMinThreshold;
    TextView newMaxThreshold;

    // Map model format URN to its name
    private static final Map<String,String> formatNames = new HashMap<String, String>() {
        {
            put("urn:com:oracle:iot:device:temperature_sensor:too_hot", "tooHotAlert");
            put("urn:com:oracle:iot:device:temperature_sensor:too_cold", "tooColdAlert");
        }
    };
    String deviceId;
    private static final String TEMPERATURE_SENSOR_MODEL_URN =
            "urn:com:oracle:iot:device:temperature_sensor";
    private static final String MAX_THRESHOLD_ATTRIBUTE = "maxThreshold";
    private static final String MIN_THRESHOLD_ATTRIBUTE = "minThreshold";
    private static final String MAX_TEMPERATURE = "maxTemp";
    private static final String MIN_TEMPERATURE = "minTemp";
    private static final String TEMPERATURE_MEASUREMENT_UNIT = "unit";
    private static final String DEVICE_START_TIME = "startTime";
    private static final String TEMPERATURE_ATTRIBUTE_NAME = "temp";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_temperature_sensor);
        EnterpriseClientUtil util = EnterpriseClientUtil.getInstance(this);
        util.setCurrentContext(this);
        deviceId = util.getSelectedDeviceToMonitor().device.getId();
        TextView appNameTV = (TextView)findViewById(R.id.appNameTitle);
        TextView deviceNameTV = (TextView)findViewById(R.id.deviceTitle);
        deviceNameTV.setText(deviceId);
        context=this;
        currentTemp = (TextView) findViewById(R.id.temperatureValue);
        minTemp = (TextView) findViewById(R.id.minTemperatureValue);
        maxTemp = (TextView) findViewById(R.id.maxTemperatureValue);;
        currentTempMinThres = (TextView) findViewById(R.id.minTemperatureThresholdValue);
        currentTempMaxThres = (TextView) findViewById(R.id.maxTemperatureThresholdValue);
        lastON = (TextView) findViewById(R.id.lastOnDate);
        systemSwitch = (Switch) findViewById(R.id.powerSwitch);
        setSystemSwitchHandler();
        manufacturer = (TextView)findViewById(R.id.manufacturer);
        modelNumber= (TextView)findViewById(R.id.modelNumber);
        newMaxThreshold=(TextView)findViewById(R.id.newMaxValue);
        newMinThreshold=(TextView)findViewById(R.id.newMinValue);
        setThresholdButton=(Button)findViewById(R.id.thresholdButton);
        setThresholdButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                try {
                    Log.d(MSG_TAG, "Setting New Max Threshold -> " + newMaxThreshold.getText().toString());
                    Log.d(MSG_TAG, "Setting New Min Threshold -> " + newMinThreshold.getText().toString());
                    new SensorActions("updateMin").execute();
                    new SensorActions("updateMax").execute();
                }catch(Exception e){
                    Log.d(ERROR_TAG, e.getMessage());
                }

            }
        });
        resetButton=(Button)findViewById(R.id.resetButton);
        resetButton.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                try {
                    Log.d(MSG_TAG, "Reset temperature");
                    new SensorActions("reset").execute();
                }catch(Exception e){
                    Log.d(ERROR_TAG, e.getMessage());
                }

            }
        });

        newMaxThreshold.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
                    new SensorActions("updateMax").execute();
                    return true;
                }
                return false;
            }
        });
        newMinThreshold.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
                    new SensorActions("updateMin").execute();
                    return true;
                }
                return false;
            }
        });
        manufacturer.setText("Manufacturer: "+util.getSelectedDeviceToMonitor().device.getValue(Device.Field.MANUFACTURER));
        modelNumber.setText("Model Number: " + util.getSelectedDeviceToMonitor().device.getValue(Device.Field.MODEL_NUMBER));
        tempMaxAlert = (TextView) findViewById(R.id.tempHotAlert);
        tempMaxAlert.setVisibility(View.INVISIBLE);
        tempMinAlert = (TextView) findViewById(R.id.tempColdAlert);
        tempMinAlert.setVisibility(View.INVISIBLE);
        deviceId = util.getSelectedDeviceToMonitor().device.getId();
        final SensorOperation sensorThread = new SensorOperation(getApplicationContext());
        sensorThread.execute(this);
    }


    private void setSystemSwitchHandler(){
        systemSwitch.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                final Context appContext = getApplicationContext();
                try {
                    if (systemSwitch.isChecked()) {
                        Log.d(MSG_TAG, "Power ON!");
                        new SensorActions("on").execute(appContext);
                    } else {
                        Log.d(MSG_TAG, "Power OFF!");
                        new SensorActions("off").execute(appContext);
                    }
                } catch (Exception e) {
                    Log.d(ERROR_TAG, e.getMessage());
                }
            }
        });
    }

    private class SensorOperation extends AsyncTask<Context, String, Void> {
        private Context context;
        EnterpriseClient client;
        String humidity;
        String max;
        String min;
        String tempUnit;
        String minTempValue;
        String maxTempValue;
        String sTime;
        int pickMax;
        int pickMin;
        String temperature;
        DeviceModel deviceModel = null;
        String modelUrn = null;
        private boolean publishFinish;

        public SensorOperation (Context mContext){
            context = mContext;
            humidity = "50";
            temperature = "0";
            max = "100";
            min = "0";
            tempUnit = "C";
            minTempValue = "0";
            maxTempValue = "100";
            pickMax = Integer.parseInt(max);
            pickMin = Integer.parseInt(min);
        }

        @Override
        protected Void doInBackground(Context... params) {
            try{
                client = EnterpriseClientUtil.getInstance(null).getECL();
            }catch (Exception e){
            }

            if (deviceId == null) {
                virtualDevice = null;
                publishProgress("No valid device Id!");
                return null;
            }


            try {
                modelUrn = EnterpriseClientUtil.getInstance(null).getSelectedDeviceToMonitor().deviceModel.urn;

                deviceModel = client.getDeviceModel(modelUrn);
                virtualDevice = client.createVirtualDevice(deviceId, deviceModel);

                if (virtualDevice == null) {
                    publishProgress("No devices!");
                    return null;
                }

                publishFinish = false;

                publishProgress("Init " + modelUrn);
                while (publishFinish == false) {
                    Thread.currentThread().sleep(200);
                }

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

            return null;
        }

        @Override
        protected void onProgressUpdate(String... s) {

            if(s[0].contains("Init")) {
                if (s[0].equals("Init " + TEMPERATURE_SENSOR_MODEL_URN)) {
                    max = virtualDevice.get(MAX_THRESHOLD_ATTRIBUTE).toString();
                    min = virtualDevice.get(MIN_THRESHOLD_ATTRIBUTE).toString();
                    tempUnit = virtualDevice.get(TEMPERATURE_MEASUREMENT_UNIT).toString();
                    currentTempMinThres.setText("Minimum:" + min + " " + tempUnit);
                    currentTempMaxThres.setText("Maximum:" + max + " " + tempUnit);
                    publishFinish = true;

                    return;
                }
            }

            try {
                if(s[0].contains("onChange")) {
                    if (s[0].contains(TEMPERATURE_ATTRIBUTE_NAME)) {
                        temperature = virtualDevice.get(TEMPERATURE_ATTRIBUTE_NAME).toString();
                        max = virtualDevice.get(MAX_THRESHOLD_ATTRIBUTE).toString();
                        min = virtualDevice.get(MIN_THRESHOLD_ATTRIBUTE).toString();
                        tempUnit = virtualDevice.get(TEMPERATURE_MEASUREMENT_UNIT).toString();
                        minTempValue = virtualDevice.get(MIN_TEMPERATURE).toString();
                        maxTempValue = virtualDevice.get(MAX_TEMPERATURE).toString();
                        sTime = virtualDevice.get(DEVICE_START_TIME).toString();

                        tempMaxAlert.setVisibility(View.INVISIBLE);
                        tempMinAlert.setVisibility(View.INVISIBLE);

                        currentTemp.setText(("Temperature: " + temperature + " " + tempUnit));

                        currentTempMinThres.setText("Minimum:" + min + " " + tempUnit);
                        currentTempMaxThres.setText("Maximum:" + max + " " + tempUnit);
                        minTemp.setText("Minimum: " + minTempValue + " " + tempUnit);
                        maxTemp.setText("Maximum: " + maxTempValue + " " + tempUnit);
                        lastON.setText("Last ON: " + sTime);

                        Log.d(MSG_TAG, "Temperature -> " + temperature);
                        Log.d(MSG_TAG, "Maximum threshold -> " + max);
                        Log.d(MSG_TAG, "Minimum threshold -> " + min);
                        Log.d(MSG_TAG, "Maximum temperature -> " + maxTempValue);
                        Log.d(MSG_TAG, "Minimum temperature -> " + minTempValue);
                        Log.d(MSG_TAG, "Temperature measurement unit -> " + tempUnit);
                        Log.d(MSG_TAG, "Last ON -> " + sTime);
                    }
                }

                if(s[0].contains("onAlert")) {
                    int idxStart = s[0].indexOf("(");
                    int idxEnd = s[0].indexOf(")");
                    String alert = s[0].substring(idxStart + 1, idxEnd);

                    switch(alert) {
                        case "tooHotAlert":
                            tempMaxAlert.setVisibility(View.VISIBLE);
                            String alertHotTemp;
                            idxStart = s[0].indexOf("=");
                            idxEnd = s[0].indexOf(",");
                            alertHotTemp = s[0].substring(idxStart + 1, idxEnd);
                            if(Double.parseDouble(temperature) != Double.parseDouble(alertHotTemp))
                                tempMaxAlert.setVisibility(View.INVISIBLE);

                            break;
                        case "tooColdAlert":
                            tempMinAlert.setVisibility(View.VISIBLE);
                            String alertColdTemp;
                            idxStart = s[0].indexOf("=");
                            idxEnd = s[0].indexOf(",");
                            alertColdTemp = s[0].substring(idxStart + 1, idxEnd);
                            if(Double.parseDouble(temperature) != Double.parseDouble(alertColdTemp))
                                tempMinAlert.setVisibility(View.INVISIBLE);

                            break;
                    }

                    Log.d(MSG_TAG, "Alert -> " + alert);

                }

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

                    boolean maxThresholdChanged = false;
                    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 (!first) {
                            msg.append(',');
                        } else {
                            first = false;
                        }

                        maxThresholdChanged |= MAX_THRESHOLD_ATTRIBUTE.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());
                    breakWaitLoop.set(displayOnlyThreshold && maxThresholdChanged);

                    publishProgress(msg.toString());
                }
            });

            // set a callback for any alert that may be raised for this virtual device
            device.setOnAlert(
                    new VirtualDevice.AlertCallback() {
                        @Override
                        public void onAlert(VirtualDevice.AlertEvent event) {
                            VirtualDevice virtualDevice = event.getVirtualDevice();

                            StringBuilder msg = new StringBuilder(new Date().toString());
                            msg.append(" : ");
                            msg.append(virtualDevice.getEndpointId());
                            msg.append(" : onAlert : ");

                            boolean first = true;
                            VirtualDevice.NamedValue<?> namedValue = event.getNamedValue();
                            while (namedValue != null) {
                                String name = namedValue.getName();
                                Object value = namedValue.getValue();

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

                                msg.append('\"');
                                msg.append(name);
                                msg.append("\"=");
                                msg.append(String.valueOf(value));

                                namedValue = namedValue.next();
                            }

                            final String alertName = formatNames.get(event.getURN());
                            if (alertName != null) {
                                msg.append(" (")
                                        .append(alertName)
                                        .append(")");

                            }

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

                            Log.d(MSG_TAG, msg.toString());
                            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());

                            // Normally some error processing would go here,
                            // but since this a sample, notify the main thread
                            // to end the sample.
                            breakWaitLoop.set(true);

                            publishProgress(msg.toString());
                        }
                    });
        }
    }

    private class SensorActions extends AsyncTask<Context, Void, Void> {
        String action;

        public SensorActions (String mAction) {
            action = mAction;
        }

        @Override
        protected Void doInBackground(Context... params) {

            try {
                switch(action) {
                    case "reset":
                        reset();
                        break;
                    case "on":
                        powerOn();
                        break;
                    case "off":
                        powerOff();
                        break;
                    case "updateMin":
                        updateThreshold(true);
                        break;
                    case "updateMax":
                        updateThreshold(false);
                        break;
                }
            } catch (Exception e) {
                Log.d(ERROR_TAG, e.getMessage());
            }

            return null;
        }

        private void updateThreshold(boolean isMin){
            if (isMin){
                virtualDevice.set(MIN_THRESHOLD_ATTRIBUTE,Integer.parseInt( newMinThreshold.getText().toString()));
            }else{
                virtualDevice.set(MAX_THRESHOLD_ATTRIBUTE,Integer.parseInt( newMaxThreshold.getText().toString()));
            }
        }

        private void reset() throws Exception {
            virtualDevice.call("reset");
        }

        private void powerOn() throws Exception {
            virtualDevice.call("power", true);
        }

        private void powerOff() throws Exception {
            virtualDevice.call("power", false);
        }
    }
}
