/*
 * Copyright (C) 2017 The Android Open Source Project
 *
 * 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.android.systemui.statusbar.phone;

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.res.Configuration;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper.RunWithLooper;
import android.view.MotionEvent;
import android.view.View;

import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

@RunWith(AndroidTestingRunner.class)
@RunWithLooper
@SmallTest
public class NearestTouchFrameTest extends SysuiTestCase {

    private NearestTouchFrame mNearestTouchFrame;

    @Before
    public void setup() {
        Configuration c = new Configuration(mContext.getResources().getConfiguration());
        c.smallestScreenWidthDp = 500;
        mNearestTouchFrame = new NearestTouchFrame(mContext, null, c);
    }

    @Test
    public void testNoActionOnLargeDevices() {
        Configuration c = new Configuration(mContext.getResources().getConfiguration());
        c.smallestScreenWidthDp = 700;
        mNearestTouchFrame = new NearestTouchFrame(mContext, null, c);

        View left = mockViewAt(0, 0, 10, 10);
        View right = mockViewAt(20, 0, 10, 10);

        mNearestTouchFrame.addView(left);
        mNearestTouchFrame.addView(right);
        mNearestTouchFrame.onMeasure(0, 0);

        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
                12 /* x */, 5 /* y */, 0);
        mNearestTouchFrame.onTouchEvent(ev);
        verify(left, never()).onTouchEvent(eq(ev));
        ev.recycle();
    }

    @Test
    public void testInvisibleViews() {
        View left = mockViewAt(0, 0, 10, 10);
        View right = mockViewAt(20, 0, 10, 10);
        when(left.getVisibility()).thenReturn(View.INVISIBLE);

        mNearestTouchFrame.addView(left);
        mNearestTouchFrame.addView(right);
        mNearestTouchFrame.onMeasure(0, 0);

        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
                12 /* x */, 5 /* y */, 0);
        mNearestTouchFrame.onTouchEvent(ev);
        verify(left, never()).onTouchEvent(eq(ev));
        verify(right, never()).onTouchEvent(eq(ev));
        ev.recycle();
    }


    @Test
    public void testNearestView_DetachedViewsExcluded() {
        View left = mockViewAt(0, 0, 10, 10);
        when(left.isAttachedToWindow()).thenReturn(false);
        View right = mockViewAt(20, 0, 10, 10);

        mNearestTouchFrame.addView(left);
        mNearestTouchFrame.addView(right);
        mNearestTouchFrame.onMeasure(0, 0);

        // Would go to left view if attached, but goes to right instead as left should be detached.
        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
                12 /* x */, 5 /* y */, 0);
        mNearestTouchFrame.onTouchEvent(ev);
        verify(right).onTouchEvent(eq(ev));
        ev.recycle();
    }

    @Test
    public void testHorizontalSelection_Left() {
        View left = mockViewAt(0, 0, 10, 10);
        View right = mockViewAt(20, 0, 10, 10);

        mNearestTouchFrame.addView(left);
        mNearestTouchFrame.addView(right);
        mNearestTouchFrame.onMeasure(0, 0);

        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
                12 /* x */, 5 /* y */, 0);
        mNearestTouchFrame.onTouchEvent(ev);
        verify(left).onTouchEvent(eq(ev));
        ev.recycle();
    }

    @Test
    public void testHorizontalSelection_Right() {
        View left = mockViewAt(0, 0, 10, 10);
        View right = mockViewAt(20, 0, 10, 10);

        mNearestTouchFrame.addView(left);
        mNearestTouchFrame.addView(right);
        mNearestTouchFrame.onMeasure(0, 0);

        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
                18 /* x */, 5 /* y */, 0);
        mNearestTouchFrame.onTouchEvent(ev);
        verify(right).onTouchEvent(eq(ev));
        ev.recycle();
    }

    @Test
    public void testVerticalSelection_Top() {
        View top = mockViewAt(0, 0, 10, 10);
        View bottom = mockViewAt(0, 20, 10, 10);

        mNearestTouchFrame.addView(top);
        mNearestTouchFrame.addView(bottom);
        mNearestTouchFrame.onMeasure(0, 0);

        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
                5 /* x */, 12 /* y */, 0);
        mNearestTouchFrame.onTouchEvent(ev);
        verify(top).onTouchEvent(eq(ev));
        ev.recycle();
    }

    @Test
    public void testVerticalSelection_Bottom() {
        View top = mockViewAt(0, 0, 10, 10);
        View bottom = mockViewAt(0, 20, 10, 10);

        mNearestTouchFrame.addView(top);
        mNearestTouchFrame.addView(bottom);
        mNearestTouchFrame.onMeasure(0, 0);

        MotionEvent ev = MotionEvent.obtain(0, 0, 0,
                5 /* x */, 18 /* y */, 0);
        mNearestTouchFrame.onTouchEvent(ev);
        verify(bottom).onTouchEvent(eq(ev));
        ev.recycle();
    }

    private View mockViewAt(int x, int y, int width, int height) {
        View v = spy(new View(mContext));
        doAnswer(invocation -> {
            int[] pos = (int[]) invocation.getArguments()[0];
            pos[0] = x;
            pos[1] = y;
            return null;
        }).when(v).getLocationInWindow(any());
        when(v.isClickable()).thenReturn(true);
        when(v.isAttachedToWindow()).thenReturn(true);

        // Stupid final methods.
        v.setLeft(0);
        v.setRight(width);
        v.setTop(0);
        v.setBottom(height);
        return v;
    }
}