// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.content.browser.input;

import android.view.inputmethod.CursorAnchorInfo;
import android.view.inputmethod.InputConnection;

import androidx.test.filters.MediumTest;

import org.hamcrest.Matchers;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;

import org.chromium.base.test.util.Batch;
import org.chromium.base.test.util.Criteria;
import org.chromium.base.test.util.CriteriaHelper;
import org.chromium.base.test.util.DisabledTest;
import org.chromium.base.test.util.Feature;
import org.chromium.content_public.browser.test.ContentJUnit4ClassRunner;
import org.chromium.content_public.browser.test.util.TestThreadUtils;

import java.util.concurrent.Callable;

/**
 * Integration tests for text input for Android L (or above) features.
 */
@RunWith(ContentJUnit4ClassRunner.class)
@Batch(ImeTest.IME_BATCH)
public class ImeLollipopTest {
    @Rule
    public ImeActivityTestRule mRule = new ImeActivityTestRule();

    @Before
    public void setUp() throws Exception {
        mRule.setUpForUrl(ImeActivityTestRule.INPUT_FORM_HTML);
    }

    @After
    public void tearDown() throws Exception {
        mRule.getActivity().finish();
    }

    @Test
    @MediumTest
    @Feature({"TextInput"})
    @DisabledTest(message = "crbug.com/1153705")
    public void testUpdateCursorAnchorInfo() throws Throwable {
        requestCursorUpdates(InputConnection.CURSOR_UPDATE_MONITOR);

        // In "MONITOR" mode, the change should be notified.
        mRule.setComposingText("ab", 1);
        waitForUpdateCursorAnchorInfoComposingText("ab");

        CursorAnchorInfo info = mRule.getInputMethodManagerWrapper().getLastCursorAnchorInfo();
        Assert.assertEquals(0, info.getComposingTextStart());
        Assert.assertNotNull(info.getCharacterBounds(0));
        Assert.assertNotNull(info.getCharacterBounds(1));
        Assert.assertNull(info.getCharacterBounds(2));

        // Should be notified not only once. Further change should be sent, too.
        mRule.setComposingText("abcd", 1);
        waitForUpdateCursorAnchorInfoComposingText("abcd");

        info = mRule.getInputMethodManagerWrapper().getLastCursorAnchorInfo();
        Assert.assertEquals(0, info.getComposingTextStart());
        Assert.assertNotNull(info.getCharacterBounds(0));
        Assert.assertNotNull(info.getCharacterBounds(1));
        Assert.assertNotNull(info.getCharacterBounds(2));
        Assert.assertNotNull(info.getCharacterBounds(3));
        Assert.assertNull(info.getCharacterBounds(4));

        // In "IMMEDIATE" mode, even when there's no change, we should be notified at least once.
        TestThreadUtils.runOnUiThreadBlocking(
                () -> { mRule.getInputMethodManagerWrapper().clearLastCursorAnchorInfo(); });
        requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE);
        waitForUpdateCursorAnchorInfoComposingText("abcd");

        mRule.setComposingText("abcde", 2);
        requestCursorUpdates(InputConnection.CURSOR_UPDATE_IMMEDIATE);
        waitForUpdateCursorAnchorInfoComposingText("abcde");
    }

    private void requestCursorUpdates(final int cursorUpdateMode) throws Exception {
        final InputConnection connection = mRule.getConnection();
        mRule.runBlockingOnImeThread(new Callable<Void>() {
            @Override
            public Void call() {
                connection.requestCursorUpdates(cursorUpdateMode);
                return null;
            }
        });
    }

    private void waitForUpdateCursorAnchorInfoComposingText(final String expected) {
        CriteriaHelper.pollUiThread(() -> {
            CursorAnchorInfo info = mRule.getInputMethodManagerWrapper().getLastCursorAnchorInfo();
            if (info != null) {
                Criteria.checkThat(info.getComposingText(), Matchers.notNullValue());
            }

            String actual = (info == null ? "" : info.getComposingText().toString());
            Criteria.checkThat(actual, Matchers.is(expected));
        });
    }
}
