/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

package com.facebook.react.uimanager;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.common.MapBuilder;
import com.facebook.react.uimanager.events.EventDispatcher;

import org.fest.assertions.data.MapEntry;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.powermock.core.classloader.annotations.PowerMockIgnore;
import org.powermock.modules.junit4.rule.PowerMockRule;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;

import static org.fest.assertions.api.Assertions.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
public class UIManagerModuleConstantsTest {

  @Rule
  public PowerMockRule rule = new PowerMockRule();

  private static final String CUSTOM_BUBBLING_EVENT_TYPES = "customBubblingEventTypes";
  private static final String CUSTOM_DIRECT_EVENT_TYPES = "customDirectEventTypes";

  private static final Map TWIRL_BUBBLING_EVENT_MAP = MapBuilder.of(
      "phasedRegistrationNames",
      MapBuilder.of(
          "bubbled",
          "onTwirl",
          "captured",
          "onTwirlCaptured"));
  private static final Map TWIRL_DIRECT_EVENT_MAP = MapBuilder.of("registrationName", "onTwirl");

  private ReactApplicationContext mReactContext;
  private UIImplementationProvider mUIImplementationProvider;

  @Before
  public void setUp() {
    mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
    mUIImplementationProvider = mock(UIImplementationProvider.class);
    when(mUIImplementationProvider.createUIImplementation(
      any(ReactApplicationContext.class),
      any(List.class),
      any(EventDispatcher.class)))
      .thenReturn(mock(UIImplementation.class));
  }

  @Test
  public void testNoCustomConstants() {
    List<ViewManager> viewManagers = Arrays.asList(mock(ViewManager.class));
    UIManagerModule uiManagerModule = new UIManagerModule(
      mReactContext,
      viewManagers,
      mUIImplementationProvider,
      false);
    Map<String, Object> constants = uiManagerModule.getConstants();
    assertThat(constants)
        .containsKey(CUSTOM_BUBBLING_EVENT_TYPES)
        .containsKey(CUSTOM_DIRECT_EVENT_TYPES)
        .containsKey("Dimensions");
  }

  @Test
  public void testCustomBubblingEvents() {
    ViewManager mockViewManager = mock(ViewManager.class);
    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
    when(mockViewManager.getExportedCustomBubblingEventTypeConstants())
        .thenReturn(MapBuilder.of("onTwirl", TWIRL_BUBBLING_EVENT_MAP));
    UIManagerModule uiManagerModule = new UIManagerModule(
      mReactContext,
      viewManagers,
      mUIImplementationProvider,
      false);
    Map<String, Object> constants = uiManagerModule.getConstants();
    assertThat((Map) constants.get(CUSTOM_BUBBLING_EVENT_TYPES))
        .contains(MapEntry.entry("onTwirl", TWIRL_BUBBLING_EVENT_MAP))
        .containsKey("topChange");
  }

  @Test
  public void testCustomDirectEvents() {
    ViewManager mockViewManager = mock(ViewManager.class);
    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
    when(mockViewManager.getExportedCustomDirectEventTypeConstants())
        .thenReturn(MapBuilder.of("onTwirl", TWIRL_DIRECT_EVENT_MAP));
    UIManagerModule uiManagerModule = new UIManagerModule(
      mReactContext,
      viewManagers,
      mUIImplementationProvider,
      false);
    Map<String, Object> constants = uiManagerModule.getConstants();
    assertThat((Map) constants.get(CUSTOM_DIRECT_EVENT_TYPES))
        .contains(MapEntry.entry("onTwirl", TWIRL_DIRECT_EVENT_MAP))
        .containsKey("topLoadingStart");
  }

