/**
   * Remove given listener from given element for given event type.
   *
   * @param elem Element
   * @param eventType EventType
   * @param listener IDapEventListener
   */
  public void remove(
      final BaseHtmlElement elem, final EventType eventType, final IDapEventListener listener) {

    EventHandlerContainer container = HtmlCtx.ctx().getEventHandlerContainer();
    String elemId = DapDomHelper.getId(elem);

    // Active Mode
    if (DapCtx.ctx().isActiveMode()) {
      List<IDapEventListener> listeners = getListeners(elemId, true);
      if (listeners.contains(listener)) {
        listeners.remove(listener);
      }
      container.removeHandler(
          elem, listener.getEventHandlerAdapter(elemId, listeners.indexOf(listener)));
      return;
    }

    // Translate|Web Mode
    if (listener.getProxyEventHandlers() != null) {
      IJsFunc handler = getHandler(listener.getProxyEventHandlers(), eventType);
      removeHandler(elem, eventType, handler, container);
    }

    throw new DsfRuntimeException(
        "js proxy handlers are required for non-active mode:"
            + "mode="
            + DapCtx.ctx().getExeMode()
            + ", elem="
            + elemId
            + ", eventType="
            + eventType
            + ", listenerType="
            + listener.getClass().getName());
  }
  /**
   * Add given listener to the element with given id for given event type.
   *
   * @param elemId String
   * @param eventType EventType
   * @param listener IDapEventListener
   */
  public void addListener(
      final String elemId, final EventType eventType, final IDapEventListener listener) {

    if (elemId == null) {
      throw new AssertionError("elemId cannot be null");
    }

    if (eventType == null) {
      throw new AssertionError("eventType cannot be null");
    }

    if (listener == null) {
      throw new AssertionError("listener cannot be null");
    }

    EventHandlerContainer container = HtmlCtx.ctx().getEventHandlerContainer();

    // Active Mode
    if (DapCtx.ctx().isActiveMode()) {
      int index = add(elemId, listener);
      ISimpleJsEventHandler handler = listener.getEventHandlerAdapter(elemId, index);
      if (BODY.equals(elemId)) {
        container.add(HtmlTypeEnum.BODY, eventType, handler);
      } else {
        container.add(elemId, eventType, handler);
      }
      return;
    }

    // Translate|Web Mode
    Map<EventType, IJsFunc> handlers = listener.getProxyEventHandlers();
    if (handlers != null) {
      IJsFunc handler = getHandler(handlers, eventType);
      if (handler != null) {
        container.add(elemId, eventType, listener.getEventHandlerAdapter(handler));
        return;
      }
    }

    throw new DsfRuntimeException(
        "js proxy handlers are required for non-active modes:"
            + "mode="
            + DapCtx.ctx().getExeMode()
            + ", elem="
            + elemId
            + ", eventType="
            + eventType
            + ", listenerType="
            + listener.getClass().getName());
  }
  private void removeHandler(
      final BaseHtmlElement elem,
      final EventType eventType,
      final IJsFunc handler,
      final EventHandlerContainer container) {

    if (handler == null) {
      return;
    }

    List<EventsToHandlerPair> pairs = container.getElements().get(elem);
    DapEventHandlerAdapter adapter;
    if (pairs == null) {
      return;
    }

    for (EventsToHandlerPair pair : pairs) {
      if (pair.getEventType() != eventType) {
        continue;
      }
      if (!(pair.getHandler() instanceof DapEventHandlerAdapter)) {
        continue;
      }
      adapter = (DapEventHandlerAdapter) pair.getHandler();
      if (adapter == null || adapter.getJsFunc() != handler) {
        continue;
      }
      pairs.remove(pair);
      return;
    }
  }