package info.nightscout.androidaps.plugins.ConfigBuilder;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.support.annotation.Nullable;
import android.support.v7.app.AppCompatActivity;

import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.Date;

import info.nightscout.androidaps.Config;
import info.nightscout.androidaps.MainApp;
import info.nightscout.androidaps.R;
import info.nightscout.androidaps.Services.Intents;
import info.nightscout.androidaps.data.PumpEnactResult;
import info.nightscout.androidaps.db.TempBasal;
import info.nightscout.androidaps.db.Treatment;
import info.nightscout.androidaps.events.EventBolusRequested;
import info.nightscout.androidaps.events.EventTempBasalChange;
import info.nightscout.androidaps.events.EventTreatmentChange;
import info.nightscout.androidaps.interfaces.APSInterface;
import info.nightscout.androidaps.interfaces.BgSourceInterface;
import info.nightscout.androidaps.interfaces.ConstraintsInterface;
import info.nightscout.androidaps.interfaces.InsulinInterface;
import info.nightscout.androidaps.interfaces.PluginBase;
import info.nightscout.androidaps.interfaces.ProfileInterface;
import info.nightscout.androidaps.interfaces.PumpDescription;
import info.nightscout.androidaps.interfaces.PumpInterface;
import info.nightscout.androidaps.interfaces.TempBasalsInterface;
import info.nightscout.androidaps.interfaces.TreatmentsInterface;
import info.nightscout.androidaps.plugins.PumpDanaR.comm.MsgError;
import info.nightscout.androidaps.plugins.Loop.APSResult;
import info.nightscout.androidaps.plugins.Loop.DeviceStatus;
import info.nightscout.androidaps.plugins.Loop.LoopPlugin;
import info.nightscout.androidaps.plugins.OpenAPSAMA.DetermineBasalResultAMA;
import info.nightscout.androidaps.plugins.OpenAPSMA.DetermineBasalResultMA;
import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressDialog;
import info.nightscout.androidaps.plugins.Overview.Dialogs.BolusProgressHelperActivity;
import info.nightscout.androidaps.plugins.Overview.Notification;
import info.nightscout.androidaps.plugins.Overview.events.EventDismissBolusprogressIfRunning;
import info.nightscout.androidaps.plugins.Overview.events.EventDismissNotification;
import info.nightscout.androidaps.plugins.Overview.events.EventNewNotification;
import info.nightscout.androidaps.plugins.NSClientInternal.data.DbLogger;
import info.nightscout.androidaps.plugins.NSClientInternal.data.NSProfile;
import info.nightscout.utils.BatteryLevel;
import info.nightscout.utils.DateUtil;
import info.nightscout.utils.SP;

/**
 * Created by mike on 05.08.2016.
 */
public class ConfigBuilderPlugin implements PluginBase, PumpInterface, ConstraintsInterface {
    private static Logger log = LoggerFactory.getLogger(ConfigBuilderPlugin.class);

    static BgSourceInterface activeBgSource;
    static PumpInterface activePump;
    static ProfileInterface activeProfile;
    static TreatmentsInterface activeTreatments;
    static TempBasalsInterface activeTempBasals;
    static APSInterface activeAPS;
    static LoopPlugin activeLoop;
    static InsulinInterface activeInsulin;

    static public String nightscoutVersionName = "";
    static public Integer nightscoutVersionCode = 0;
    static public String nsClientVersionName = "";
    static public Integer nsClientVersionCode = 0;

    static ArrayList<PluginBase> pluginList;

    PowerManager.WakeLock mWakeLock;

    public ConfigBuilderPlugin() {
        MainApp.bus().register(this);
        PowerManager powerManager = (PowerManager) MainApp.instance().getApplicationContext().getSystemService(Context.POWER_SERVICE);
        mWakeLock = powerManager.newWakeLock(PowerManager.SCREEN_DIM_WAKE_LOCK, "ConfigBuilderPlugin");
        ;
    }

    @Override
    public int getType() {
        return PluginBase.GENERAL;
    }

    @Override
    public String getFragmentClass() {
        return ConfigBuilderFragment.class.getName();
    }

    @Override
    public String getName() {
        return MainApp.instance().getString(R.string.configbuilder);
    }

    @Override
    public String getNameShort() {
        String name = MainApp.sResources.getString(R.string.configbuilder_shortname);
        if (!name.trim().isEmpty()) {
            //only if translation exists
            return name;
        }
        // use long name as fallback
        return getName();
    }

    @Override
    public boolean isEnabled(int type) {
        return type == GENERAL && true;
    }

    @Override
    public boolean isVisibleInTabs(int type) {
        return type == GENERAL && true;
    }

    @Override
    public boolean canBeHidden(int type) {
        return false;
    }

    @Override
    public boolean hasFragment() {
        return true;
    }

    @Override
    public boolean showInList(int type) {
        return false;
    }

    @Override
    public void setFragmentEnabled(int type, boolean fragmentEnabled) {
        // Always enabled
    }

    @Override
    public void setFragmentVisible(int type, boolean fragmentVisible) {
        // Always visible
    }

    public void initialize() {
        pluginList = MainApp.getPluginsList();
        loadSettings();
    }

