protected Rules defaultRules(
      PortionSizeScriptManager scriptManager,
      CompoundFoodTemplateManager templateManager,
      RecipeManager recipeManager) {
    return new Rules(
        // meal prompts
        TreePVector.<WithPriority<PromptRule<Meal, MealOperation>>>empty()
            .plus(AskForMealTime.withPriority(3))
            .plus(ShowEditMeal.withPriority(2))
            .plus(ShowDrinkReminderPrompt.withPriority(1))
            .plus(ShowReadyMealsPrompt.withPriority(0)),

        // food prompts
        TreePVector.<WithPriority<PromptRule<FoodEntry, FoodOperation>>>empty()
            .plus(ShowBrandNamePrompt.withPriority(-1))
            .plus(ShowNextPortionSizeStep.withPriority(scriptManager, 0))
            .plus(ChoosePortionSizeMethod.withPriority(1))
            .plus(AskForMissingFoodDescription.withPriority(2))
            .plus(ShowSimpleHomeRecipePrompt.withPriority(2))
            .plus(AskIfHomeRecipe.withPriority(3))
            .plus(SplitFood.withPriority(4))
            .plus(InformFoodComplete.withPriority(-100)),

        // extended food propmts
        TreePVector.<WithPriority<PromptRule<Pair<FoodEntry, Meal>, MealOperation>>>empty()
            .plus(ShowEditIngredientsPrompt.withPriority(3))
            .plus(AskToLookupFood.withPriority(3, locale, recipeManager))
            .plus(
                ShowSameAsBeforePrompt.withPriority(
                    3, getSchemeId(), getDataVersion(), scriptManager, templateManager))
            .plus(ShowHomeRecipeServingsPrompt.withPriority(2))
            .plus(ShowTemplateRecipeSavePrompt.withPriority(1, recipeManager))
            .plus(ShowCompoundFoodPrompt.withPriority(0, locale))
            .plus(ShowAssociatedFoodPrompt.withPriority(0, locale)),

        // global prompts

        TreePVector.<WithPriority<PromptRule<Survey, SurveyOperation>>>empty()
            .plus(ConfirmCompletion.withPriority(0))
            .plus(ShowEnergyValidationPrompt.withPriority(1, 500.0))
            .plus(ShowEmptySurveyPrompt.withPriority(1))
            .plus(ShowTimeGapPrompt.withPriority(2, 180, new Time(9, 0), new Time(21, 0))),

        // selection rules
        TreePVector.<WithPriority<SelectionRule>>empty()
            .plus(SelectForPortionSize.withPriority(3))
            .plus(SelectRawFood.withPriority(2))
            .plus(SelectFoodForAssociatedPrompts.withPriority(1))
            .plus(SelectIncompleteFreeEntryMeal.withPriority(1))
            .plus(SelectMealWithNoDrink.withPriority(1))
            .plus(SelectUnconfirmedMeal.withPriority(1))
            .plus(SelectMealForReadyMeals.withPriority(1)));
  }
public class CompoundFoodPrompt implements Prompt<Pair<FoodEntry, Meal>, MealOperation> {
  private static final PromptMessages messages = PromptMessages.Util.getInstance();
  private static final HelpMessages helpMessages = HelpMessages.Util.getInstance();

  private static final PVector<ShepherdTour.Step> tour =
      TreePVector.<ShepherdTour.Step>empty()
          .plus(
              new ShepherdTour.Step(
                  "prompt",
                  "#intake24-compound-food-prompt",
                  helpMessages.compoundFood_promptTitle(),
                  helpMessages.compoundFood_promptDescription()));;

  private final Meal meal;
  private final int foodIndex;
  private final int componentIndex;
  private final boolean allowSkip;
  private final String locale;

  private Panel buttonsPanel;
  private boolean isFirst;
  private static Logger log = Logger.getLogger(CompoundFoodPrompt.class.getSimpleName());

  private FoodBrowser foodBrowser;

