/**
   * Handles an incoming {@link ZWaveCommandClassValueEvent}. Implement this message in derived
   * classes to convert the value and post an update on the openHAB bus.
   *
   * @param provider the {@link ZWaveBindingProvider} that provides the item
   * @param itemName the name of the item that will receive the event.
   * @param event the received {@link ZWaveCommandClassValueEvent}.
   */
  public void handleEvent(
      ZWaveBindingProvider provider, String itemName, ZWaveCommandClassValueEvent event) {
    ZWaveBindingConfig bindingConfiguration = provider.getZwaveBindingConfig(itemName);
    Item item = provider.getItem(itemName);
    String commandClassName = bindingConfiguration.getArguments().get("command");
    boolean respondToBasic =
        "true".equalsIgnoreCase(bindingConfiguration.getArguments().get("respond_to_basic"));

    logger.trace(
        "Getting converter for item = {}, command class = {}, item command class = {}",
        itemName,
        event.getCommandClass().getLabel(),
        commandClassName);

    if (item == null) return;

    // check whether this item is bound to the right command class.

    if (commandClassName != null
        && !commandClassName.equalsIgnoreCase(event.getCommandClass().getLabel().toLowerCase())
        && !(respondToBasic && event.getCommandClass() == CommandClass.BASIC)) return;

    ZWaveCommandClassConverter<?> converter = this.getConverter(event.getCommandClass());

    if (converter == null) {
      logger.warn(
          "No converter found for command class = {}, ignoring event.",
          event.getCommandClass().toString());
      return;
    }

    converter.handleEvent(event, item, bindingConfiguration.getArguments());
  }
  /**
   * Receives a command from openHAB and translates it to an operation on the Z-Wave network.
   *
   * @param provider the {@link ZWaveBindingProvider} that provides the item
   * @param itemName the name of the item that will receive the event.
   * @param command the received {@link Command}
   */
  @SuppressWarnings("unchecked")
  public void receiveCommand(ZWaveBindingProvider provider, String itemName, Command command) {
    ZWaveBindingConfig bindingConfiguration = provider.getZwaveBindingConfig(itemName);
    ZWaveNode node = this.controller.getNode(bindingConfiguration.getNodeId());
    ZWaveCommandClass commandClass;
    String commandClassName = bindingConfiguration.getArguments().get("command");

    // ignore nodes that are not initialized or dead.
    if (node.getNodeStage() != NodeStage.DONE) return;

    if (commandClassName != null) {
      commandClass =
          node.resolveCommandClass(
              CommandClass.getCommandClass(commandClassName), bindingConfiguration.getEndpoint());

      if (commandClass == null) {
        logger.warn(
            "No command class found for item = {}, command class name = {}, ignoring command.",
            itemName,
            commandClassName);
        return;
      }
    } else {
      commandClass =
          resolveConverter(provider.getItem(itemName), node, bindingConfiguration.getEndpoint());
    }

    if (commandClass == null) {
      logger.warn("No converter found for item = {}, ignoring command.", itemName);
      return;
    }

    ZWaveCommandClassConverter<ZWaveCommandClass> converter =
        (ZWaveCommandClassConverter<ZWaveCommandClass>)
            getConverter(commandClass.getCommandClass());

    if (converter == null) {
      logger.warn("No converter found for item = {}, ignoring command.", itemName);
      return;
    }

    converter.receiveCommand(
        provider.getItem(itemName),
        command,
        node,
        commandClass,
        bindingConfiguration.getEndpoint(),
        bindingConfiguration.getArguments());
  }
  /**
   * Execute refresh method. This method is called every time a binding item is refreshed and the
   * corresponding node should be sent a message.
   *
   * @param provider the {@link ZWaveBindingProvider} that provides the item
   * @param itemName the name of the item to poll.
   * @param forceRefresh indicates that a polling refresh should be forced.
   */
  @SuppressWarnings("unchecked")
  public void executeRefresh(ZWaveBindingProvider provider, String itemName, boolean forceRefresh) {
    ZWaveBindingConfig bindingConfiguration = provider.getZwaveBindingConfig(itemName);
    ZWaveCommandClass commandClass;
    String commandClassName = bindingConfiguration.getArguments().get("command");

    // this binding is configured not to poll.
    if (!forceRefresh
        && bindingConfiguration.getRefreshInterval() != null
        && 0 == bindingConfiguration.getRefreshInterval()) return;

    ZWaveNode node = this.controller.getNode(bindingConfiguration.getNodeId());

    // ignore nodes that are not initialized or dead.
    if (node == null) return;

    if (commandClassName != null) {

      // this is a report item, handle it with the report info converter.
      if (commandClassName.equalsIgnoreCase("info")) {
        infoConverter.executeRefresh(
            provider.getItem(itemName),
            node,
            bindingConfiguration.getEndpoint(),
            bindingConfiguration.getArguments());
        return;
      }

      // ignore nodes that are not initialized or dead.
      if (node.getNodeStage() != NodeStage.DONE) return;

      commandClass =
          node.resolveCommandClass(
              CommandClass.getCommandClass(commandClassName), bindingConfiguration.getEndpoint());

      if (commandClass == null) {
        logger.warn(
            "No command class found for item = {}, command class name = {}, ignoring execute refresh.",
            itemName,
            commandClassName);
        return;
      }
    } else {
      commandClass =
          resolveConverter(provider.getItem(itemName), node, bindingConfiguration.getEndpoint());
    }

    if (commandClass == null) {
      logger.warn("No converter found for item = {}, ignoring execute refresh.", itemName);
      return;
    }

    ZWaveCommandClassConverter<ZWaveCommandClass> converter =
        (ZWaveCommandClassConverter<ZWaveCommandClass>)
            getConverter(commandClass.getCommandClass());

    if (converter == null) {
      logger.warn("No converter found for item = {}, ignoring execute refresh.", itemName);
      return;
    }

    if (bindingConfiguration.getRefreshInterval() == null) {
      bindingConfiguration.setRefreshInterval(converter.getRefreshInterval());

      // this binding is configured not to poll.
      if (!forceRefresh && 0 == bindingConfiguration.getRefreshInterval()) return;
    }

    // not enough time has passed to refresh the item.
    if (!forceRefresh
        && bindingConfiguration.getLastRefreshed() != null
        && (bindingConfiguration.getLastRefreshed().getTime()
                + (bindingConfiguration.getRefreshInterval() * 1000)
            > Calendar.getInstance().getTimeInMillis())) return;

    bindingConfiguration.setLastRefreshed(Calendar.getInstance().getTime());
    converter.executeRefresh(
        node,
        commandClass,
        bindingConfiguration.getEndpoint(),
        bindingConfiguration.getArguments());
  }