/**
 * 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;

import java.util.Date;

import android.util.DisplayMetrics;
import android.view.MotionEvent;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.CatalystInstance;
import com.facebook.react.bridge.ReactTestHelper;
import com.facebook.react.bridge.JavaOnlyArray;
import com.facebook.react.bridge.JavaOnlyMap;
import com.facebook.react.bridge.WritableArray;
import com.facebook.react.common.SystemClock;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.DisplayMetricsHolder;
import com.facebook.react.uimanager.events.Event;
import com.facebook.react.uimanager.events.EventDispatcher;
import com.facebook.react.uimanager.events.RCTEventEmitter;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.Rule;
import org.mockito.ArgumentCaptor;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.powermock.api.mockito.PowerMockito;
import org.powermock.core.classloader.annotations.PrepareForTest;
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.Matchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.when;

@PrepareForTest({Arguments.class, SystemClock.class})
@RunWith(RobolectricTestRunner.class)
@PowerMockIgnore({"org.mockito.*", "org.robolectric.*", "android.*"})
public class RootViewTest {

  @Rule
  public PowerMockRule rule = new PowerMockRule();

  private ReactContext mReactContext;
  private CatalystInstance mCatalystInstanceMock;

  @Before
  public void setUp() {
    final long ts = SystemClock.uptimeMillis();
    PowerMockito.mockStatic(Arguments.class);
    PowerMockito.when(Arguments.createArray()).thenAnswer(new Answer<Object>() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        return new JavaOnlyArray();
      }
    });
    PowerMockito.when(Arguments.createMap()).thenAnswer(new Answer<Object>() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        return new JavaOnlyMap();
      }
    });
    PowerMockito.mockStatic(SystemClock.class);
    PowerMockito.when(SystemClock.uptimeMillis()).thenAnswer(new Answer<Object>() {
      @Override
      public Object answer(InvocationOnMock invocation) throws Throwable {
        return ts;
      }
    });

    mCatalystInstanceMock = ReactTestHelper.createMockCatalystInstance();
    mReactContext = new ReactApplicationContext(RuntimeEnvironment.application);
    mReactContext.initializeWithInstance(mCatalystInstanceMock);
    DisplayMetricsHolder.initDisplayMetricsIfNotInitialized(mReactContext);

    UIManagerModule uiManagerModuleMock = mock(UIManagerModule.class);
    when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
        .thenReturn(uiManagerModuleMock);
  }

  @Test
  public void testTouchEmitter() {
    ReactInstanceManager instanceManager = mock(ReactInstanceManager.class);
    when(instanceManager.getCurrentReactContext()).thenReturn(mReactContext);

    UIManagerModule uiManager = mock(UIManagerModule.class);
    EventDispatcher eventDispatcher = mock(EventDispatcher.class);
    RCTEventEmitter eventEmitterModuleMock = mock(RCTEventEmitter.class);
    when(mCatalystInstanceMock.getNativeModule(UIManagerModule.class))
        .thenReturn(uiManager);
    when(uiManager.getEventDispatcher()).thenReturn(eventDispatcher);

    int rootViewId = 7;

    ReactRootView rootView = new ReactRootView(mReactContext);
    rootView.setId(rootViewId);
    rootView.startReactApplication(instanceManager, "");
    rootView.simulateAttachForTesting();

    long ts = SystemClock.uptimeMillis();

    // Test ACTION_DOWN event
    rootView.onTouchEvent(
        MotionEvent.obtain(100, ts, MotionEvent.ACTION_DOWN, 0, 0, 0));

    ArgumentCaptor<Event> downEventCaptor = ArgumentCaptor.forClass(Event.class);
    verify(eventDispatcher).dispatchEvent(downEventCaptor.capture());
    verifyNoMoreInteractions(eventDispatcher);

    downEventCaptor.getValue().dispatch(eventEmitterModuleMock);

    ArgumentCaptor<JavaOnlyArray> downActionTouchesArgCaptor =
        ArgumentCaptor.forClass(JavaOnlyArray.class);
    verify(eventEmitterModuleMock).receiveTouches(
        eq("topTouchStart"),
        downActionTouchesArgCaptor.capture(),
        any(JavaOnlyArray.class));
    verifyNoMoreInteractions(eventEmitterModuleMock);

    assertThat(downActionTouchesArgCaptor.getValue().size()).isEqualTo(1);
    assertThat(downActionTouchesArgCaptor.getValue().getMap(0)).isEqualTo(
        JavaOnlyMap.of(
            "pageX",
            0.,
            "pageY",
            0.,
            "locationX",
            0.,
            "locationY",
            0.,
            "target",
            rootViewId,
            "timestamp",
            (double) ts,
            "identifier",
            0.));

    // Test ACTION_UP event
    reset(eventEmitterModuleMock, eventDispatcher);

    ArgumentCaptor<Event> upEventCaptor = ArgumentCaptor.forClass(Event.class);
    ArgumentCaptor<JavaOnlyArray> upActionTouchesArgCaptor =
        ArgumentCaptor.forClass(JavaOnlyArray.class);

    rootView.onTouchEvent(
        MotionEvent.obtain(50, ts, MotionEvent.ACTION_UP, 0, 0, 0));
    verify(eventDispatcher).dispatchEvent(upEventCaptor.capture());
    verifyNoMoreInteractions(eventDispatcher);

    upEventCaptor.getValue().dispatch(eventEmitterModuleMock);
    verify(eventEmitterModuleMock).receiveTouches(
        eq("topTouchEnd"),
        upActionTouchesArgCaptor.capture(),
        any(WritableArray.class));
    verifyNoMoreInteractions(eventEmitterModuleMock);

    assertThat(upActionTouchesArgCaptor.getValue().size()).isEqualTo(1);
    assertThat(upActionTouchesArgCaptor.getValue().getMap(0)).isEqualTo(
        JavaOnlyMap.of(
            "pageX",
            0.,
            "pageY",
            0.,
            "locationX",
            0.,
            "locationY",
            0.,
            "target",
            rootViewId,
            "timestamp",
            (double) ts,
            "identifier",
            0.));

    // Test other action
    reset(eventDispatcher);
    rootView.onTouchEvent(
        MotionEvent.obtain(50, new Date().getTime(), MotionEvent.ACTION_HOVER_MOVE, 0, 0, 0));
    verifyNoMoreInteractions(eventDispatcher);
  }
}