  public CompoundFoodPrompt(
      final String locale,
      final Meal meal,
      final int foodIndex,
      final int componentIndex,
      boolean isFirst,
      boolean allowSkip) {
    this.locale = locale;
    this.meal = meal;
    this.foodIndex = foodIndex;
    this.componentIndex = componentIndex;
    this.isFirst = isFirst;
    this.allowSkip = allowSkip;
  }

  @Override
  public SurveyStageInterface getInterface(
      final Callback1<MealOperation> onComplete,
      Callback1<Function1<Pair<FoodEntry, Meal>, Pair<FoodEntry, Meal>>> updateIntermediateState) {
    final TemplateFood food = (TemplateFood) meal.foods.get(foodIndex);
    final ComponentDef component = food.data.template.get(componentIndex);

    final FlowPanel content = new FlowPanel();

    PromptUtil.addBackLink(content);

    component.headerConstructor.accept(
        new Option.SideEffectVisitor<Function0<Widget>>() {
          @Override
          public void visitSome(Function0<Widget> item) {
            Widget header = item.apply();
            ShepherdTour.makeShepherdTarget(header);
            content.add(header);
          }

          @Override
          public void visitNone() {}
        });

    SafeHtml promptText;

    if (isFirst) promptText = SafeHtmlUtils.fromSafeConstant(component.primaryInstancePrompt);
    else promptText = SafeHtmlUtils.fromSafeConstant(component.secondaryInstancePrompt);

    final Panel promptPanel =
        WidgetFactory.createPromptPanel(
            promptText,
            WidgetFactory.createHelpButton(
                new ClickHandler() {
                  @Override
                  public void onClick(ClickEvent arg0) {
                    String promptType =
                        CompoundFoodPrompt.class.getSimpleName() + "." + food.data.template_id;
                    GoogleAnalytics.trackHelpButtonClicked(promptType);
                    ShepherdTour.startTour(
                        tour.plusAll(foodBrowser.getShepherdTourSteps()), promptType);
                  }
                }));

    content.add(promptPanel);
    ShepherdTour.makeShepherdTarget(promptPanel);
    promptPanel.getElement().setId("intake24-compound-food-prompt");

    String skipLabel;

    if (isFirst) skipLabel = component.primarySkipButtonLabel;
    else skipLabel = component.secondarySkipButtonLabel;

    Option<SkipFoodHandler> skipHandler =
        allowSkip
            ? Option.some(
                new SkipFoodHandler(
                    skipLabel,
                    new Callback() {
                      @Override
                      public void call() {
                        onComplete.call(
                            MealOperation.updateTemplateFood(
                                foodIndex,
                                new Function1<TemplateFood, TemplateFood>() {
                                  @Override
                                  public TemplateFood apply(TemplateFood argument) {
                                    return argument.markComponentComplete(componentIndex);
                                  }
                                }));
                      }
                    }))
            : Option.<SkipFoodHandler>none();

    foodBrowser =
        new FoodBrowser(
            locale,
            new Callback1<FoodData>() {
              @Override
              public void call(final FoodData result) {
                onComplete.call(
                    MealOperation.update(
                        new Function1<Meal, Meal>() {
                          @Override
                          public Meal apply(Meal argument) {
                            EncodedFood componentFood =
                                new EncodedFood(
                                    result,
                                    FoodLink.newLinked(food.link.id),
                                    "compound food template");
                            return argument
                                .plusFood(componentFood)
                                .updateFood(
                                    foodIndex,
                                    food.addComponent(componentIndex, componentFood.link.id));
                          }
                        }));
              }
            },
            new Callback1<String>() {
              @Override
              public void call(String code) {
                throw new RuntimeException(
                    "Special foods are not allowed as compound food ingredients");
              }
            },
            new Callback() {
              @Override
              public void call() {
                onComplete.call(
                    MealOperation.update(
                        new Function1<Meal, Meal>() {
                          @Override
                          public Meal apply(Meal argument) {
                            MissingFood missingFood =
                                new MissingFood(
                                        FoodLink.newLinked(food.link.id), component.name, false)
                                    .withFlag(MissingFood.NOT_HOME_RECIPE_FLAG);

                            return argument
                                .plusFood(missingFood)
                                .updateFood(
                                    foodIndex,
                                    food.addComponent(componentIndex, missingFood.link.id));
                          }
                        }));
              }
            },
            skipHandler,
            false,
            Option.<Pair<String, String>>none());

    foodBrowser.browse(
        component.categoryCode,
        component.dataSetLabel,
        component.foodsLabel,
        component.categoriesLabel);

    content.add(foodBrowser);

    return new SurveyStageInterface.Aligned(
        content,
        HasHorizontalAlignment.ALIGN_LEFT,
        HasVerticalAlignment.ALIGN_TOP,
        SurveyStageInterface.DEFAULT_OPTIONS);
  }

