package com.eveningoutpost.dexdrip.wearintegration;

import android.app.Service;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothManager;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.graphics.Bitmap;
import android.os.Build;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.util.Base64;
import android.util.Log;

import com.eveningoutpost.dexdrip.BestGlucose;
import com.eveningoutpost.dexdrip.G5Model.Extensions;
import com.eveningoutpost.dexdrip.G5Model.Transmitter;
import com.eveningoutpost.dexdrip.ImportedLibraries.dexcom.Dex_Constants;
import com.eveningoutpost.dexdrip.Models.ActiveBgAlert;
import com.eveningoutpost.dexdrip.Models.ActiveBluetoothDevice;
import com.eveningoutpost.dexdrip.Models.HeartRate;
import com.eveningoutpost.dexdrip.Models.JoH;
import com.eveningoutpost.dexdrip.Models.StepCounter;
import com.eveningoutpost.dexdrip.Models.TransmitterData;
import com.eveningoutpost.dexdrip.Models.Treatments;
import com.eveningoutpost.dexdrip.Models.UserError;
import com.eveningoutpost.dexdrip.UtilityModels.AlertPlayer;
import com.eveningoutpost.dexdrip.UtilityModels.BgGraphBuilder;
import com.eveningoutpost.dexdrip.UtilityModels.BgSparklineBuilder;
import com.eveningoutpost.dexdrip.UtilityModels.Pref;
import com.eveningoutpost.dexdrip.utils.PowerStateReceiver;
import com.eveningoutpost.dexdrip.xdrip;
import com.huami.watch.transport.DataBundle;
import com.huami.watch.transport.DataTransportResult;
import com.huami.watch.transport.TransportDataItem;
import com.kieronquinn.library.amazfitcommunication.Transporter;
import com.kieronquinn.library.amazfitcommunication.TransporterClassic;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.ByteArrayOutputStream;
import java.util.Set;


/**
 * Created by klaus3d3.
 */

// TODO Use Wakelocking
// TODO Use Constants for time MS
// TODO Use tsl()
// TODO Use TAG for logging
// TODO Use DexCollectionType
// TODO Use Lightweight entry type class for remote calls and prefs logic


public class Amazfitservice extends Service {

    BestGlucose.DisplayGlucose dg;
    private static String action;
    private static String alert_to_send;
    private static int default_snooze;
    private ActiveBluetoothDevice activeBluetoothDevice;
    private BluetoothManager mBluetoothManager;
    private BluetoothAdapter mBluetoothAdapter;

    private String space_mins;
    private double low_occurs_at;
    private Transporter transporter;
    //private Context context;
    DataBundle dataBundle = new DataBundle();
    private HeartRate heartrate;
    private StepCounter stepcounter;
    private SharedPreferences prefs;


