private void initStage1(final FlowPanel div) {
    div.add(new HTMLPanel("h2", "1. Set the start and end dates"));

    HorizontalPanel hpanel = new HorizontalPanel();

    hpanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
    hpanel.setSpacing(10);

    final DatePicker dateFrom = new DatePicker();
    final DatePicker dateTo = new DatePicker();

    hpanel.add(new Label("Start date:"));
    hpanel.add(dateFrom);
    hpanel.add(new Label("End date:"));
    hpanel.add(dateTo);

    final Button cont =
        WidgetFactory.createButton(
            "Continue",
            new ClickHandler() {
              @Override
              public void onClick(ClickEvent event) {
                final long twelveHours = 12 * 60 * 60 * 1000;
                long timeFrom = dateFrom.getValue().getTime() - twelveHours;
                long timeTo = dateTo.getValue().getTime() + twelveHours;

                initStage2(timeFrom, timeTo, div);
              }
            });

    cont.setEnabled(false);
    cont.getElement().addClassName("scran24-admin-button");

    dateFrom.addValueChangeHandler(
        new ValueChangeHandler<Date>() {
          @Override
          public void onValueChange(ValueChangeEvent<Date> event) {
            if (event.getValue() != null && dateTo.getValue() != null) {
              cont.setEnabled(true);
            }
          }
        });

    dateTo.addValueChangeHandler(
        new ValueChangeHandler<Date>() {
          @Override
          public void onValueChange(ValueChangeEvent<Date> event) {
            if (event.getValue() != null && dateFrom.getValue() != null) {
              cont.setEnabled(true);
            }
          }
        });

    div.add(hpanel);
    div.add(cont);
  }
  @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 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 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;
  }
  private void updateSchedule() {
    content.clear();
    content.add(new HTMLPanel("<h1>Update schedule</h1>"));
    HorizontalPanel hpanel = new HorizontalPanel();

    hpanel.setVerticalAlignment(HasVerticalAlignment.ALIGN_TOP);
    hpanel.setSpacing(10);

    final DatePicker dateFrom = new DatePicker();
    final DatePicker dateTo = new DatePicker();

    final Button update =
        WidgetFactory.createButton(
            "Update",
            new ClickHandler() {
              @Override
              public void onClick(ClickEvent event) {
                final long twelveHours = 12 * 60 * 60 * 1000;
                final long timeFrom = dateFrom.getValue().getTime() - twelveHours;
                final long timeTo = dateTo.getValue().getTime() + twelveHours;

                surveyControl.getParameters(
                    new AsyncCallback<SurveyParameters>() {
                      @Override
                      public void onFailure(Throwable caught) {
                        content.clear();
                        content.add(new HTMLPanel("<p>Server error: </p>" + caught.getMessage()));
                      }

                      @Override
                      public void onSuccess(SurveyParameters result) {
                        surveyControl.setParameters(
                            result.withDates(timeFrom, timeTo),
                            new AsyncCallback<Void>() {
                              @Override
                              public void onFailure(Throwable caught) {
                                content.clear();
                                content.add(
                                    new HTMLPanel("<p>Server error: </p>" + caught.getMessage()));
                              }

                              @Override
                              public void onSuccess(Void result) {
                                Location.reload();
                              }
                            });
                      }
                    });
              }
            });

    update.getElement().addClassName("scran24-admin-button");

    dateFrom.addValueChangeHandler(
        new ValueChangeHandler<Date>() {
          @Override
          public void onValueChange(ValueChangeEvent<Date> event) {
            if (event.getValue() != null && dateTo.getValue() != null) {
              update.setEnabled(true);
            }
          }
        });

    dateTo.addValueChangeHandler(
        new ValueChangeHandler<Date>() {
          @Override
          public void onValueChange(ValueChangeEvent<Date> event) {
            if (event.getValue() != null && dateFrom.getValue() != null) {
              update.setEnabled(true);
            }
          }
        });

    update.setEnabled(false);

    switch (parameters.state) {
      case SUSPENDED:
      case ACTIVE:
        dateFrom.setValue(new Date(parameters.startDate), true);
        dateTo.setValue(new Date(parameters.endDate), true);
        break;
      case NOT_INITIALISED:
        break;
    }

    hpanel.add(new Label("Start date:"));
    hpanel.add(dateFrom);
    hpanel.add(new Label("End date:"));
    hpanel.add(dateTo);

    content.add(hpanel);
    content.add(update);
  }
  private void showSurveyStatus() {
    content.clear();

    switch (parameters.state) {
      case SUSPENDED:
        content.add(new HTMLPanel("<h1>Survey is suspended</h1>"));
        content.add(
            new HTMLPanel(
                "<h2>Reason</h2><p>"
                    + SafeHtmlUtils.htmlEscape(parameters.suspensionReason)
                    + "</p>"));

        Button resumeButton =
            WidgetFactory.createButton(
                "Resume",
                new ClickHandler() {
                  @Override
                  public void onClick(ClickEvent event) {
                    surveyControl.setParameters(
                        parameters.withState(SurveyState.ACTIVE).withSuspensionReason(""),
                        new AsyncCallback<Void>() {
                          @Override
                          public void onFailure(Throwable caught) {
                            content.clear();
                            content.add(
                                new HTMLPanel("<p>Server error: </p>" + caught.getMessage()));
                          }

                          @Override
                          public void onSuccess(Void result) {
                            Location.reload();
                          }
                        });
                  }
                });

        resumeButton.getElement().addClassName("scran24-admin-button");

        content.add(resumeButton);
        break;
      case NOT_INITIALISED:
        content.add(new HTMLPanel("<h1>Survey has not yet been activated</h1>"));
        content.add(new HTMLPanel("<p>Follow the instructions below to activate the survey."));

        FlowPanel initDiv = new FlowPanel();
        content.add(initDiv);

        initStage1(initDiv);
        break;
      case ACTIVE:
        Date now = new Date();

        boolean showSuspend = true;

        if (now.getTime() < parameters.startDate)
          content.add(new HTMLPanel("<h1>Survey is active, but not yet started</h1>"));
        else if (now.getTime() > parameters.endDate) {
          content.add(new HTMLPanel("<h1>Survey is finished</h1>"));
          showSuspend = false;
        } else content.add(new HTMLPanel("<h1>Survey is running</h1>"));

        content.add(new HTMLPanel("<h2>Start date (inclusive)</h2>"));
        content.add(
            new HTMLPanel(
                "<p>"
                    + DateTimeFormat.getFormat("EEE, MMMM d, yyyy")
                        .format(new Date(parameters.startDate))
                    + "</p>"));

        content.add(new HTMLPanel("<h2>End date (exclusive)</h2>"));
        content.add(
            new HTMLPanel(
                "<p>"
                    + DateTimeFormat.getFormat("EEE, MMMM d, yyyy")
                        .format(new Date(parameters.endDate))
                    + "</p>"));

        if (showSuspend) {

          content.add(new HTMLPanel(SafeHtmlUtils.fromSafeConstant("<h3>Suspend survey</h3>")));
          content.add(
              new HTMLPanel(SafeHtmlUtils.fromSafeConstant("<p>Reason for suspension:</p>")));
          final TextBox reason = new TextBox();
          reason.getElement().getStyle().setWidth(600, Unit.PX);

          Button suspend =
              WidgetFactory.createButton(
                  "Suspend",
                  new ClickHandler() {
                    @Override
                    public void onClick(ClickEvent event) {
                      if (reason.getText().isEmpty()) {
                        Window.alert("Please give a reason for suspension.");
                      } else {
                        surveyControl.setParameters(
                            parameters
                                .withSuspensionReason(reason.getText())
                                .withState(SurveyState.SUSPENDED),
                            new AsyncCallback<Void>() {
                              @Override
                              public void onSuccess(Void result) {
                                Location.reload();
                              }

                              @Override
                              public void onFailure(Throwable caught) {
                                Window.alert("Server error: " + caught.getMessage());
                              }
                            });
                      }
                    }
                  });

          suspend.getElement().addClassName("scran24-admin-button");

          content.add(reason);
          content.add(new HTMLPanel("<p></p>"));
          content.add(suspend);
        }
        break;
    }
  }
  private void initStage2(final long startDate, final long endDate, FlowPanel div) {
    div.clear();
    div.add(new HTMLPanel("h2", "2. Upload the respondent accounts"));

    final FlowPanel messageDiv = new FlowPanel();

    final Button start =
        WidgetFactory.createButton(
            "Activate survey",
            new ClickHandler() {
              @Override
              public void onClick(ClickEvent event) {
                surveyControl.getParameters(
                    new AsyncCallback<SurveyParameters>() {

                      @Override
                      public void onFailure(Throwable caught) {
                        messageDiv.clear();
                        messageDiv.getElement().getStyle().setColor("#d00");
                        messageDiv.add(
                            new HTMLPanel(
                                SafeHtmlUtils.fromString(
                                    "Survey could not be started: " + caught.getMessage())));
                      }

                      @Override
                      public void onSuccess(SurveyParameters result) {
                        surveyControl.setParameters(
                            result.withDates(startDate, endDate).withState(SurveyState.ACTIVE),
                            new AsyncCallback<Void>() {
                              @Override
                              public void onSuccess(Void result) {
                                Location.reload();
                              }

                              @Override
                              public void onFailure(Throwable caught) {
                                messageDiv.clear();
                                messageDiv.getElement().getStyle().setColor("#d00");
                                messageDiv.add(
                                    new HTMLPanel(
                                        SafeHtmlUtils.fromString(
                                            "Survey could not be started: "
                                                + caught.getMessage())));
                              }
                            });
                      }
                    });
              }
            });

    start.getElement().addClassName("intake24-admin-button");
    start.setEnabled(false);

    List<String> permissions = Arrays.asList(new String[] {"processSurvey:" + surveyId});

    final UserInfoUpload userUpload =
        new UserInfoUpload(
            surveyId,
            "respondent",
            permissions,
            new Callback1<Option<String>>() {
              @Override
              public void call(Option<String> res) {
                res.accept(
                    new Option.SideEffectVisitor<String>() {
                      @Override
                      public void visitSome(String error) {
                        messageDiv.clear();
                        messageDiv.getElement().getStyle().setColor("#d00");
                        messageDiv.add(new HTMLPanel(SafeHtmlUtils.fromString(error)));
                        start.setEnabled(false);
                      }

                      @Override
                      public void visitNone() {
                        messageDiv.clear();
                        messageDiv.getElement().getStyle().setColor("#0d0");
                        messageDiv.add(
                            new HTMLPanel(
                                SafeHtmlUtils.fromString(
                                    "Respondent accounts uploaded successfully")));
                        start.setEnabled(true);
                      }
                    });
              }
            });

    div.add(userUpload);
    div.add(messageDiv);
    div.add(start);
  }