/**
   * This method is synchronized to prevent multiple check-ins in a row at the same location and
   * during the same visit. See #579.
   *
   * @see org.openmrs.module.emrapi.adt.AdtService#checkInPatient(org.openmrs.Patient,
   *     org.openmrs.Location, org.openmrs.Provider, java.util.List, java.util.List, boolean)
   */
  @Override
  @Transactional
  public synchronized Encounter checkInPatient(
      Patient patient,
      Location where,
      Provider checkInClerk,
      List<Obs> obsForCheckInEncounter,
      List<Order> ordersForCheckInEncounter,
      boolean newVisit) {
    if (checkInClerk == null) {
      checkInClerk = getProvider(Context.getAuthenticatedUser());
    }

    Visit activeVisit = getActiveVisitHelper(patient, where);

    if (activeVisit != null && newVisit) {
      closeAndSaveVisit(activeVisit);
      activeVisit = null;
    }

    if (activeVisit == null) {
      activeVisit = ensureActiveVisit(patient, where);
    }

    Encounter lastEncounter = getLastEncounter(patient);
    if (lastEncounter != null
        && activeVisit.equals(lastEncounter.getVisit())
        && emrApiProperties.getCheckInEncounterType().equals(lastEncounter.getEncounterType())
        && where.equals(lastEncounter.getLocation())) {
      log.warn(
          "Patient id:{} tried to check-in twice in a row at id:{} during the same visit",
          patient.getId(),
          where.getId());
      return lastEncounter;
    }

    Encounter encounter =
        buildEncounter(
            emrApiProperties.getCheckInEncounterType(),
            patient,
            where,
            null,
            new Date(),
            obsForCheckInEncounter,
            ordersForCheckInEncounter);
    encounter.addProvider(emrApiProperties.getCheckInClerkEncounterRole(), checkInClerk);
    activeVisit.addEncounter(encounter);
    encounterService.saveEncounter(encounter);
    return encounter;
  }
  /**
   * Construct a new EncounterDetailSubmissionElement
   *
   * @param context
   * @param parameters
   * @should display 'Enter' option if 'type' is set to Autocomplete
   */
  public EncounterDetailSubmissionElement(
      FormEntryContext context, Map<String, Object> parameters) {

    // Register Date and Time widgets, if appropriate
    if (Boolean.TRUE.equals(parameters.get("date"))) {

      dateWidget = new DateWidget();
      dateErrorWidget = new ErrorWidget();

      if (context.getExistingEncounter() != null) {
        dateWidget.setInitialValue(context.getExistingEncounter().getEncounterDatetime());
      } else if (parameters.get("defaultDate") != null) {
        dateWidget.setInitialValue(parameters.get("defaultDate"));
      }

      if (parameters.get("disallowMultipleEncountersOnDate") != null
          && StringUtils.hasText((String) parameters.get("disallowMultipleEncountersOnDate"))) {
        dateWidget.setOnChangeFunction(
            "existingEncounterOnDate(this, '"
                + parameters.get("disallowMultipleEncountersOnDate")
                + "') ");
      }

      if ("true".equals(parameters.get("showTime"))) {
        timeWidget = new TimeWidget();
        timeErrorWidget = new ErrorWidget();
        if (context.getExistingEncounter() != null) {
          timeWidget.setInitialValue(context.getExistingEncounter().getEncounterDatetime());
        } else if (parameters.get("defaultDate") != null) {
          timeWidget.setInitialValue(parameters.get("defaultDate"));
        }
        context.registerWidget(timeWidget);
        context.registerErrorWidget(timeWidget, timeErrorWidget);
      }
      context.registerWidget(dateWidget);
      context.registerErrorWidget(dateWidget, dateErrorWidget);
    }

    // Register Provider widgets, if appropriate
    if (Boolean.TRUE.equals(parameters.get("provider"))) {

      if ("autocomplete".equals(parameters.get("type"))) {
        providerWidget = new AutocompleteWidget(Person.class);
      } else {
        providerWidget = new DropdownWidget();
      }
      providerErrorWidget = new ErrorWidget();

      List<Option> providerOptions = new ArrayList<Option>();
      // If specific persons are specified, display only those persons in order
      String personsParam = (String) parameters.get("persons");
      if (personsParam != null) {
        for (String s : personsParam.split(",")) {
          Person p = HtmlFormEntryUtil.getPerson(s);
          if (p == null) {
            throw new RuntimeException("Cannot find Person: " + s);
          }
          String label = p.getPersonName().getFullName();
          providerOptions.add(new Option(label, p.getId().toString(), false));
        }
        removeNonProviders(providerOptions);
      }

      // Only if specific person ids are not passed in do we get by user Role
      if (providerOptions.isEmpty()) {

        List<PersonStub> users = new ArrayList<PersonStub>();
        List<Option> providerUsers = new ArrayList<Option>();

        // If the "role" attribute is passed in, limit to users with this role
        if (parameters.get("role") != null) {
          Role role = Context.getUserService().getRole((String) parameters.get("role"));
          if (role == null) {
            throw new RuntimeException("Cannot find role: " + parameters.get("role"));
          } else {
            users =
                Context.getService(HtmlFormEntryService.class)
                    .getUsersAsPersonStubs(role.getRole());
          }
        }

        // Otherwise, use default options appropriate to the underlying OpenMRS version
        else {
          if (openmrsVersionDoesNotSupportProviders()) {
            // limit to users with the default OpenMRS PROVIDER role,
            String defaultRole = OpenmrsConstants.PROVIDER_ROLE;
            Role role = Context.getUserService().getRole(defaultRole);
            if (role != null) {
              users =
                  Context.getService(HtmlFormEntryService.class)
                      .getUsersAsPersonStubs(role.getRole());
            }
            // If this role isn't used, default to all Users
            if (users.isEmpty()) {
              users = Context.getService(HtmlFormEntryService.class).getUsersAsPersonStubs(null);
            }
          } else {
            // in OpenMRS 1.9+, get all suitable providers
            users = getAllProvidersThatArePersonsAsPersonStubs();
          }
        }

        for (PersonStub personStub : users) {

          Option option = new Option(personStub.toString(), personStub.getId().toString(), false);
          providerUsers.add(option);
        }
        providerOptions.addAll(providerUsers);
      }

      // Set default values as appropriate
      Person defaultProvider = null;
      Option defProviderOption;
      if (context.getExistingEncounter() != null) {
        defaultProvider = context.getExistingEncounter().getProvider();
        // this is done to avoid default provider being added twice due to that it can be added from
        // the
        // users = getAllProvidersThatArePersonsAsPersonStubs(); section with selected="false",
        // therefore this can't be caught when
        // searching whether the options list contains the 'defaultProvider'
        boolean defaultOptionPresent = false;
        if (defaultProvider != null) {
          for (Option option : providerOptions) {
            if (option.getValue().equals(defaultProvider.getId().toString())) {
              defaultOptionPresent = true;
              providerOptions.remove(option);
              break;
            }
          }
        }
        if (defaultOptionPresent) {
          defProviderOption =
              new Option(
                  defaultProvider.getPersonName().getFullName(),
                  defaultProvider.getId().toString(),
                  true);
          providerOptions.add(defProviderOption);
        }

      } else {
        String defParam = (String) parameters.get("default");
        if (StringUtils.hasText(defParam)) {
          if ("currentuser".equalsIgnoreCase(defParam)) {
            defaultProvider = Context.getAuthenticatedUser().getPerson();
          } else {
            defaultProvider = HtmlFormEntryUtil.getPerson(defParam);
          }
          if (defaultProvider == null) {
            throw new IllegalArgumentException(
                "Invalid default provider specified for encounter: " + defParam);
          } else {
            defProviderOption =
                new Option(
                    defaultProvider.getPersonName().getFullName(),
                    defaultProvider.getId().toString(),
                    true);
            for (Option option : providerOptions) {
              if (option.getValue().equals(defProviderOption.getValue())) {
                providerOptions.remove(option);
                break;
              }
            }
            providerOptions.add(defProviderOption);
          }
        }
      }
      if (defaultProvider != null) {
        providerWidget.setInitialValue(new PersonStub(defaultProvider));
      }
      Collections.sort(providerOptions, new OptionComparator());

      if (("autocomplete").equals(parameters.get("type"))) {
        providerWidget.addOption(new Option());
        if (!providerOptions.isEmpty()) {
          providerWidget.setOptions(providerOptions);
        }

      } else {
        // if initialValueIsSet=false, no initial/default provider, hence this shows the 'select
        // input' field as first option
        boolean initialValueIsSet = !(providerWidget.getInitialValue() == null);
        providerWidget.addOption(
            new Option(
                Context.getMessageSourceService().getMessage("htmlformentry.chooseAProvider"),
                "",
                !initialValueIsSet)); // if no initial or default value

        if (!providerOptions.isEmpty()) {
          for (Option option : providerOptions) {
            providerWidget.addOption(option);
          }
        }
      }
      context.registerWidget(providerWidget);
      context.registerErrorWidget(providerWidget, providerErrorWidget);
    }

    if (Boolean.TRUE.equals(parameters.get("encounterType"))) {
      encounterTypeWidget = new EncounterTypeWidget();
      encounterTypeErrorWidget = new ErrorWidget();
      if (parameters.get("types") != null) {
        List<EncounterType> encounterTypes = new ArrayList<EncounterType>();
        String[] temp = ((String) parameters.get("types")).split(",");
        for (String s : temp) {
          EncounterType type = HtmlFormEntryUtil.getEncounterType(s);
          if (type == null) {
            throw new RuntimeException("Cannot find encounter type: " + s);
          }
          encounterTypes.add(type);
        }

        encounterTypeWidget.setOptions(encounterTypes);
      }
      // Set default values

      EncounterType defaultEncounterType = null;
      if (context.getExistingEncounter() != null) {
        defaultEncounterType = context.getExistingEncounter().getEncounterType();
      } else {
        String defaultTypeId = (String) parameters.get("default");
        if (StringUtils.hasText(defaultTypeId)) {
          defaultEncounterType = HtmlFormEntryUtil.getEncounterType(defaultTypeId);
        }
      }

      encounterTypeWidget.setInitialValue(defaultEncounterType);
      context.registerWidget(encounterTypeWidget);
      context.registerErrorWidget(encounterTypeWidget, encounterTypeErrorWidget);
    }

    // Register Location widgets, if appropriate
    if (Boolean.TRUE.equals(parameters.get("location"))) {

      locationErrorWidget = new ErrorWidget();
      List<Location> locations = new ArrayList<Location>();
      List<Option> locationOptions = new ArrayList<Option>();

      if ("autocomplete".equals(parameters.get("type"))) {
        locationWidget = new AutocompleteWidget(Location.class);
      } else {
        locationWidget = new DropdownWidget();
      }

      // If the "order" attribute is passed in, limit to the specified locations in order
      if (parameters.get("order") != null) {

        String[] temp = ((String) parameters.get("order")).split(",");
        for (String s : temp) {
          Location loc = HtmlFormEntryUtil.getLocation(s);
          if (loc == null) {
            throw new RuntimeException("Cannot find location: " + loc);
          }
          locations.add(loc);
        }
      }

      // Set default values
      Location defaultLocation = null;
      if (context.getExistingEncounter() != null) {
        defaultLocation = context.getExistingEncounter().getLocation();
      } else {
        String defaultLocId = (String) parameters.get("default");
        if (StringUtils.hasText(defaultLocId)) {
          defaultLocation = HtmlFormEntryUtil.getLocation(defaultLocId);
        }
      }
      defaultLocation = defaultLocation == null ? context.getDefaultLocation() : defaultLocation;
      locationWidget.setInitialValue(defaultLocation);

      if (!locations.isEmpty()) {
        for (Location location : locations) {
          String label = location.getName();
          Option option =
              new Option(label, location.getId().toString(), location.equals(defaultLocation));
          locationOptions.add(option);
        }
      } else {
        locations = Context.getLocationService().getAllLocations();
        for (Location location : locations) {
          String label = location.getName();
          Option option =
              new Option(label, location.getId().toString(), location.equals(defaultLocation));
          locationOptions.add(option);
        }
        Collections.sort(locationOptions, new OptionComparator());
      }

      if ("autocomplete".equals(parameters.get("type"))) {
        locationWidget.addOption(new Option());
        if (!locationOptions.isEmpty()) {
          locationWidget.setOptions(locationOptions);
        }
      } else {
        boolean initialValueIsSet = !(locationWidget.getInitialValue() == null);
        locationWidget.addOption(
            new Option(
                Context.getMessageSourceService().getMessage("htmlformentry.chooseALocation"),
                "",
                !initialValueIsSet));
        if (!locationOptions.isEmpty()) {
          for (Option option : locationOptions) locationWidget.addOption(option);
        }
      }
      context.registerWidget(locationWidget);
      context.registerErrorWidget(locationWidget, locationErrorWidget);
    }

    if (Boolean.TRUE.equals(parameters.get("showVoidEncounter"))
        && context.getMode()
            == Mode
                .EDIT) { // only show void option if the encounter already exists.  And VIEW implies
                         // not voided.
      if (parameters.get("toggle") != null) {
        ToggleWidget toggleWidget = new ToggleWidget((String) parameters.get("toggle"));
        voidWidget =
            new CheckboxWidget(
                " " + Context.getMessageSourceService().getMessage("general.voided"),
                (context.getExistingEncounter() != null
                        && context.getExistingEncounter().isVoided().equals(true))
                    ? "true"
                    : "false",
                toggleWidget.getTargetId(),
                toggleWidget.isToggleDim());
      } else {
        voidWidget = new CheckboxWidget();
      }
      voidWidget.setLabel(" " + Context.getMessageSourceService().getMessage("general.voided"));
      voidErrorWidget = new ErrorWidget();
      if (context.getExistingEncounter() != null
          && context.getExistingEncounter().isVoided().equals(true))
        voidWidget.setInitialValue("true");
      context.registerWidget(voidWidget);
      context.registerErrorWidget(voidWidget, voidErrorWidget);
    }

    // set the id, if it has been specified
    if (parameters.get("id") != null) {
      id = (String) parameters.get("id");
    }
  }
 /**
  * @param a
  * @param b
  * @return true if a.equals(b) or a is an ancestor of b.
  */
 private boolean isSameOrAncestor(Location a, Location b) {
   if (a == null || b == null) {
     return a == null && b == null;
   }
   return a.equals(b) || isSameOrAncestor(a, b.getParentLocation());
 }