package de.jotomo.ruffyscripter.commands;

import android.os.SystemClock;

import org.monkey.d.ruffy.ruffy.driver.display.MenuAttribute;
import org.monkey.d.ruffy.ruffy.driver.display.MenuType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

import de.jotomo.ruffyscripter.RuffyScripter;

public class BolusCommand implements Command {
    private static final Logger log = LoggerFactory.getLogger(BolusCommand.class);

    private final double bolus;

    public BolusCommand(double bolus) {
        this.bolus = bolus;
    }

    @Override
    public List<String> validateArguments() {
        List<String> violations = new ArrayList<>();

        if (bolus <= 0 || bolus > 25) {
            violations.add("Requested bolus " + bolus + " out of limits (0-25)");
        }

        return violations;
    }

    @Override
    public CommandResult execute(RuffyScripter scripter) {
        try {
            enterBolusMenu(scripter);

            boolean bolusAmountInputSuccess = false;
            int bolusAmountInputRetries = 2;
            while (!bolusAmountInputSuccess) {
                try {
                    inputBolusAmount(scripter);
                    SystemClock.sleep(750);
                    verifyDisplayedBolusAmount(scripter);
                    bolusAmountInputSuccess = true;
                } catch (CommandException e) {
                    if (bolusAmountInputRetries >= 0) {
                        log.warn("Failed to set bolus amount, retrying", e);
                        bolusAmountInputRetries--;
                    } else {
                        throw e;
                    }
                }
            }

            // confirm bolus
            scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
            scripter.pressCheckKey();

            // the pump displays the entered bolus and waits a bit to let user check and cancel
            scripter.waitForMenuToBeLeft(MenuType.BOLUS_ENTER);

            scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
                    "Pump did not return to MAIN_MEU from BOLUS_ENTER to deliver bolus. "
                            + "Check pump manually, the bolus might not have been delivered.");

            // wait for bolus delivery to complete
            Double bolusRemaining = (Double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS_REMAINING);
            while (bolusRemaining != null) {
                log.debug("Delivering bolus, remaining: " + bolusRemaining);
                SystemClock.sleep(200);
                bolusRemaining = (Double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS_REMAINING);
            }

            // TODO what if we hit 'cartridge low' alert here? is it immediately displayed or after the bolus?
            // TODO how are error states reported back to the caller that occur outside of calls in genal? Low battery, low cartridge?

            // make sure no alert (occlusion, cartridge empty) has occurred.
            scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU,
                    "Bolus delivery did not complete as expected. "
                            + "Check pump manually, the bolus might not have been delivered.");

            return new CommandResult().success(true).enacted(true)
                    .message(String.format(Locale.US, "Delivered %02.1f U", bolus));
        } catch (CommandException e) {
            return e.toCommandResult();
        }
    }

    private void enterBolusMenu(RuffyScripter scripter) {
        scripter.verifyMenuIsDisplayed(MenuType.MAIN_MENU);
        scripter.navigateToMenu(MenuType.BOLUS_MENU);
        scripter.verifyMenuIsDisplayed(MenuType.BOLUS_MENU);
        scripter.pressCheckKey();
        scripter.waitForMenuUpdate();
        scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
    }

    private void inputBolusAmount(RuffyScripter scripter) {
        // press 'up' once for each 0.1 U increment
        long steps = Math.round(bolus * 10);
        for (int i = 0; i < steps; i++) {
            scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
            scripter.pressUpKey();
            SystemClock.sleep(100);
        }
    }

    private void verifyDisplayedBolusAmount(RuffyScripter scripter) {
        scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);
        double displayedBolus = (double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS);
        log.debug("Final bolus: " + displayedBolus);
        if (Math.abs(displayedBolus - bolus) > 0.05) {
            throw new CommandException().message("Failed to set correct bolus. Expected: " + bolus + ", actual: " + displayedBolus);
        }
    }

    @Override
    public String toString() {
        return "BolusCommand{" +
                "bolus=" + bolus +
                '}';
    }
}

@@ -4333,156 +4333,65 @@
     
-scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);%0A        double displayedBolus = (double) scripter.currentMenu.getAttribute(MenuAttribute.BOLUS
+double displayedBolus = readDisplayedBolusAmount(scripter
 );%0A 
@@ -4622,32 +4622,32 @@
 isplayedBolus);%0A
-
         %7D%0A    %7D%0A
@@ -4639,32 +4639,470 @@
        %7D%0A    %7D%0A%0A
+    private double readDisplayedBolusAmount(RuffyScripter scripter) %7B%0A        scripter.verifyMenuIsDisplayed(MenuType.BOLUS_ENTER);%0A        Object amountObj = scripter.currentMenu.getAttribute(MenuAttribute.BOLUS);%0A        while (!(amountObj instanceof Double)) %7B%0A            scripter.waitForMenuUpdate();%0A            amountObj = scripter.currentMenu.getAttribute(MenuAttribute.BOLUS);%0A        %7D%0A        return (double) amountObj;%0A    %7D%0A%0A
     @Override%0A  

deal with blinking values.