gt:                 if (maxMoodId == null || maxMoodId < id) {
context: /**
   * Copyright (c) 2010-2020 Contributors to the openHAB project
   *
   * See the NOTICE file(s) distributed with this work for additional
   * information.
   *
   * This program and the accompanying materials are made available under the
   * terms of the Eclipse Public License 2.0 which is available at
   * http://www.eclipse.org/legal/epl-2.0
   *
   * SPDX-License-Identifier: EPL-2.0
   */
  package org.openhab.binding.loxone.internal.controls;
  
  import static org.openhab.binding.loxone.internal.LxBindingConstants.*;
  
  import java.io.IOException;
  import java.math.BigDecimal;
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.HashMap;
  import java.util.List;
  import java.util.Map;
  import java.util.stream.Collectors;
  
  import org.openhab.binding.loxone.internal.types.LxState;
  import org.openhab.binding.loxone.internal.types.LxUuid;
  import org.openhab.core.library.types.DecimalType;
  import org.openhab.core.library.types.UpDownType;
  import org.openhab.core.thing.ChannelUID;
  import org.openhab.core.thing.type.ChannelTypeUID;
  import org.openhab.core.types.Command;
  import org.openhab.core.types.State;
  import org.openhab.core.types.StateDescriptionFragmentBuilder;
  import org.openhab.core.types.StateOption;
  import org.openhab.core.types.UnDefType;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import com.google.gson.JsonSyntaxException;
  
  /**
   * A Light Controller V2 type of control on Loxone Miniserver.
   * <p>
   * This control has been introduced in Loxone Config 9 in 2017 and it makes the {@link LxControlLightController}
   * obsolete. Both controls will exist for some time together.
   * <p>
   * Light controller V2 can have N outputs named AQ1...AQN that can function as Switch, Dimmer, RGB, Lumitech or Smart
   * Actuator functional blocks. Individual controls will be created for these outputs so they can be operated directly
   * and independently from the controller.
   * <p>
   * Controller can also have M moods configured. Each mood defines own subset of outputs and their settings, which will
   * be engaged when the mood is active. A dedicated switch control object will be created for each mood.
   * This effectively will allow for mixing various moods by individually enabling/disabling them.
   * <p>
   * It seems there is no imposed limitation for the number of outputs and moods.
   *
   * @author Pawel Pieczul - initial contribution
   *
   */
  class LxControlLightControllerV2 extends LxControl {
  
      static class Factory extends LxControlInstance {
          @Override
          LxControl create(LxUuid uuid) {
              return new LxControlLightControllerV2(uuid);
          }
  
          @Override
          String getType() {
              return "lightcontrollerv2";
          }
      }
  
      /**
       * State with list of active moods
       */
      private static final String STATE_ACTIVE_MOODS_LIST = "activemoods";
      /**
       * State with list of available moods
       */
      private static final String STATE_MOODS_LIST = "moodlist";
  
      /**
       * Command string used to set a given mood
       */
      private static final String CMD_CHANGE_TO_MOOD = "changeTo";
      /**
       * Command string used to change to the next mood
       */
      private static final String CMD_NEXT_MOOD = "plus";
      /**
       * Command string used to change to the previous mood
       */
      private static final String CMD_PREVIOUS_MOOD = "minus";
      /**
       * Command string used to add mood to the active moods (mix it in)
       */
      private static final String CMD_ADD_MOOD = "addMood";
      /**
       * Command string used to remove mood from the active moods (mix it out)
       */
      private static final String CMD_REMOVE_MOOD = "removeMood";
  
      private final transient Logger logger = LoggerFactory.getLogger(LxControlLightControllerV2.class);
  
      // Following commands are not supported:
      // moveFavoriteMood, moveAdditionalMood, moveMood, addToFavoriteMood, removeFromFavoriteMood, learn, delete
  
      private Map<Integer, LxControlMood> moodList = new HashMap<>();
      private List<Integer> activeMoods = new ArrayList<>();
      private ChannelUID channelId;
  
      private LxControlLightControllerV2(LxUuid uuid) {
          super(uuid);
      }
  
      @Override
      public void initialize(LxControlConfig config) {
          super.initialize(config);
          tags.add("Scene");
          // add only channel, state description will be added later when a control state update message is received
          channelId = addChannel("Number", new ChannelTypeUID(BINDING_ID, MINISERVER_CHANNEL_TYPE_LIGHT_CTRL),
                  defaultChannelLabel, "Light controller V2", tags, this::handleCommands, this::getChannelState);
      }
  
      private void handleCommands(Command command) throws IOException {
          if (command instanceof UpDownType) {
              if ((UpDownType) command == UpDownType.UP) {
                  sendAction(CMD_NEXT_MOOD);
              } else {
                  sendAction(CMD_PREVIOUS_MOOD);
              }
          } else if (command instanceof DecimalType) {
              int moodId = ((DecimalType) command).intValue();
              if (isMoodOk(moodId)) {
                  sendAction(CMD_CHANGE_TO_MOOD + "/" + moodId);
              }
          }
      }
  
      private State getChannelState() {
          // update the single mood channel state
          if (activeMoods.size() == 1) {
              Integer id = activeMoods.get(0);
              if (isMoodOk(id)) {
                  return new DecimalType(id);
              }
          }
          return UnDefType.UNDEF;
      }
  
      /**
       * Get configured and active moods from a new state value received from the Miniserver
       *
       * @param state state update from the Miniserver
       */
      @Override
      public void onStateChange(LxState state) {
          String stateName = state.getName();
          Object value = state.getStateValue();
          try {
              if (STATE_MOODS_LIST.equals(stateName) && value instanceof String) {
                  onMoodsListChange((String) value);
              } else if (STATE_ACTIVE_MOODS_LIST.equals(stateName) && value instanceof String) {
                  // this state can be received before list of moods, but it contains a valid list of IDs
                  Integer[] array = getGson().fromJson((String) value, Integer[].class);
                  activeMoods = Arrays.asList(array).stream().filter(id -> isMoodOk(id)).collect(Collectors.toList());
                  // update all moods states - this will force update of channels too
                  moodList.values().forEach(mood -> mood.onStateChange(null));
                  // finally we update controller's state based on the active moods list
                  super.onStateChange(state);
              }
          } catch (JsonSyntaxException e) {
              logger.debug("Error parsing state {}: {}", stateName, e.getMessage());
          }
      }
  
      /**
       * Mix a mood into currently active moods.
       *
       * @param moodId ID of the mood to add
       * @throws IOException when something went wrong with communication
       */
      void addMood(Integer moodId) throws IOException {
          if (isMoodOk(moodId)) {
              sendAction(CMD_ADD_MOOD + "/" + moodId);
          }
      }
  
      /**
       * Check if mood is currently active.
       *
       * @param moodId mood ID to check
       * @return true if mood is currently active
       */
      boolean isMoodActive(Integer moodId) {
          return activeMoods.contains(moodId);
      }
  
      /**
       * Check if mood ID is within allowed range
       *
       * @param moodId mood ID to check
       * @return true if mood ID is within allowed range or range is not configured
       */
      boolean isMoodOk(Integer moodId) {
          return moodId != null && moodList.containsKey(moodId);
      }
  
      /**
       * Mix a mood out of currently active moods.
       *
       * @param moodId ID of the mood to remove
       * @throws IOException when something went wrong with communication
       */
      void removeMood(Integer moodId) throws IOException {
          if (isMoodOk(moodId)) {
              sendAction(CMD_REMOVE_MOOD + "/" + moodId);
          }
      }
  
      /**
       * Handles a change in the list of configured moods
       *
       * @param text json structure with new moods
       * @throws JsonSyntaxException error parsing json structure
       */
      private void onMoodsListChange(String text) throws JsonSyntaxException {
          LxControlMood[] array = getGson().fromJson(text, LxControlMood[].class);
          Map<Integer, LxControlMood> newMoodList = new HashMap<>();
          Integer minMoodId = null;
          Integer maxMoodId = null;
          for (LxControlMood mood : array) {
              Integer id = mood.getId();
              if (id != null && mood.getName() != null) {
                  logger.debug("Adding mood (id={}, name={})", id, mood.getName());
                  // mood-UUID = <controller-UUID>-M<mood-ID>
                  LxUuid moodUuid = new LxUuid(getUuid().toString() + "-M" + id);
                  mood.initialize(getConfig(), this, moodUuid);
                  newMoodList.put(id, mood);
                  if (minMoodId == null || minMoodId > id) {
                      minMoodId = id;
                  }
  