  @Override
  public String toString() {
    return "Compound food prompt";
  }
}
public class DrinkScalePrompt implements SimplePrompt<Double> {
  private static final PromptMessages messages = PromptMessages.Util.getInstance();
  private static final HelpMessages helpMessages = HelpMessages.Util.getInstance();

  private static final PVector<ShepherdTour.Step> tour =
      TreePVector.<ShepherdTour.Step>empty()
          .plus(
              new ShepherdTour.Step(
                  "image",
                  "#intake24-sliding-scale-image",
                  helpMessages.drinkScale_imageTitle(),
                  helpMessages.drinkScale_imageDescription()))
          .plus(
              new ShepherdTour.Step(
                  "overlay",
                  "#intake24-sliding-scale-overlay",
                  helpMessages.drinkScale_overlayTitle(),
                  helpMessages.drinkScale_overlayDescription()))
          .plus(
              new ShepherdTour.Step(
                  "label",
                  "#intake24-sliding-scale-overlay",
                  helpMessages.drinkScale_volumeLabelTitle(),
                  helpMessages.drinkScale_volumeLabelDescription(),
                  "top right",
                  "bottom right"))
          .plus(
              new ShepherdTour.Step(
                  "slider",
                  "#intake24-sliding-scale-slider",
                  helpMessages.drinkScale_sliderTitle(),
                  helpMessages.drinkScale_sliderDescription(),
                  "middle right",
                  "middle left"))
          .plus(
              new ShepherdTour.Step(
                  "lessButton",
                  "#intake24-sliding-scale-less-button",
                  helpMessages.drinkScale_lessButtonTitle(),
                  helpMessages.drinkScale_lessButtonDescription()))
          .plus(
              new ShepherdTour.Step(
                  "moreButton",
                  "#intake24-sliding-scale-more-button",
                  helpMessages.drinkScale_moreButtonTitle(),
                  helpMessages.drinkScale_moreButtonDescription()))
          .plus(
              new ShepherdTour.Step(
                  "continueButton",
                  "#intake24-sliding-scale-continue-button",
                  helpMessages.drinkScale_continueButtonTitle(),
                  helpMessages.drinkScale_continueButtonDescription(),
                  "top right",
                  "bottom right"));

  private final DrinkScalePromptDef def;

  public DrinkScalePrompt(DrinkScalePromptDef def) {
    this.def = def;
  }