    public void storeSettings() {
        if (pluginList != null) {
            if (Config.logPrefsChange)
                log.debug("Storing settings");
            SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext());
            SharedPreferences.Editor editor = settings.edit();

            for (int type = 1; type < PluginBase.LAST; type++) {
                for (PluginBase p : pluginList) {
                    String settingEnabled = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Enabled";
                    String settingVisible = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Visible";
                    editor.putBoolean(settingEnabled, p.isEnabled(type));
                    editor.putBoolean(settingVisible, p.isVisibleInTabs(type));
                }
            }
            editor.apply();
            verifySelectionInCategories();
        }
    }

    private void loadSettings() {
        if (Config.logPrefsChange)
            log.debug("Loading stored settings");
        SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext());
        for (int type = 1; type < PluginBase.LAST; type++) {
            for (PluginBase p : pluginList) {
                try {
                    String settingEnabled = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Enabled";
                    String settingVisible = "ConfigBuilder_" + type + "_" + p.getClass().getSimpleName() + "_Visible";
                    if (SP.contains(settingEnabled))
                        p.setFragmentEnabled(type, SP.getBoolean(settingEnabled, true));
                    if (SP.contains(settingVisible))
                        p.setFragmentVisible(type, SP.getBoolean(settingVisible, true) && SP.getBoolean(settingEnabled, true));
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
        verifySelectionInCategories();
    }

    public static BgSourceInterface getActiveBgSource() {
        return activeBgSource;
    }

    @Nullable
    public static ProfileInterface getActiveProfile() {
        return activeProfile;
    }

    public static TreatmentsInterface getActiveTreatments() {
        return activeTreatments;
    }

    public static TempBasalsInterface getActiveTempBasals() {
        return activeTempBasals;
    }

    public static InsulinInterface getActiveInsulin() {
        return activeInsulin;
    }

    public static APSInterface getActiveAPS() {
        return activeAPS;
    }

    public static LoopPlugin getActiveLoop() {
        return activeLoop;
    }

    public void logPluginStatus() {
        for (PluginBase p : pluginList) {
            log.debug(p.getName() + ":" +
                    (p.isEnabled(1) ? " GENERAL" : "") +
                    (p.isEnabled(2) ? " TREATMENT" : "") +
                    (p.isEnabled(3) ? " TEMPBASAL" : "") +
                    (p.isEnabled(4) ? " PROFILE" : "") +
                    (p.isEnabled(5) ? " APS" : "") +
                    (p.isEnabled(6) ? " PUMP" : "") +
                    (p.isEnabled(7) ? " CONSTRAINTS" : "") +
                    (p.isEnabled(8) ? " LOOP" : "") +
                    (p.isEnabled(9) ? " BGSOURCE" : "") +
                    (p.isEnabled(10) ? " INSULIN" : "")
            );
        }
    }

    private void verifySelectionInCategories() {
        ArrayList<PluginBase> pluginsInCategory;

        // PluginBase.APS
        pluginsInCategory = MainApp.getSpecificPluginsListByInterface(APSInterface.class);
        activeAPS = (APSInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.APS);
        if (activeAPS != null) {
            if (Config.logConfigBuilder)
                log.debug("Selected APS interface: " + ((PluginBase) activeAPS).getName());
            for (PluginBase p : pluginsInCategory) {
                if (!p.getName().equals(((PluginBase) activeAPS).getName())) {
                    p.setFragmentVisible(PluginBase.APS, false);
                }
            }
        }

        // PluginBase.INSULIN
        pluginsInCategory = MainApp.getSpecificPluginsListByInterface(InsulinInterface.class);
        activeInsulin = (InsulinInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.INSULIN);
        if (Config.logConfigBuilder)
            log.debug("Selected insulin interface: " + ((PluginBase) activeInsulin).getName());
        for (PluginBase p : pluginsInCategory) {
            if (!p.getName().equals(((PluginBase) activeInsulin).getName())) {
                p.setFragmentVisible(PluginBase.INSULIN, false);
            }
        }

         // PluginBase.PROFILE
        pluginsInCategory = MainApp.getSpecificPluginsListByInterface(ProfileInterface.class);
        activeProfile = (ProfileInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.PROFILE);
        if (Config.logConfigBuilder)
            log.debug("Selected profile interface: " + ((PluginBase) activeProfile).getName());
        for (PluginBase p : pluginsInCategory) {
            if (!p.getName().equals(((PluginBase) activeProfile).getName())) {
                p.setFragmentVisible(PluginBase.PROFILE, false);
            }
        }

        // PluginBase.BGSOURCE
        pluginsInCategory = MainApp.getSpecificPluginsListByInterface(BgSourceInterface.class);
        activeBgSource = (BgSourceInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.BGSOURCE);
        if (Config.logConfigBuilder)
            log.debug("Selected bgSource interface: " + ((PluginBase) activeBgSource).getName());
        for (PluginBase p : pluginsInCategory) {
            if (!p.getName().equals(((PluginBase) activeBgSource).getName())) {
                p.setFragmentVisible(PluginBase.BGSOURCE, false);
            }
        }

        // PluginBase.PUMP
        pluginsInCategory = MainApp.getSpecificPluginsList(PluginBase.PUMP);
        activePump = (PumpInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.PUMP);
        if (Config.logConfigBuilder && activePump != null)
            log.debug("Selected pump interface: " + ((PluginBase) activePump).getName());
        for (PluginBase p : pluginsInCategory) {
            if (!p.getName().equals(((PluginBase) activePump).getName())) {
                p.setFragmentVisible(PluginBase.PUMP, false);
            }
        }

        // PluginBase.LOOP
        pluginsInCategory = MainApp.getSpecificPluginsList(PluginBase.LOOP);
        activeLoop = (LoopPlugin) getTheOneEnabledInArray(pluginsInCategory, PluginBase.LOOP);
        if (activeLoop != null) {
            if (Config.logConfigBuilder)
                log.debug("Selected loop interface: " + activeLoop.getName());
            for (PluginBase p : pluginsInCategory) {
                if (!p.getName().equals(activeLoop.getName())) {
                    p.setFragmentVisible(PluginBase.LOOP, false);
                }
            }
        }

        // PluginBase.TEMPBASAL
        pluginsInCategory = MainApp.getSpecificPluginsList(PluginBase.TEMPBASAL);
        activeTempBasals = (TempBasalsInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.TEMPBASAL);
        if (Config.logConfigBuilder && activeTempBasals != null)
            log.debug("Selected tempbasal interface: " + ((PluginBase) activeTempBasals).getName());
        for (PluginBase p : pluginsInCategory) {
            if (!p.getName().equals(((PluginBase) activeTempBasals).getName())) {
                p.setFragmentVisible(PluginBase.TEMPBASAL, false);
            }
        }

        // PluginBase.TREATMENT
        pluginsInCategory = MainApp.getSpecificPluginsList(PluginBase.TREATMENT);
        activeTreatments = (TreatmentsInterface) getTheOneEnabledInArray(pluginsInCategory, PluginBase.TREATMENT);
        if (Config.logConfigBuilder)
            log.debug("Selected treatment interface: " + ((PluginBase) activeTreatments).getName());
        for (PluginBase p : pluginsInCategory) {
            if (!p.getName().equals(((PluginBase) activeTreatments).getName())) {
                p.setFragmentVisible(PluginBase.TREATMENT, false);
            }
        }
    }

    @Nullable
    private PluginBase getTheOneEnabledInArray(ArrayList<PluginBase> pluginsInCategory, int type) {
        PluginBase found = null;
        for (PluginBase p : pluginsInCategory) {
            if (p.isEnabled(type) && found == null) {
                found = p;
            } else if (p.isEnabled(type)) {
                // set others disabled
                p.setFragmentEnabled(type, false);
            }
        }
        // If none enabled, enable first one
        if (found == null && pluginsInCategory.size() > 0)
            found = pluginsInCategory.get(0);
        return found;
    }

    /*
        * Pump interface
        *
        * Config builder return itself as a pump and check constraints before it passes command to pump driver
        */
    @Override
    public boolean isInitialized() {
        if (activePump != null)
            return activePump.isInitialized();
        else return true;
    }

    @Override
    public boolean isSuspended() {
        if (activePump != null)
            return activePump.isSuspended();
        else return false;
    }

    @Override
    public boolean isBusy() {
        if (activePump != null)
            return activePump.isBusy();
        else return false;
    }

    @Override
    public boolean isTempBasalInProgress() {
        if (activePump != null)
            return activePump.isTempBasalInProgress();
        else return false;
    }

    @Override
    public boolean isExtendedBoluslInProgress() {
        if (activePump != null)
            return activePump.isExtendedBoluslInProgress();
        else return false;
    }

    @Override
    public int setNewBasalProfile(NSProfile profile) {
        // Compare with pump limits
        NSProfile.BasalValue[] basalValues = profile.getBasalValues();

        for (int index = 0; index < basalValues.length; index++) {
            if (basalValues[index].value < getPumpDescription().basalMinimumRate) {
                Notification notification = new Notification(Notification.BASAL_VALUE_BELOW_MINIMUM, MainApp.sResources.getString(R.string.basalvaluebelowminimum), Notification.URGENT);
                MainApp.bus().post(new EventNewNotification(notification));
                return FAILED;
            }
        }

        MainApp.bus().post(new EventDismissNotification(Notification.BASAL_VALUE_BELOW_MINIMUM));

        if (isThisProfileSet(profile)) {
            log.debug("Correct profile already set");
            return NOT_NEEDED;
        } else if (activePump != null) {
            return activePump.setNewBasalProfile(profile);
        } else
            return SUCCESS;
    }

    @Override
    public boolean isThisProfileSet(NSProfile profile) {
        if (activePump != null)
            return activePump.isThisProfileSet(profile);
        else return true;
    }

    @Override
    public Date lastDataTime() {
        if (activePump != null)
            return activePump.lastDataTime();
        else return new Date();
    }

    @Override
    public void refreshDataFromPump(String reason) {
        if (activePump != null)
            activePump.refreshDataFromPump(reason);
    }

    @Override
    public double getBaseBasalRate() {
        if (activePump != null)
            return activePump.getBaseBasalRate();
        else
            return 0d;
    }

    @Override
    public double getTempBasalAbsoluteRate() {
        if (activePump != null)
            return activePump.getTempBasalAbsoluteRate();
        else
            return 0d;
    }

    @Override
    public double getTempBasalRemainingMinutes() {
        if (activePump != null)
            return activePump.getTempBasalRemainingMinutes();
        else
            return 0d;
    }

    @Override
    public TempBasal getTempBasal(Date time) {
        if (activePump != null)
            return activePump.getTempBasal(time);
        else
            return null;
    }

    @Override
    public TempBasal getTempBasal() {
        if (activePump != null)
            return activePump.getTempBasal();
        else
            return null;
    }

    @Override
    public TempBasal getExtendedBolus() {
        if (activePump != null)
            return activePump.getExtendedBolus();
        else
            return null;
    }

    public PumpEnactResult deliverTreatmentFromBolusWizard(InsulinInterface insulinType, Context context, Double insulin, Integer carbs, Double glucose, String glucoseType, int carbTime, JSONObject boluscalc) {
        mWakeLock.acquire();
        PumpEnactResult result;
        if (activePump != null) {
            insulin = applyBolusConstraints(insulin);
            carbs = applyCarbsConstraints(carbs);

            BolusProgressDialog bolusProgressDialog = null;
            if (context != null) {
                bolusProgressDialog = new BolusProgressDialog();
                bolusProgressDialog.setInsulin(insulin);
                bolusProgressDialog.show(((AppCompatActivity) context).getSupportFragmentManager(), "BolusProgress");
            }

            MainApp.bus().post(new EventBolusRequested(insulin));

            result = activePump.deliverTreatment(insulinType, insulin, carbs, context);

            BolusProgressDialog.bolusEnded = true;

            MainApp.bus().post(new EventDismissBolusprogressIfRunning(result));

            if (result.success) {
                Treatment t = new Treatment(insulinType);
                t.insulin = result.bolusDelivered;
                if (carbTime == 0)
                    t.carbs = (double) result.carbsDelivered; // with different carbTime record will come back from nightscout
                t.created_at = new Date();
                t.mealBolus = result.carbsDelivered > 0;
                MainApp.getDbHelper().create(t);
                t.setTimeIndex(t.getTimeIndex());
                t.carbs = (double) result.carbsDelivered;
                uploadBolusWizardRecord(t, glucose, glucoseType, carbTime, boluscalc);
            }
        } else {
            if (Config.logCongigBuilderActions)
                log.debug("Creating treatment: " + insulin + " carbs: " + carbs);
            Treatment t = new Treatment(insulinType);
            t.insulin = insulin;
            t.carbs = (double) carbs;
            t.created_at = new Date();
            t.mealBolus = t.carbs > 0;
            MainApp.getDbHelper().create(t);
            t.setTimeIndex(t.getTimeIndex());
            t.sendToNSClient();
            result = new PumpEnactResult();
            result.success = true;
            result.bolusDelivered = insulin;
            result.carbsDelivered = carbs;
        }
        mWakeLock.release();
        return result;
    }

    @Override
    public PumpEnactResult deliverTreatment(InsulinInterface insulinType, Double insulin, Integer carbs, Context context) {
        return deliverTreatment(insulinType, insulin, carbs, context, true);
    }

    public PumpEnactResult deliverTreatment(InsulinInterface insulinType, Double insulin, Integer carbs, Context context, boolean createTreatment) {
        mWakeLock.acquire();
        PumpEnactResult result;
        if (activePump != null) {
            insulin = applyBolusConstraints(insulin);
            carbs = applyCarbsConstraints(carbs);

            BolusProgressDialog bolusProgressDialog = null;
            if (context != null) {
                bolusProgressDialog = new BolusProgressDialog();
                bolusProgressDialog.setInsulin(insulin);
                bolusProgressDialog.show(((AppCompatActivity) context).getSupportFragmentManager(), "BolusProgress");
            } else {
                Intent i = new Intent();
                i.putExtra("insulin", insulin.doubleValue());
                i.setClass(MainApp.instance(), BolusProgressHelperActivity.class);
                i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                MainApp.instance().startActivity(i);
            }

            MainApp.bus().post(new EventBolusRequested(insulin));

            result = activePump.deliverTreatment(insulinType, insulin, carbs, context);

            BolusProgressDialog.bolusEnded = true;

            MainApp.bus().post(new EventDismissBolusprogressIfRunning(result));

            if (Config.logCongigBuilderActions)
                log.debug("deliverTreatment insulin: " + insulin + " carbs: " + carbs + " success: " + result.success + " enacted: " + result.enacted + " bolusDelivered: " + result.bolusDelivered);

            if (result.success && createTreatment) {
                Treatment t = new Treatment(insulinType);
                t.insulin = result.bolusDelivered;
                t.carbs = (double) result.carbsDelivered;
                t.created_at = new Date();
                t.mealBolus = t.carbs > 0;
                MainApp.getDbHelper().create(t);
                t.setTimeIndex(t.getTimeIndex());
                t.sendToNSClient();
            }
        } else {
            if (Config.logCongigBuilderActions)
                log.debug("Creating treatment: " + insulin + " carbs: " + carbs);
            Treatment t = new Treatment(insulinType);
            t.insulin = insulin;
            t.carbs = (double) carbs;
            t.created_at = new Date();
            t.mealBolus = t.carbs > 0;
            MainApp.getDbHelper().create(t);
            t.setTimeIndex(t.getTimeIndex());
            t.sendToNSClient();
            result = new PumpEnactResult();
            result.success = true;
            result.bolusDelivered = insulin;
            result.carbsDelivered = carbs;
        }
        mWakeLock.release();
        return result;
    }


    @Override
    public void stopBolusDelivering() {
        activePump.stopBolusDelivering();
    }

    /**
     * apply constraints, set temp based on absolute valus and expecting absolute result
     *
     * @param absoluteRate
     * @param durationInMinutes
     * @return
     */
    @Override
    public PumpEnactResult setTempBasalAbsolute(Double absoluteRate, Integer durationInMinutes) {
        Double rateAfterConstraints = applyBasalConstraints(absoluteRate);
        PumpEnactResult result = activePump.setTempBasalAbsolute(rateAfterConstraints, durationInMinutes);
        if (Config.logCongigBuilderActions)
            log.debug("setTempBasalAbsolute rate: " + rateAfterConstraints + " durationInMinutes: " + durationInMinutes + " success: " + result.success + " enacted: " + result.enacted);
        if (result.enacted && result.success) {
            if (result.isPercent) {
                uploadTempBasalStartPercent(result.percent, result.duration);
            } else {
                uploadTempBasalStartAbsolute(result.absolute, result.duration);
            }
            MainApp.bus().post(new EventTempBasalChange());
        }
        return result;
    }

    /**
     * apply constraints, set temp based on percent and expecting result in percent
     *
     * @param percent           0 ... 100 ...
     * @param durationInMinutes
     * @return result
     */
    @Override
    public PumpEnactResult setTempBasalPercent(Integer percent, Integer durationInMinutes) {
        Integer percentAfterConstraints = applyBasalConstraints(percent);
        PumpEnactResult result = activePump.setTempBasalPercent(percentAfterConstraints, durationInMinutes);
        if (Config.logCongigBuilderActions)
            log.debug("setTempBasalPercent percent: " + percentAfterConstraints + " durationInMinutes: " + durationInMinutes + " success: " + result.success + " enacted: " + result.enacted);
        if (result.enacted && result.success) {
            uploadTempBasalStartPercent(result.percent, result.duration);
            MainApp.bus().post(new EventTempBasalChange());
        }
        return result;
    }

    @Override
    public PumpEnactResult setExtendedBolus(Double insulin, Integer durationInMinutes) {
        Double rateAfterConstraints = applyBolusConstraints(insulin);
        PumpEnactResult result = activePump.setExtendedBolus(rateAfterConstraints, durationInMinutes);
        if (Config.logCongigBuilderActions)
            log.debug("setExtendedBolus rate: " + rateAfterConstraints + " durationInMinutes: " + durationInMinutes + " success: " + result.success + " enacted: " + result.enacted);
        if (result.enacted && result.success) {
            uploadExtendedBolus(result.bolusDelivered, result.duration);
            MainApp.bus().post(new EventTreatmentChange());
        }
        return result;
    }

    @Override
    public PumpEnactResult cancelTempBasal() {
        PumpEnactResult result = activePump.cancelTempBasal();
        if (Config.logCongigBuilderActions)
            log.debug("cancelTempBasal success: " + result.success + " enacted: " + result.enacted);
        if (result.enacted && result.success) {
            uploadTempBasalEnd();
            MainApp.bus().post(new EventTempBasalChange());
        }
        return result;
    }

    @Override
    public PumpEnactResult cancelExtendedBolus() {
        PumpEnactResult result = activePump.cancelExtendedBolus();
        if (Config.logCongigBuilderActions)
            log.debug("cancelExtendedBolus success: " + result.success + " enacted: " + result.enacted);
        return result;
    }

    /**
     * expect absolute request and allow both absolute and percent response based on pump capabilities
     *
     * @param request
     * @return
     */
    public PumpEnactResult applyAPSRequest(APSResult request) {
        request.rate = applyBasalConstraints(request.rate);
        PumpEnactResult result;

        if (!isInitialized()) {
            result = new PumpEnactResult();
            result.comment = MainApp.sResources.getString(R.string.pumpNotInitialized);
            result.enacted = false;
            result.success = false;
            log.debug("applyAPSRequest: " + MainApp.sResources.getString(R.string.pumpNotInitialized));
            return result;
        }

        if (isSuspended()) {
            result = new PumpEnactResult();
            result.comment = MainApp.sResources.getString(R.string.pumpsuspended);
            result.enacted = false;
            result.success = false;
            log.debug("applyAPSRequest: " + MainApp.sResources.getString(R.string.pumpsuspended));
            return result;
        }

        if (Config.logCongigBuilderActions)
            log.debug("applyAPSRequest: " + request.toString());
        if ((request.rate == 0 && request.duration == 0) || Math.abs(request.rate - getBaseBasalRate()) < 0.05) {
            if (isTempBasalInProgress()) {
                if (Config.logCongigBuilderActions)
                    log.debug("applyAPSRequest: cancelTempBasal()");
                result = cancelTempBasal();
            } else {
                result = new PumpEnactResult();
                result.absolute = request.rate;
                result.duration = 0;
                result.enacted = false;
                result.comment = "Basal set correctly";
                result.success = true;
                if (Config.logCongigBuilderActions)
                    log.debug("applyAPSRequest: Basal set correctly");
            }
        } else if (isTempBasalInProgress() && Math.abs(request.rate - getTempBasalAbsoluteRate()) < 0.05) {
            result = new PumpEnactResult();
            result.absolute = getTempBasalAbsoluteRate();
            result.duration = activePump.getTempBasal().getPlannedRemainingMinutes();
            result.enacted = false;
            result.comment = "Temp basal set correctly";
            result.success = true;
            if (Config.logCongigBuilderActions)
                log.debug("applyAPSRequest: Temp basal set correctly");
        } else {
            if (Config.logCongigBuilderActions)
                log.debug("applyAPSRequest: setTempBasalAbsolute()");
            result = setTempBasalAbsolute(request.rate, request.duration);
        }
        return result;
    }

    @Nullable
    @Override
    public JSONObject getJSONStatus() {
        if (activePump != null)
            return activePump.getJSONStatus();
        else return null;
    }

    @Override
    public String deviceID() {
        if (activePump != null)
            return activePump.deviceID();
        else return "No Pump active!";
    }

    @Override
    public PumpDescription getPumpDescription() {
        if (activePump != null)
            return activePump.getPumpDescription();
        else {
            PumpDescription emptyDescription = new PumpDescription();
            emptyDescription.isBolusCapable = false;
            emptyDescription.isExtendedBolusCapable = false;
            emptyDescription.isSetBasalProfileCapable = false;
            emptyDescription.isTempBasalCapable = true; // needs to be true before real driver is selected
            emptyDescription.isRefillingCapable = false;
            return emptyDescription;
        }
    }

    @Override
    public String shortStatus(boolean veryShort) {
        if (activePump != null) {
            return activePump.shortStatus(veryShort);
        } else {
            return "No Pump active!";
        }
    }

    /**
     * Constraints interface
     **/
    @Override
    public boolean isLoopEnabled() {
        boolean result = true;

        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            result = result && constrain.isLoopEnabled();
        }
        return result;
    }

    @Override
    public boolean isClosedModeEnabled() {
        boolean result = true;

        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            result = result && constrain.isClosedModeEnabled();
        }
        return result;
    }

    @Override
    public boolean isAutosensModeEnabled() {
        boolean result = true;

        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            result = result && constrain.isAutosensModeEnabled();
        }
        return result;
    }

    @Override
    public boolean isAMAModeEnabled() {
        SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext());
        boolean result = preferences.getBoolean("openapsama_useautosens", false);

        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            result = result && constrain.isAMAModeEnabled();
        }
        return result;
    }

    @Override
    public Double applyBasalConstraints(Double absoluteRate) {
        Double rateAfterConstrain = absoluteRate;
        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            rateAfterConstrain = Math.min(constrain.applyBasalConstraints(absoluteRate), rateAfterConstrain);
        }
        return rateAfterConstrain;
    }

    @Override
    public Integer applyBasalConstraints(Integer percentRate) {
        Integer rateAfterConstrain = percentRate;
        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            rateAfterConstrain = Math.min(constrain.applyBasalConstraints(percentRate), rateAfterConstrain);
        }
        return rateAfterConstrain;
    }

    @Override
    public Double applyBolusConstraints(Double insulin) {
        Double insulinAfterConstrain = insulin;
        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            insulinAfterConstrain = Math.min(constrain.applyBolusConstraints(insulin), insulinAfterConstrain);
        }
        return insulinAfterConstrain;
    }

    @Override
    public Integer applyCarbsConstraints(Integer carbs) {
        Integer carbsAfterConstrain = carbs;
        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            carbsAfterConstrain = Math.min(constrain.applyCarbsConstraints(carbs), carbsAfterConstrain);
        }
        return carbsAfterConstrain;
    }

    @Override
    public Double applyMaxIOBConstraints(Double maxIob) {
        Double maxIobAfterConstrain = maxIob;
        ArrayList<PluginBase> constraintsPlugins = MainApp.getSpecificPluginsListByInterface(ConstraintsInterface.class);
        for (PluginBase p : constraintsPlugins) {
            ConstraintsInterface constrain = (ConstraintsInterface) p;
            if (!p.isEnabled(PluginBase.CONSTRAINTS)) continue;
            maxIobAfterConstrain = Math.min(constrain.applyMaxIOBConstraints(maxIob), maxIobAfterConstrain);
        }
        return maxIobAfterConstrain;
    }

    public void uploadTempBasalStartAbsolute(Double absolute, double durationInMinutes) {
        try {
            Context context = MainApp.instance().getApplicationContext();
            JSONObject data = new JSONObject();
            data.put("eventType", "Temp Basal");
            data.put("duration", durationInMinutes);
            data.put("absolute", absolute);
            data.put("created_at", DateUtil.toISOString(new Date()));
            data.put("enteredBy", MainApp.instance().getString(R.string.app_name));
            data.put("notes", MainApp.sResources.getString(R.string.androidaps_tempbasalstartnote) + " " + absolute + "u/h " + durationInMinutes + " min"); // ECOR
            Bundle bundle = new Bundle();
            bundle.putString("action", "dbAdd");
            bundle.putString("collection", "treatments");
            bundle.putString("data", data.toString());
            Intent intent = new Intent(Intents.ACTION_DATABASE);
            intent.putExtras(bundle);
            intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
            context.sendBroadcast(intent);
            DbLogger.dbAdd(intent, data.toString(), ConfigBuilderPlugin.class);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public static void uploadOpenAPSOffline(double durationInMinutes) {
        try {
            Context context = MainApp.instance().getApplicationContext();
            JSONObject data = new JSONObject();
            data.put("eventType", "OpenAPS Offline");
            data.put("duration", durationInMinutes);
            data.put("created_at", DateUtil.toISOString(new Date()));
            data.put("enteredBy", MainApp.instance().getString(R.string.app_name));
            Bundle bundle = new Bundle();
            bundle.putString("action", "dbAdd");
            bundle.putString("collection", "treatments");
            bundle.putString("data", data.toString());
            Intent intent = new Intent(Intents.ACTION_DATABASE);
            intent.putExtras(bundle);
            intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
            context.sendBroadcast(intent);
            DbLogger.dbAdd(intent, data.toString(), ConfigBuilderPlugin.class);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void uploadTempBasalStartPercent(Integer percent, double durationInMinutes) {
        try {
            SharedPreferences SP = PreferenceManager.getDefaultSharedPreferences(MainApp.instance().getApplicationContext());
            boolean useAbsolute = SP.getBoolean("ns_sync_use_absolute", false);
            if (useAbsolute) {
                double absolute = getBaseBasalRate() * percent / 100d;
                uploadTempBasalStartAbsolute(absolute, durationInMinutes);
            } else {
                Context context = MainApp.instance().getApplicationContext();
                JSONObject data = new JSONObject();
                data.put("eventType", "Temp Basal");
                data.put("duration", durationInMinutes);
                data.put("percent", percent - 100);
                data.put("created_at", DateUtil.toISOString(new Date()));
                data.put("enteredBy", MainApp.instance().getString(R.string.app_name));
                data.put("notes", MainApp.sResources.getString(R.string.androidaps_tempbasalstartnote) + " " + percent + "% " + durationInMinutes + " min"); // ECOR
                Bundle bundle = new Bundle();
                bundle.putString("action", "dbAdd");
                bundle.putString("collection", "treatments");
                bundle.putString("data", data.toString());
                Intent intent = new Intent(Intents.ACTION_DATABASE);
                intent.putExtras(bundle);
                intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
                context.sendBroadcast(intent);
                DbLogger.dbAdd(intent, data.toString(), ConfigBuilderPlugin.class);
            }
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void uploadTempBasalEnd() {
        try {
            Context context = MainApp.instance().getApplicationContext();
            JSONObject data = new JSONObject();
            data.put("eventType", "Temp Basal");
            data.put("created_at", DateUtil.toISOString(new Date()));
            data.put("enteredBy", MainApp.instance().getString(R.string.app_name));
            data.put("notes", MainApp.sResources.getString(R.string.androidaps_tempbasalendnote)); // ECOR
            Bundle bundle = new Bundle();
            bundle.putString("action", "dbAdd");
            bundle.putString("collection", "treatments");
            bundle.putString("data", data.toString());
            Intent intent = new Intent(Intents.ACTION_DATABASE);
            intent.putExtras(bundle);
            intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
            context.sendBroadcast(intent);
            DbLogger.dbAdd(intent, data.toString(), ConfigBuilderPlugin.class);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void uploadExtendedBolus(Double insulin, double durationInMinutes) {
        try {
            Context context = MainApp.instance().getApplicationContext();
            JSONObject data = new JSONObject();
            data.put("eventType", "Combo Bolus");
            data.put("duration", durationInMinutes);
            data.put("splitNow", 0);
            data.put("splitExt", 100);
            data.put("enteredinsulin", insulin);
            data.put("relative", insulin);
            data.put("created_at", DateUtil.toISOString(new Date()));
            data.put("enteredBy", MainApp.instance().getString(R.string.app_name));
            Bundle bundle = new Bundle();
            bundle.putString("action", "dbAdd");
            bundle.putString("collection", "treatments");
            bundle.putString("data", data.toString());
            Intent intent = new Intent(Intents.ACTION_DATABASE);
            intent.putExtras(bundle);
            intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
            context.sendBroadcast(intent);
            DbLogger.dbAdd(intent, data.toString(), ConfigBuilderPlugin.class);
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void uploadDeviceStatus() {
        DeviceStatus deviceStatus = new DeviceStatus();
        try {
            LoopPlugin.LastRun lastRun = LoopPlugin.lastRun;
            if (lastRun != null && lastRun.lastAPSRun.getTime() > new Date().getTime() - 300 * 1000L) {
                // do not send if result is older than 1 min
                APSResult apsResult = lastRun.request;
                apsResult.json().put("timestamp", DateUtil.toISOString(lastRun.lastAPSRun));
                deviceStatus.suggested = apsResult.json();

                if (lastRun.request instanceof DetermineBasalResultMA) {
                    DetermineBasalResultMA result = (DetermineBasalResultMA) lastRun.request;
                    deviceStatus.iob = result.iob.json();
                    deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun));
                }

                if (lastRun.request instanceof DetermineBasalResultAMA) {
                    DetermineBasalResultAMA result = (DetermineBasalResultAMA) lastRun.request;
                    deviceStatus.iob = result.iob.json();
                    deviceStatus.iob.put("time", DateUtil.toISOString(lastRun.lastAPSRun));
                }

                if (lastRun.setByPump != null && lastRun.setByPump.enacted) { // enacted
                    deviceStatus.enacted = lastRun.request.json();
                    deviceStatus.enacted.put("rate", lastRun.setByPump.json().get("rate"));
                    deviceStatus.enacted.put("duration", lastRun.setByPump.json().get("duration"));
                    deviceStatus.enacted.put("recieved", true);
                    JSONObject requested = new JSONObject();
                    requested.put("duration", lastRun.request.duration);
                    requested.put("rate", lastRun.request.rate);
                    requested.put("temp", "absolute");
                    deviceStatus.enacted.put("requested", requested);
                }
            } else {
                log.debug("OpenAPS data too old to upload");
            }
            if (activePump != null) {
                deviceStatus.device = "openaps://" + deviceID();
                JSONObject pumpstatus = getJSONStatus();
                if (pumpstatus != null) {
                    deviceStatus.pump = pumpstatus;
                }
            }

            int batteryLevel = BatteryLevel.getBatteryLevel();
            deviceStatus.uploaderBattery = batteryLevel;

            deviceStatus.created_at = DateUtil.toISOString(new Date());
            deviceStatus.sendToNSClient();
        } catch (JSONException e) {
            e.printStackTrace();
        }
    }

    public void uploadBolusWizardRecord(Treatment t, double glucose, String glucoseType, int carbTime, JSONObject boluscalc) {
        JSONObject data = new JSONObject();
        try {
            data.put("eventType", "Bolus Wizard");
            if (t.insulin != 0d) data.put("insulin", t.insulin);
            if (t.carbs != 0d) data.put("carbs", t.carbs.intValue());
            data.put("created_at", DateUtil.toISOString(t.created_at));
            data.put("timeIndex", t.timeIndex);
            if (glucose != 0d) data.put("glucose", glucose);
            data.put("glucoseType", glucoseType);
            data.put("boluscalc", boluscalc);
            if (carbTime != 0) data.put("preBolus", carbTime);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        uploadCareportalEntryToNS(data);
    }

    public static void uploadCareportalEntryToNS(JSONObject data) {
        try {
            if (data.has("preBolus") && data.has("carbs")) {
                JSONObject prebolus = new JSONObject();
                prebolus.put("carbs", data.get("carbs"));
                data.remove("carbs");
                prebolus.put("eventType", data.get("eventType"));
                if (data.has("enteredBy")) prebolus.put("enteredBy", data.get("enteredBy"));
                if (data.has("notes")) prebolus.put("notes", data.get("notes"));
                long mills = DateUtil.fromISODateString(data.getString("created_at")).getTime();
                Date preBolusDate = new Date(mills + data.getInt("preBolus") * 60000L);
                prebolus.put("created_at", DateUtil.toISOString(preBolusDate));
                uploadCareportalEntryToNS(prebolus);
            }
            Context context = MainApp.instance().getApplicationContext();
            Bundle bundle = new Bundle();
            bundle.putString("action", "dbAdd");
            bundle.putString("collection", "treatments");
            bundle.putString("data", data.toString());
            Intent intent = new Intent(Intents.ACTION_DATABASE);
            intent.putExtras(bundle);
            intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
            context.sendBroadcast(intent);
            DbLogger.dbAdd(intent, data.toString(), ConfigBuilderPlugin.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public static void removeCareportalEntryFromNS(String _id) {
        try {
            Context context = MainApp.instance().getApplicationContext();
            Bundle bundle = new Bundle();
            bundle.putString("action", "dbRemove");
            bundle.putString("collection", "treatments");
            bundle.putString("_id", _id);
            Intent intent = new Intent(Intents.ACTION_DATABASE);
            intent.putExtras(bundle);
            intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
            context.sendBroadcast(intent);
            DbLogger.dbRemove(intent, _id, ConfigBuilderPlugin.class);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    public void uploadError(String error) {
        Context context = MainApp.instance().getApplicationContext();
        Bundle bundle = new Bundle();
        bundle.putString("action", "dbAdd");
        bundle.putString("collection", "treatments");
        JSONObject data = new JSONObject();
        try {
            data.put("eventType", "Announcement");
            data.put("created_at", DateUtil.toISOString(new Date()));
            data.put("notes", error);
            data.put("isAnnouncement", true);
        } catch (JSONException e) {
            e.printStackTrace();
        }
        bundle.putString("data", data.toString());
        Intent intent = new Intent(Intents.ACTION_DATABASE);
        intent.putExtras(bundle);
        intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
        context.sendBroadcast(intent);
        DbLogger.dbAdd(intent, data.toString(), MsgError.class);
    }

    public void uploadAppStart() {
        if (SP.getBoolean(R.string.key_ns_logappstartedevent, true)) {
            Context context = MainApp.instance().getApplicationContext();
            Bundle bundle = new Bundle();
            bundle.putString("action", "dbAdd");
            bundle.putString("collection", "treatments");
            JSONObject data = new JSONObject();
            try {
                data.put("eventType", "Note");
                data.put("created_at", DateUtil.toISOString(new Date()));
                data.put("notes", MainApp.sResources.getString(R.string.androidaps_start));
            } catch (JSONException e) {
                e.printStackTrace();
            }
            bundle.putString("data", data.toString());
            Intent intent = new Intent(Intents.ACTION_DATABASE);
            intent.putExtras(bundle);
            intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES);
            context.sendBroadcast(intent);
            DbLogger.dbAdd(intent, data.toString(), ConfigBuilderPlugin.class);
        }
    }

}