    @Override
    public void onCreate() {
        super.onCreate();
        prefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
        transporter = (TransporterClassic) Transporter.get(getApplicationContext(), "com.eveningoutpost.dexdrip.wearintegration");
        transporter.connectTransportService();
        transporter.addChannelListener(new Transporter.ChannelListener() {
            @Override
            public void onChannelChanged(boolean ready) {
                //Transporter is ready if ready is true, send an action now. This will **NOT** work before the transporter is ready!
                //You can change the action to whatever you want, there's also an option for a data bundle to be added (see below)
                if (ready)
                    UserError.Log.e("Amazfitservice", "channel changed - trying automatic resend ");
                Amazfitservice.start("xDrip_synced_SGV_data");
            }

        });

        transporter.addDataListener(new Transporter.DataListener() {
            @Override
            public void onDataReceived(TransportDataItem item) {


                //Confirmation that watch received SGV Data
                if (item.getAction().equals("SGVDataConfirmation")) {
                    DataBundle db = item.getData();
                    //UserError.Log.e("Amazfitservice", db.getString("reply_message"));
                }
                if (item.getAction().equals("CancelConfirmation")) {
                    DataBundle db = item.getData();
                    //UserError.Log.e("Amazfitservice", db.getString("reply_message"));
                }

                // In case of getting a remote Snooze from watch check for an active alert and confirm snooze in case of
                if (item.getAction().equals("Amazfit_Remote_Snooze")) {
                    DataBundle db = item.getData();


                    UserError.Log.e("Amazfitservice", "Remote SNOOZE recieved for " + db.getInt("snoozetime") + " mins");

                    if (ActiveBgAlert.currentlyAlerting() && db.getInt("snoozetime") > 0) {
                        UserError.Log.e("Amazfitservice", "snoozing all alarms");
                        AlertPlayer.getPlayer().Snooze(xdrip.getAppContext(), db.getInt("snoozetime"), true);
                        db.putString("reply_message", "Snooze accepted by Phone");
                    } else if (ActiveBgAlert.currentlyAlerting()) {
                        AlertPlayer.defaultSnooze();
                        db.putString("reply_message", "Snooze accepted by Phone");
                    } else {
                        UserError.Log.e("Amazfitservice", "No Alarms found to snooze");
                        db.putString("reply_message", "No alert found");
                    }

                    //transporter.send("SnoozeRemoteConfirmation", db);
                }


                if (item.getAction().equals("Amazfit_Healthdata")) {
                    DataBundle databundle = item.getData();
                    final StepCounter pm = StepCounter.createEfficientRecord(JoH.tsl(), databundle.getInt("steps"));
                    HeartRate.create(JoH.tsl(), databundle.getInt("heart_rate"), databundle.getInt("heart_acuracy"));

                }

                if (item.getAction().equals("Amazfit_Treatmentsdata")) {
                    DataBundle databundle = item.getData();
                    Treatments.create(databundle.getDouble("carbs"), databundle.getDouble("insulin"), databundle.getLong("timestamp"));

                }
            }

        });
    }


    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public void onDestroy() {

        if (transporter != null) {
            transporter.disconnectTransportService();
        }
        UserError.Log.e("Amazfitservice", "killing service ");

        super.onDestroy();
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        Transporter.DataSendResultCallback test = new Transporter.DataSendResultCallback() {
            @Override
            public void onResultBack(DataTransportResult dataTransportResult) {

                UserError.Log.e("Amazfitservice", dataTransportResult.toString());
            }
        };
        if (!transporter.isTransportServiceConnected()) {
            UserError.Log.e("Amazfitservice", "Service not connected - trying to reconnect ");
            transporter.connectTransportService();

        }

        if (!transporter.isTransportServiceConnected()) {
            UserError.Log.e("Amazfitservice", "Service is not connectable ");

        } else {
            DataBundle db = new DataBundle();
            db.putString("Data", getDatatosend());
            transporter.send(getAction(), db, test);
            //UserError.Log.e("Amazfitservice", "trying to send Data to watch " + action);
        }

        return START_STICKY;
    }


    public static String getAction() {
        return action;
    }

    public static void setAction(String actionremote) {
        action = actionremote;
    }


    // TODO use switch
    public String getDatatosend() {
        String datatosend = new String();
        if (action.equals("xDrip_synced_SGV_data")) datatosend = getSGVJSON();
        if (action.equals("xDrip_Alarm")) {
            datatosend = getAlarmJSON();
        }
        if (action.equals("xDrip_Otheralert")) {
            datatosend = getOtheralertJSON();
        }
        if (action.equals("xDrip_AlarmCancel")) {
            datatosend = getAlarmCancelJSON();
        }
        return datatosend;
    }


    private String gettransmitterbattery() {
        TransmitterData td = TransmitterData.last();
        String returntext;
        if (td == null || td.sensor_battery_level == 0) {
            returntext = "not available";

        } else if ((System.currentTimeMillis() - td.timestamp) > 1000 * 60 * 60 * 24) {
            returntext = "no data in 24 hours";

        } else {
            returntext = "" + td.sensor_battery_level;

            if (td.sensor_battery_level <= Dex_Constants.TRANSMITTER_BATTERY_EMPTY) {
                returntext = returntext + " - very low";
            } else if (td.sensor_battery_level <= Dex_Constants.TRANSMITTER_BATTERY_LOW) {
                returntext = returntext + " - low";
                returntext = returntext + "\n(experimental interpretation)";
            } else {
                returntext = returntext + " - ok";
            }
        }
        return returntext;
    }