  @Override
  public FlowPanel getInterface(final Callback1<Double> onComplete) {
    FlowPanel content = new FlowPanel();

    FlowPanel promptPanel =
        WidgetFactory.createPromptPanel(
            def.message,
            ShepherdTour.createTourButton(tour, DrinkScalePrompt.class.getSimpleName()));
    content.add(promptPanel);

    SlidingScaleDef ssd =
        new SlidingScaleDef(
            def.scaleDef.baseImage,
            def.scaleDef.overlayImage,
            def.scaleDef.width,
            def.scaleDef.height,
            def.scaleDef.emptyLevel,
            def.scaleDef.fullLevel);

    final Function1<Double, String> label =
        new Function1<Double, String>() {
          @Override
          public String apply(Double argument) {
            double volume = def.scaleDef.calcVolume(argument);
            int roundedVolume = (int) volume;

            NumberFormat nf = NumberFormat.getDecimalFormat();

            return nf.format(roundedVolume) + " " + messages.drinkScale_volumeUnit();
          }
        };

    final SlidingScale scale = new SlidingScale(ssd, def.limit, def.initialLevel, label);

    content.add(scale);

    final Button less =
        WidgetFactory.createButton(
            def.lessLabel,
            new ClickHandler() {
              @Override
              public void onClick(ClickEvent event) {
                scale.sliderBar.setValue(scale.sliderBar.getValue() + scale.sliderBar.getStep());
                /*if (scale.sliderBar.getValue() > 0.99)
                	less.setEnabled(false);
                else
                	less.setEnabled(true);*/
              }
            });

    less.getElement().setId("intake24-sliding-scale-less-button");

    final Button more =
        WidgetFactory.createButton(
            def.moreLabel,
            new ClickHandler() {
              @Override
              public void onClick(ClickEvent event) {
                scale.sliderBar.setValue(scale.sliderBar.getValue() - scale.sliderBar.getStep());
                /*if (scale.sliderBar.getValue() < 0.01)
                	more.setEnabled(false);
                else
                	more.setEnabled(true);*/
              }
            });

    more.getElement().setId("intake24-sliding-scale-more-button");

    final Button finish =
        WidgetFactory.createGreenButton(
            def.acceptLabel,
            new ClickHandler() {
              @Override
              public void onClick(ClickEvent event) {
                onComplete.call(scale.getValue());
              }
            });

    finish.getElement().setId("intake24-sliding-scale-continue-button");

    content.add(WidgetFactory.createButtonsPanel(less, more, finish));

    ShepherdTour.makeShepherdTarget(
        promptPanel, scale.image, scale.overlayDiv, scale.sliderBar, less, more, finish);

    return content;
  }
}
public class AsServedPrompt implements SimplePrompt<Integer> {
  private final AsServedPromptDef def;

  public AsServedPrompt(AsServedPromptDef def) {
    this.def = def;
  }

  private static final HelpMessages helpMessages = HelpMessages.Util.getInstance();

  private static final PVector<ShepherdTour.Step> tour =
      TreePVector.<ShepherdTour.Step>empty()
          .plus(
              new ShepherdTour.Step(
                  "image",
                  "#intake24-as-served-image-container",
                  helpMessages.asServed_imageTitle(),
                  helpMessages.asServed_imageDescription()))
          .plus(
              new ShepherdTour.Step(
                  "label",
                  "#intake24-as-served-image-container",
                  helpMessages.asServed_labelTitle(),
                  helpMessages.asServed_labelDescription(),
                  "top right",
                  "bottom right"))
          .plus(
              new ShepherdTour.Step(
                  "thumbs",
                  "#intake24-as-served-thumbs-container",
                  helpMessages.asServed_thumbsTitle(),
                  helpMessages.asServed_thumbsDescription()))
          .plus(
              new ShepherdTour.Step(
                  "hadLess",
                  "#intake24-as-served-prev-button",
                  helpMessages.asServed_prevButtonTitle(),
                  helpMessages.asServed_prevButtonDescription(),
                  "bottom left",
                  "top left"))
          .plus(
              new ShepherdTour.Step(
                  "hadMore",
                  "#intake24-as-served-next-button",
                  helpMessages.asServed_nextButtonTitle(),
                  helpMessages.asServed_nextButtonDescription(),
                  "bottom left",
                  "top left"))
          .plus(
              new ShepherdTour.Step(
                  "hadThisMuch",
                  "#intake24-as-served-confirm-button",
                  helpMessages.asServed_confirmButtonTitle(),
                  helpMessages.asServed_confirmButtonDescription(),
                  "bottom right",
                  "top right"));

  @Override
  public FlowPanel getInterface(final Callback1<Integer> onComplete) {
    final FlowPanel content = new FlowPanel();

    FlowPanel promptPanel =
        WidgetFactory.createPromptPanel(
            def.promptText,
            ShepherdTour.createTourButton(tour, AsServedPrompt.class.getSimpleName()));
    ShepherdTour.makeShepherdTarget(promptPanel);
    content.add(promptPanel);

    ImageChooser imageChooser =
        new ImageChooser(
            def.images,
            def.prevLabel,
            def.nextLabel,
            def.acceptLabel,
            def.images.length / 2,
            new ImageChooser.ResultHandler() {
              @Override
              public void handleResult(final int index) {
                onComplete.call(index);
              };
            });

    content.add(imageChooser);

    ShepherdTour.makeShepherdTarget(
        imageChooser.imageContainer,
        imageChooser.thumbsContainer,
        imageChooser.nextButton,
        imageChooser.prevButton,
        imageChooser.confirmButton);

    return content;
  }

