/*
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.facebook.litho;

import androidx.annotation.VisibleForTesting;
import androidx.collection.SparseArrayCompat;
import com.facebook.infer.annotation.ThreadSafe;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/** Manages the mapping of event handlers to dispatchers. */
@ThreadSafe
public class EventHandlersController {

  private final Map<String, EventHandlersWrapper> mEventHandlers = new HashMap<>();

  /**
   * Update all the known event handlers for a dispatcher with the given key with the new dispacher
   * instance.
   */
  public synchronized void bindEventHandlers(
      ComponentContext c, HasEventDispatcher dispatcher, String globalKey) {
    if (globalKey == null) {
      return;
    }

    final EventHandlersWrapper eventHandlers = mEventHandlers.get(globalKey);

    if (eventHandlers == null) {
      return;
    }

    // Mark that the list of event handlers for this component is still needed.
    eventHandlers.mUsedInCurrentTree = true;
    eventHandlers.bindToDispatcher(c, dispatcher);
  }

  /** Remove entries for dispatchers that are no longer present in the tree. */
  public synchronized void clearUnusedEventHandlers() {
    final Iterator iterator = mEventHandlers.keySet().iterator();
    while (iterator.hasNext()) {
      final EventHandlersWrapper eventHandlersWrapper = mEventHandlers.get(iterator.next());

      if (eventHandlersWrapper == null || !eventHandlersWrapper.mUsedInCurrentTree) {
        iterator.remove();
      } else {
        eventHandlersWrapper.mUsedInCurrentTree = false;
      }
    }
  }

  /** Map the given event handler to a dispatcher with the given global key. */
  public synchronized void recordEventHandler(String globalKey, EventHandler eventHandler) {
    if (globalKey == null) {
      return;
    }

    EventHandlersWrapper eventHandlers = mEventHandlers.get(globalKey);

    if (eventHandlers == null) {
      eventHandlers = new EventHandlersWrapper();
      mEventHandlers.put(globalKey, eventHandlers);
    }

    eventHandlers.addEventHandler(eventHandler);
  }

  @VisibleForTesting
  public synchronized Map<String, EventHandlersWrapper> getEventHandlers() {
    return mEventHandlers;
  }

  /** Used to hold a dispatcher's event handlers. */
  @VisibleForTesting
  public static class EventHandlersWrapper {

    private final SparseArrayCompat<EventHandler> mEventHandlers = new SparseArrayCompat<>();

    boolean mUsedInCurrentTree;

    void addEventHandler(EventHandler eventHandler) {
      mEventHandlers.put(eventHandler.id, eventHandler);
    }

    void bindToDispatcher(ComponentContext c, HasEventDispatcher dispatcher) {
      for (int i = 0, size = mEventHandlers.size(); i < size; i++) {
        final EventHandler eventHandler = mEventHandlers.valueAt(i);
        eventHandler.mHasEventDispatcher = dispatcher;

        // Params should only be null for tests
        if (eventHandler.params != null) {
          eventHandler.params[0] = c;
        }
      }
    }

    @VisibleForTesting
    public SparseArrayCompat<EventHandler> getEventHandlers() {
      return mEventHandlers;
    }
  }
}

@@ -640,16 +640,53 @@
 litho;%0A%0A
+import androidx.annotation.Nullable;%0A
 import a
@@ -1668,24 +1668,27 @@
 andlers.bind
+All
 ToDispatcher
@@ -1688,19 +1688,16 @@
 patcher(
-c, 
 dispatch
@@ -1698,16 +1698,19 @@
 spatcher
+, c
 );%0A  %7D%0A%0A
@@ -2495,32 +2495,38 @@
 er eventHandlers
+ForKey
  = mEventHandler
@@ -2534,32 +2534,77 @@
 .get(globalKey);
+%0A    EventHandler%3C?%3E prevEventHandler = null;
 %0A%0A    if (eventH
@@ -2602,32 +2602,38 @@
 f (eventHandlers
+ForKey
  == null) %7B%0A    
@@ -2643,24 +2643,30 @@
 ventHandlers
+ForKey
  = new Event
@@ -2733,24 +2733,30 @@
 Handlers
+ForKey
 );%0A    %7D
 %0A%0A    ev
@@ -2751,58 +2751,742 @@
    %7D
-%0A%0A    eventHandlers.addEventHandler(eventHandler);
+ else %7B%0A      prevEventHandler = eventHandlersForKey.getEventHandlers().get(eventHandler.id);%0A    %7D%0A%0A    if (eventHandler.isEquivalentTo(prevEventHandler)) %7B%0A      final ComponentContext c =%0A          eventHandler.params == null ? null : (ComponentContext) eventHandler.params%5B0%5D;%0A      bindEventHandlerToDispatcher(prevEventHandler, eventHandler.mHasEventDispatcher, c);%0A    %7D else %7B%0A      eventHandlersForKey.addEventHandler(eventHandler);%0A    %7D%0A  %7D%0A%0A  private static void bindEventHandlerToDispatcher(%0A      EventHandler%3C?%3E eventHandler, HasEventDispatcher dispatcher, @Nullable ComponentContext c) %7B%0A    eventHandler.mHasEventDispatcher = dispatcher;%0A%0A    if (eventHandler.params != null) %7B%0A      eventHandler.params%5B0%5D = c;%0A    %7D
 %0A  %7D
@@ -3996,16 +3996,19 @@
 oid bind
+All
 ToDispat
@@ -4012,36 +4012,16 @@
 patcher(
-ComponentContext c, 
 HasEvent
@@ -4041,16 +4041,36 @@
 spatcher
+, ComponentContext c
 ) %7B%0A    
@@ -4203,33 +4203,37 @@
 eAt(i);%0A        
-e
+bindE
 ventHandler.mHas
@@ -4231,182 +4231,50 @@
 dler
-.mHasEventDispatcher = dispatcher;%0A%0A        // Params should only be null for tests%0A        if (eventHandler.params != null) %7B%0A          eventHandler.params%5B0%5D = c;%0A        %7D
+ToDispatcher(eventHandler, dispatcher, c);
 %0A   

Fix stale EventHandler bug