    // TODO what does this do? Use DexCollectionType and make as unified as possible
    public String getCurrentDevice() {
        activeBluetoothDevice = ActiveBluetoothDevice.first();
        mBluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE);
        String currentdevice;
        if (activeBluetoothDevice != null) {
            currentdevice = activeBluetoothDevice.name;
        } else {
            currentdevice = "None Set";
        }

        String collection_method = prefs.getString("dex_collection_method", "BluetoothWixel");
        if (collection_method.compareTo("DexcomG5") == 0) {
            Transmitter defaultTransmitter = new Transmitter(prefs.getString("dex_txid", "ABCDEF"));
            if (Build.VERSION.SDK_INT >= 18) {
                mBluetoothAdapter = mBluetoothManager.getAdapter();
            }
            if (mBluetoothAdapter != null) {
                Set<BluetoothDevice> pairedDevices = mBluetoothAdapter.getBondedDevices();
                if ((pairedDevices != null) && (pairedDevices.size() > 0)) {
                    for (BluetoothDevice device : pairedDevices) {
                        if (device.getName() != null) {

                            String transmitterIdLastTwo = Extensions.lastTwoCharactersOfString(defaultTransmitter.transmitterId);
                            String deviceNameLastTwo = Extensions.lastTwoCharactersOfString(device.getName());

                            if (transmitterIdLastTwo.equals(deviceNameLastTwo)) {
                                currentdevice = defaultTransmitter.transmitterId;
                            }

                        }
                    }
                }
            } else {
                currentdevice = "No Bluetooth";
            }
        }
        return currentdevice;
    }

    // TODO use getBestCollectorHardwareName instead ?
    private String getCollectionMethod() {
        return prefs.getString("dex_collection_method", "BluetoothWixel").replace("Dexbridge", "xBridge");
    }

    public String getSGVJSON() {
        final int sensor_age = Pref.getInt("nfc_sensor_age", 0);
        final String age_problem = (Pref.getBooleanDefaultFalse("nfc_age_problem") ? " \u26A0\u26A0\u26A0" : "");
        final double expires = JoH.tolerantParseDouble(prefs.getString("nfc_expiry_days", "14.5")) - ((double) sensor_age) / 1440;
        BestGlucose.DisplayGlucose dg = BestGlucose.getDisplayGlucose();
        try {
            // Extract data from JSON

            JSONObject json_data = new JSONObject();
            json_data.put("Collection_info", getCollectionMethod());
            json_data.put("hardware_source_info", getCurrentDevice());
            json_data.put("sensor.latest_battery_level", gettransmitterbattery());
            json_data.put("sensor_expires", ((expires >= 0) ? (JoH.qs(expires, 1) + "d") : "EXPIRED! ") + age_problem);

            json_data.put("date", dg.timestamp);
            json_data.put("sgv", String.valueOf(dg.unitized) + String.valueOf(dg.delta_arrow));
            json_data.put("delta", String.valueOf(dg.spannableString(dg.unitized_delta)));
            json_data.put("ishigh", dg.isHigh());
            json_data.put("islow", dg.isLow());
            json_data.put("isstale", dg.isStale());
            json_data.put("plugin_name", dg.plugin_name);
            json_data.put("phone_battery", String.valueOf(PowerStateReceiver.getBatteryLevel(getApplicationContext())));

            if (Pref.getBoolean("pref_amazfit_widget_graph", false))
                json_data.put("SGVGraph", BitmaptoString(createWearBitmap(Pref.getStringToInt("amazfit_widget_graph_hours", 4))));
            else
                json_data.put("SGVGraph", "false");

            if (Pref.getBoolean("pref_amazfit_watchface_graph", false))
                json_data.put("WFGraph", BitmaptoString(createWFBitmap(Pref.getStringToInt("amazfit_watchface_graph_hours", 4))));
            else
                json_data.put("WFGraph", "false");


            return json_data.toString();
        } catch (JSONException e) {
            Log.w("AmazfitService", e.toString());
        }
        return "";

    }


    public String getAlarmJSON() {

        BestGlucose.DisplayGlucose dg = BestGlucose.getDisplayGlucose();
        try {
            // Extract data from JSON

            JSONObject json_data = new JSONObject();
            json_data.put("alarmtext", alert_to_send);
            json_data.put("date", System.currentTimeMillis());
            json_data.put("sgv", String.valueOf(dg.unitized) + String.valueOf(dg.delta_arrow));
            json_data.put("default_snooze", default_snooze);
            return json_data.toString();
        } catch (JSONException e) {
            Log.w("AmazfitService", e.toString());
        }
        return "";

    }

    public String getOtheralertJSON() {
        BestGlucose.DisplayGlucose dg = BestGlucose.getDisplayGlucose();
        try {
            // Extract data from JSON

            JSONObject json_data = new JSONObject();

            json_data.put("alarmtext", alert_to_send);
            json_data.put("date", System.currentTimeMillis());
            json_data.put("sgv", String.valueOf(dg.unitized) + String.valueOf(dg.delta_arrow));
            return json_data.toString();
        } catch (JSONException e) {
            Log.w("AmazfitService", e.toString());
        }
        return "";
    }

    public String getAlarmCancelJSON() {
        try {
            // Extract data from JSON

            JSONObject json_data = new JSONObject();
            json_data.put("reply_message", "Watch acknowledged CANCEL");
            json_data.put("date", System.currentTimeMillis());
            return json_data.toString();
        } catch (JSONException e) {
            Log.w("AmazfitService", e.toString());
        }
        return "";
    }

    /*
    public static void start(String action_text, BgReading bg) {
        action = action_text;

        //JoH.startService(Amazfitservice.class);

    }
    */

    public static void start(String action_text, String alert_name, int snooze_time) {
        action = action_text;
        alert_to_send = alert_name;
        default_snooze = snooze_time;
        JoH.startService(Amazfitservice.class);
    }

    public static void start(String action_text) {
        action = action_text;
        JoH.startService(Amazfitservice.class);

    }

    private Bitmap createWearBitmapTinyDots(long start, long end) {
        return new BgSparklineBuilder(xdrip.getAppContext())
                .setBgGraphBuilder(new BgGraphBuilder(xdrip.getAppContext()))
                .setStart(start)
                .setEnd(end)
                .showAxes()
                .setWidthPx(290)
                .setHeightPx(160)
                .setTinyDots()
                .build();
    }

    private Bitmap createWearBitmapSmallDots(long start, long end) {
        return new BgSparklineBuilder(xdrip.getAppContext())
                .setBgGraphBuilder(new BgGraphBuilder(xdrip.getAppContext()))
                .setStart(start)
                .setEnd(end)
                .showAxes()
                .setWidthPx(290)
                .setHeightPx(160)
                .setSmallDots()
                .build();
    }

    private Bitmap createWFBitmapTinyDots(long start, long end) {
        return new BgSparklineBuilder(xdrip.getAppContext())
                .setBgGraphBuilder(new BgGraphBuilder(xdrip.getAppContext()))
                .setStart(start)
                .setEnd(end)
                //.showAxes()
                .setWidthPx(300)
                .setHeightPx(130)
                .setTinyDots()
                .build();
    }

    private Bitmap createWFBitmapSmallDots(long start, long end) {
        return new BgSparklineBuilder(xdrip.getAppContext())
                .setBgGraphBuilder(new BgGraphBuilder(xdrip.getAppContext()))
                .setStart(start)
                .setEnd(end)
                //.showAxes()
                .setWidthPx(300)
                .setHeightPx(130)
                .setSmallDots()
                .build();
    }

    private Bitmap createWearBitmap(long hours) {
        if (Pref.getBooleanDefaultFalse("pref_amazfit_widget_graph_dots"))
            return createWearBitmapTinyDots(System.currentTimeMillis() - 60000 * 60 * hours, System.currentTimeMillis());
        else
            return createWearBitmapSmallDots(System.currentTimeMillis() - 60000 * 60 * hours, System.currentTimeMillis());
    }

    private Bitmap createWFBitmap(long hours) {
        if (Pref.getBooleanDefaultFalse("pref_amazfit_watchface_graph_dots"))
            return createWFBitmapTinyDots(System.currentTimeMillis() - 60000 * 60 * hours, System.currentTimeMillis());
        else
            return createWFBitmapSmallDots(System.currentTimeMillis() - 60000 * 60 * hours, System.currentTimeMillis());
    }

    private String BitmaptoString(Bitmap bitmap) {
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, outputStream);
        return Base64.encodeToString(outputStream.toByteArray(), Base64.DEFAULT);
    }


}