  @Test
  public void testCustomViewConstants() {
    ViewManager mockViewManager = mock(ViewManager.class);
    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
    when(mockViewManager.getName()).thenReturn("RedPandaPhotoOfTheDayView");
    when(mockViewManager.getExportedViewConstants())
        .thenReturn(MapBuilder.of("PhotoSizeType", MapBuilder.of("Small", 1, "Large", 2)));
    UIManagerModule uiManagerModule = new UIManagerModule(
      mReactContext,
      viewManagers,
      mUIImplementationProvider,
      false);
    Map<String, Object> constants = uiManagerModule.getConstants();
    assertThat(constants).containsKey("RedPandaPhotoOfTheDayView");
    assertThat((Map) constants.get("RedPandaPhotoOfTheDayView")).containsKey("Constants");
    assertThat((Map) valueAtPath(constants, "RedPandaPhotoOfTheDayView", "Constants"))
        .containsKey("PhotoSizeType");
  }

  @Test
  public void testNativeProps() {
    ViewManager mockViewManager = mock(ViewManager.class);
    List<ViewManager> viewManagers = Arrays.asList(mockViewManager);
    when(mockViewManager.getName()).thenReturn("SomeView");
    when(mockViewManager.getNativeProps())
        .thenReturn(MapBuilder.of("fooProp", "number"));
    UIManagerModule uiManagerModule = new UIManagerModule(
      mReactContext,
      viewManagers,
      mUIImplementationProvider,
      false);
    Map<String, Object> constants = uiManagerModule.getConstants();
    assertThat((String) valueAtPath(constants, "SomeView", "NativeProps", "fooProp"))
        .isEqualTo("number");
  }

  @Test
  public void testMergeConstants() {
    ViewManager managerX = mock(ViewManager.class);
    when(managerX.getExportedCustomDirectEventTypeConstants()).thenReturn(MapBuilder.of(
        "onTwirl",
        MapBuilder.of(
            "registrationName",
            "onTwirl",
            "keyToOverride",
            "valueX",
            "mapToMerge",
            MapBuilder.of("keyToOverride", "innerValueX", "anotherKey", "valueX"))));

    ViewManager managerY = mock(ViewManager.class);
    when(managerY.getExportedCustomDirectEventTypeConstants()).thenReturn(MapBuilder.of(
        "onTwirl",
        MapBuilder.of(
            "extraKey",
            "extraValue",
            "keyToOverride",
            "valueY",
            "mapToMerge",
            MapBuilder.of("keyToOverride", "innerValueY", "extraKey", "valueY"))));

    List<ViewManager> viewManagers = Arrays.asList(managerX, managerY);
    UIManagerModule uiManagerModule = new UIManagerModule(
      mReactContext,
      viewManagers,
      mUIImplementationProvider,
      false);
    Map<String, Object> constants = uiManagerModule.getConstants();
    assertThat((Map) constants.get(CUSTOM_DIRECT_EVENT_TYPES)).containsKey("onTwirl");

    Map twirlMap = (Map) valueAtPath(constants, CUSTOM_DIRECT_EVENT_TYPES, "onTwirl");
    assertThat(twirlMap)
        .contains(MapEntry.entry("registrationName", "onTwirl"))
        .contains(MapEntry.entry("keyToOverride", "valueY"))
        .contains(MapEntry.entry("extraKey", "extraValue"))
        .containsKey("mapToMerge");

    Map mapToMerge = (Map) valueAtPath(twirlMap, "mapToMerge");
    assertThat(mapToMerge)
        .contains(MapEntry.entry("keyToOverride", "innerValueY"))
        .contains(MapEntry.entry("anotherKey", "valueX"))
        .contains(MapEntry.entry("extraKey", "valueY"));
  }

  private static Object valueAtPath(Map nestedMap, String... keyPath) {
    assertThat(keyPath).isNotEmpty();
    Object value = nestedMap;
    for (String key : keyPath) {
      assertThat(value).isInstanceOf(Map.class);
      nestedMap = (Map) value;
      assertThat(nestedMap).containsKey(key);
      value = nestedMap.get(key);
    }
    return value;
  }
}