  @Override
  public String toString() {
    return "As served portion size prompt";
  }
}
/**
 * Basic implementation of a survey scheme. Uses the default prompt rules (see <b>defaultRules</b>),
 * and starting meals (see <b>startingSurveyData</b>).
 */
public abstract class BasicScheme implements SurveyScheme {
  static final double MAX_AGE_HOURS = 8.0;

  protected final SurveyInterfaceManager interfaceManager;
  protected final StateManager stateManager;
  protected final PromptManager defaultPromptManager;
  protected final SelectionManager defaultSelectionManager;
  protected final PortionSizeScriptManager defaultScriptManager;
  protected final CompoundFoodTemplateManager defaultTemplateManager;
  protected final RecipeManager recipeManager;
  protected final LogRecorder log;
  protected final String locale;

  protected final TreePVector<Function1<Survey, Survey>> basicPostProcess =
      TreePVector.<Function1<Survey, Survey>>empty().plus(new ProcessMilkInHotDrinks());

  private static Logger logger = Logger.getLogger("BasicScheme");

  protected Survey startingSurveyData() {
    return new Survey(
        PredefinedMeals.getStartingMealsForCurrentLocale(),
        new Selection.EmptySelection(SelectionMode.AUTO_SELECTION),
        System.currentTimeMillis(),
        HashTreePSet.<String>empty(),
        HashTreePMap.<String, String>empty());
  }

  protected Survey postProcess(Survey data, PVector<Function1<Survey, Survey>> functions) {
    Survey postProcessed = data;

    for (Function1<Survey, Survey> f : functions) postProcessed = f.apply(postProcessed);

    return postProcessed;
  }

  protected Rules defaultRules(
      PortionSizeScriptManager scriptManager,
      CompoundFoodTemplateManager templateManager,
      RecipeManager recipeManager) {
    return new Rules(
        // meal prompts
        TreePVector.<WithPriority<PromptRule<Meal, MealOperation>>>empty()
            .plus(AskForMealTime.withPriority(3))
            .plus(ShowEditMeal.withPriority(2))
            .plus(ShowDrinkReminderPrompt.withPriority(1))
            .plus(ShowReadyMealsPrompt.withPriority(0)),

        // food prompts
        TreePVector.<WithPriority<PromptRule<FoodEntry, FoodOperation>>>empty()
            .plus(ShowBrandNamePrompt.withPriority(-1))
            .plus(ShowNextPortionSizeStep.withPriority(scriptManager, 0))
            .plus(ChoosePortionSizeMethod.withPriority(1))
            .plus(AskForMissingFoodDescription.withPriority(2))
            .plus(ShowSimpleHomeRecipePrompt.withPriority(2))
            .plus(AskIfHomeRecipe.withPriority(3))
            .plus(SplitFood.withPriority(4))
            .plus(InformFoodComplete.withPriority(-100)),

        // extended food propmts
        TreePVector.<WithPriority<PromptRule<Pair<FoodEntry, Meal>, MealOperation>>>empty()
            .plus(ShowEditIngredientsPrompt.withPriority(3))
            .plus(AskToLookupFood.withPriority(3, locale, recipeManager))
            .plus(
                ShowSameAsBeforePrompt.withPriority(
                    3, getSchemeId(), getDataVersion(), scriptManager, templateManager))
            .plus(ShowHomeRecipeServingsPrompt.withPriority(2))
            .plus(ShowTemplateRecipeSavePrompt.withPriority(1, recipeManager))
            .plus(ShowCompoundFoodPrompt.withPriority(0, locale))
            .plus(ShowAssociatedFoodPrompt.withPriority(0, locale)),

        // global prompts

        TreePVector.<WithPriority<PromptRule<Survey, SurveyOperation>>>empty()
            .plus(ConfirmCompletion.withPriority(0))
            .plus(ShowEnergyValidationPrompt.withPriority(1, 500.0))
            .plus(ShowEmptySurveyPrompt.withPriority(1))
            .plus(ShowTimeGapPrompt.withPriority(2, 180, new Time(9, 0), new Time(21, 0))),

        // selection rules
        TreePVector.<WithPriority<SelectionRule>>empty()
            .plus(SelectForPortionSize.withPriority(3))
            .plus(SelectRawFood.withPriority(2))
            .plus(SelectFoodForAssociatedPrompts.withPriority(1))
            .plus(SelectIncompleteFreeEntryMeal.withPriority(1))
            .plus(SelectMealWithNoDrink.withPriority(1))
            .plus(SelectUnconfirmedMeal.withPriority(1))
            .plus(SelectMealForReadyMeals.withPriority(1)));
  }

  public BasicScheme(String locale, final SurveyInterfaceManager interfaceManager) {
    this.log = new LogRecorder();
    this.interfaceManager = interfaceManager;
    this.locale = locale;

    interfaceManager.setCallbacks(
        new Callback1<Survey>() {
          @Override
          public void call(Survey updatedState) {
            if (updatedState.flags.contains(Survey.FLAG_SKIP_HISTORY))
              stateManager.updateState(updatedState.clearFlag(Survey.FLAG_SKIP_HISTORY), false);
            else stateManager.updateState(updatedState, true);
            showNextPage();
          }
        },
        new Callback2<Survey, Boolean>() {
          @Override
          public void call(Survey updatedState, Boolean makeHistoryEntry) {
            stateManager.updateState(updatedState, makeHistoryEntry);
          }
        });

    defaultScriptManager = new PortionSizeScriptManager(DefaultPortionSizeScripts.getCtors());

    defaultTemplateManager =
        new CompoundFoodTemplateManager(
            HashTreePMap.<String, TemplateFoodData>empty()
                .plus("sandwich", FoodTemplates.sandwich)
                .plus("salad", FoodTemplates.salad));

    recipeManager =
        new RecipeManager(
            getSchemeId(), getDataVersion(), defaultScriptManager, defaultTemplateManager);

    final Rules rules = defaultRules(defaultScriptManager, defaultTemplateManager, recipeManager);

    defaultPromptManager = new RuleBasedPromptManager(rules);

    // final SelectionManager selectionManager = new
    // RuleBasedSelectionManager(rules.selectionRules);

    defaultSelectionManager = new PromptAvailabilityBasedSelectionManager(defaultPromptManager);

    Survey initialState =
        StateManagerUtil.getLatestState(
                CurrentUser.getUserInfo().userName,
                getSchemeId(),
                getDataVersion(),
                defaultScriptManager,
                defaultTemplateManager)
            .accept(
                new Option.Visitor<Survey, Survey>() {
                  @Override
                  public Survey visitSome(Survey data) {
                    double age = (System.currentTimeMillis() - data.startTime) / 3600000.0;
                    logger.info("Saved state is " + age + " hours old.");

                    if (age > MAX_AGE_HOURS) {
                      logger.info(
                          "Saved state is older than " + MAX_AGE_HOURS + " hours and has expired.");
                      return startingSurveyData();
                    } else {
                      return data.clearCompletionConfirmed().clearEnergyValueConfirmed();
                    }
                  }

                  @Override
                  public Survey visitNone() {
                    // log.info("No saved state, starting new survey.");
                    return startingSurveyData();
                  }
                });

    stateManager =
        new StateManager(
            initialState,
            getSchemeId(),
            getDataVersion(),
            new Callback() {
              @Override
              public void call() {
                showNextPage();
              }
            },
            defaultScriptManager);
  }

  @Override
  public abstract void showNextPage();

  @Override
  public abstract String getDataVersion();

  @Override
  public abstract String getSchemeId();

  @Override
  public List<Anchor> navBarLinks() {
    return Collections.emptyList();
  }
}