/*
 * Copyright (C) 2015 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 android.widget;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.clearText;
import static android.support.test.espresso.action.ViewActions.click;
import static android.support.test.espresso.action.ViewActions.replaceText;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
import static android.support.test.espresso.matcher.ViewMatchers.withId;
import static android.support.test.espresso.matcher.ViewMatchers.withText;
import static android.widget.espresso.DragHandleUtils.onHandleView;
import static android.widget.espresso.FloatingToolbarEspressoUtils.assertFloatingToolbarContainsItem;
import static android.widget.espresso.FloatingToolbarEspressoUtils.clickFloatingToolbarItem;
import static android.widget.espresso.FloatingToolbarEspressoUtils.sleepForFloatingToolbarPopup;
import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupContainsItem;
import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsDisplayed;
import static android.widget.espresso.SuggestionsPopupwindowUtils.assertSuggestionsPopupIsNotDisplayed;
import static android.widget.espresso.SuggestionsPopupwindowUtils.clickSuggestionsPopupItem;
import static android.widget.espresso.SuggestionsPopupwindowUtils.onSuggestionsPopup;
import static android.widget.espresso.TextViewActions.clickOnTextAtIndex;
import static android.widget.espresso.TextViewActions.longPressOnTextAtIndex;
import static org.hamcrest.Matchers.is;
import android.content.res.TypedArray;
import android.support.test.espresso.NoMatchingViewException;
import android.support.test.espresso.ViewAssertion;
import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.SmallTest;
import android.test.suitebuilder.annotation.Suppress;
import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned;
import android.text.TextPaint;
import android.text.style.SuggestionSpan;
import android.text.style.TextAppearanceSpan;
import android.view.View;

import com.android.frameworks.coretests.R;

/**
 * SuggestionsPopupWindowTest tests.
 *
 * TODO: Add tests for when there are no suggestions
 */
public class SuggestionsPopupWindowTest extends ActivityInstrumentationTestCase2<TextViewActivity> {

    public SuggestionsPopupWindowTest() {
        super(TextViewActivity.class);
    }

    @Override
    protected void setUp() throws Exception {
        super.setUp();
        getActivity();
    }

    private void setSuggestionSpan(SuggestionSpan span, int start, int end) {
        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
        textView.post(
                () -> {
                    final Spannable text = (Spannable) textView.getText();
                    text.setSpan(span, start, end, Spanned.SPAN_INCLUSIVE_INCLUSIVE);
                    Selection.setSelection(text, (start + end) / 2);
                });
        getInstrumentation().waitForIdleSync();
    }

    @SmallTest
    public void testOnTextContextMenuItem() {
        final String text = "abc def ghi";

        onView(withId(R.id.textview)).perform(click());
        onView(withId(R.id.textview)).perform(replaceText(text));

        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);

        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
        textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE));
        getInstrumentation().waitForIdleSync();

        assertSuggestionsPopupIsDisplayed();
    }

    @SmallTest
    public void testSelectionActionMode() {
        final String text = "abc def ghi";

        onView(withId(R.id.textview)).perform(click());
        onView(withId(R.id.textview)).perform(replaceText(text));

        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);

        onView(withId(R.id.textview)).perform(longPressOnTextAtIndex(text.indexOf('e')));
        sleepForFloatingToolbarPopup();
        assertFloatingToolbarContainsItem(
                getActivity().getString(com.android.internal.R.string.replace));
        sleepForFloatingToolbarPopup();
        clickFloatingToolbarItem(
                getActivity().getString(com.android.internal.R.string.replace));

        assertSuggestionsPopupIsDisplayed();
    }

    @SmallTest
    public void testInsertionActionMode() {
        final String text = "abc def ghi";

        onView(withId(R.id.textview)).perform(click());
        onView(withId(R.id.textview)).perform(replaceText(text));

        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);

        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
        onHandleView(com.android.internal.R.id.insertion_handle).perform(click());
        sleepForFloatingToolbarPopup();
        assertFloatingToolbarContainsItem(
                getActivity().getString(com.android.internal.R.string.replace));
        clickFloatingToolbarItem(
                getActivity().getString(com.android.internal.R.string.replace));

        assertSuggestionsPopupIsDisplayed();
    }

    private void showSuggestionsPopup() {
        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);
        textView.post(() -> textView.onTextContextMenuItem(TextView.ID_REPLACE));
        getInstrumentation().waitForIdleSync();
        assertSuggestionsPopupIsDisplayed();
    }

    @SmallTest
    public void testSuggestionItems() {
        final String text = "abc def ghi";

        onView(withId(R.id.textview)).perform(click());
        onView(withId(R.id.textview)).perform(replaceText(text));

        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_AUTO_CORRECTION);
        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);

        showSuggestionsPopup();

        assertSuggestionsPopupIsDisplayed();
        assertSuggestionsPopupContainsItem("DEF");
        assertSuggestionsPopupContainsItem("Def");
        assertSuggestionsPopupContainsItem(
                getActivity().getString(com.android.internal.R.string.delete));

        // Select an item.
        clickSuggestionsPopupItem("DEF");
        assertSuggestionsPopupIsNotDisplayed();
        onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi")));

        showSuggestionsPopup();
        assertSuggestionsPopupIsDisplayed();
        assertSuggestionsPopupContainsItem("def");
        assertSuggestionsPopupContainsItem("Def");
        assertSuggestionsPopupContainsItem(
                getActivity().getString(com.android.internal.R.string.delete));

        // Delete
        clickSuggestionsPopupItem(
                getActivity().getString(com.android.internal.R.string.delete));
        assertSuggestionsPopupIsNotDisplayed();
        onView(withId(R.id.textview)).check(matches(withText("abc ghi")));
    }

    @SmallTest
    public void testMisspelled() {
        final String text = "abc def ghi";

        onView(withId(R.id.textview)).perform(click());
        onView(withId(R.id.textview)).perform(replaceText(text));

        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
                new String[]{"DEF", "Def"}, SuggestionSpan.FLAG_MISSPELLED);
        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);

        showSuggestionsPopup();

        assertSuggestionsPopupIsDisplayed();
        assertSuggestionsPopupContainsItem("DEF");
        assertSuggestionsPopupContainsItem("Def");
        assertSuggestionsPopupContainsItem(
                getActivity().getString(com.android.internal.R.string.addToDictionary));
        assertSuggestionsPopupContainsItem(
                getActivity().getString(com.android.internal.R.string.delete));

        // Click "Add to dictionary".
        clickSuggestionsPopupItem(
                getActivity().getString(com.android.internal.R.string.addToDictionary));
        // TODO: Check if add to dictionary dialog is displayed.
    }

    @SmallTest
    public void testEasyCorrect() {
        final String text = "abc def ghi";

        onView(withId(R.id.textview)).perform(click());
        onView(withId(R.id.textview)).perform(replaceText(text));

        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
                new String[]{"DEF", "Def"},
                SuggestionSpan.FLAG_EASY_CORRECT | SuggestionSpan.FLAG_MISSPELLED);
        setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);

        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));

        assertSuggestionsPopupIsDisplayed();
        assertSuggestionsPopupContainsItem("DEF");
        assertSuggestionsPopupContainsItem("Def");
        assertSuggestionsPopupContainsItem(
                getActivity().getString(com.android.internal.R.string.delete));

        // Select an item.
        clickSuggestionsPopupItem("DEF");
        assertSuggestionsPopupIsNotDisplayed();
        onView(withId(R.id.textview)).check(matches(withText("abc DEF ghi")));

        onView(withId(R.id.textview)).perform(clickOnTextAtIndex(text.indexOf('e')));
        assertSuggestionsPopupIsNotDisplayed();

        showSuggestionsPopup();
        assertSuggestionsPopupIsDisplayed();
        assertSuggestionsPopupContainsItem("def");
        assertSuggestionsPopupContainsItem("Def");
        assertSuggestionsPopupContainsItem(
                getActivity().getString(com.android.internal.R.string.delete));
    }

    @SmallTest
    public void testTextAppearanceInSuggestionsPopup() {
        final String text = "abc def ghi";

        final String[] singleWordCandidates = {"DEF", "Def"};
        final SuggestionSpan suggestionSpan = new SuggestionSpan(getActivity(),
                singleWordCandidates, SuggestionSpan.FLAG_MISSPELLED);
        final String[] multiWordCandidates = {"ABC DEF GHI", "Abc Def Ghi"};
        final SuggestionSpan multiWordSuggestionSpan = new SuggestionSpan(getActivity(),
                multiWordCandidates, SuggestionSpan.FLAG_MISSPELLED);

        final TypedArray array =
                getActivity().obtainStyledAttributes(com.android.internal.R.styleable.Theme);
        final int id = array.getResourceId(
                com.android.internal.R.styleable.Theme_textEditSuggestionHighlightStyle, 0);
        array.recycle();
        final TextAppearanceSpan expectedSpan = new TextAppearanceSpan(getActivity(), id);
        final TextPaint tmpTp = new TextPaint();
        expectedSpan.updateDrawState(tmpTp);
        final int expectedHighlightTextColor = tmpTp.getColor();
        final float expectedHighlightTextSize = tmpTp.getTextSize();
        final TextView textView = (TextView) getActivity().findViewById(R.id.textview);

        // In this test, the SuggestionsPopupWindow looks like
        //   abc def ghi
        // -----------------
        // | abc *DEF* ghi |
        // | abc *Def* ghi |
        // | *ABC DEF GHI* |
        // | *Abc Def Ghi* |
        // -----------------
        // | DELETE        |
        // -----------------
        // *XX* means that XX is highlighted.
        for (int i = 0; i < 2; i++) {
            onView(withId(R.id.textview)).perform(click());
            onView(withId(R.id.textview)).perform(replaceText(text));
            setSuggestionSpan(suggestionSpan, text.indexOf('d'), text.indexOf('f') + 1);
            setSuggestionSpan(multiWordSuggestionSpan, 0, text.length());

            showSuggestionsPopup();
            assertSuggestionsPopupIsDisplayed();
            assertSuggestionsPopupContainsItem("abc DEF ghi");
            assertSuggestionsPopupContainsItem("abc Def ghi");
            assertSuggestionsPopupContainsItem("ABC DEF GHI");
            assertSuggestionsPopupContainsItem("Abc Def Ghi");
            assertSuggestionsPopupContainsItem(
                    getActivity().getString(com.android.internal.R.string.delete));

            onSuggestionsPopup().check(new ViewAssertion() {
                @Override
                public void check(View view, NoMatchingViewException e) {
                    final ListView listView = (ListView) view.findViewById(
                            com.android.internal.R.id.suggestionContainer);
                    assertNotNull(listView);
                    final int childNum = listView.getChildCount();
                    assertEquals(singleWordCandidates.length + multiWordCandidates.length,
                            childNum);

                    for (int j = 0; j < childNum; j++) {
                        final TextView suggestion = (TextView) listView.getChildAt(j);
                        assertNotNull(suggestion);
                        final Spanned spanned = (Spanned) suggestion.getText();
                        assertNotNull(spanned);

                        // Check that the suggestion item order is kept.
                        final String expectedText;
                        if (j < singleWordCandidates.length) {
                            expectedText = "abc " + singleWordCandidates[j] + " ghi";
                        } else {
                            expectedText = multiWordCandidates[j - singleWordCandidates.length];
                        }
                        assertEquals(expectedText, spanned.toString());

                        // Check that the text is highlighted with correct color and text size.
                        final TextAppearanceSpan[] taSpan = spanned.getSpans(
                                text.indexOf('d'), text.indexOf('f') + 1, TextAppearanceSpan.class);
                        assertEquals(1, taSpan.length);
                        TextPaint tp = new TextPaint();
                        taSpan[0].updateDrawState(tp);
                        assertEquals(expectedHighlightTextColor, tp.getColor());
                        assertEquals(expectedHighlightTextSize, tp.getTextSize());

                        // Check the correct part of the text is highlighted.
                        final int expectedStart;
                        final int expectedEnd;
                        if (j < singleWordCandidates.length) {
                            expectedStart = text.indexOf('d');
                            expectedEnd = text.indexOf('f') + 1;
                        } else {
                            expectedStart = 0;
                            expectedEnd = text.length();
                        }
                        assertEquals(expectedStart, spanned.getSpanStart(taSpan[0]));
                        assertEquals(expectedEnd, spanned.getSpanEnd(taSpan[0]));
                    }
                }
            });
            pressBack();
            onView(withId(R.id.textview))
                    .inRoot(withDecorView(is(getActivity().getWindow().getDecorView())))
                    .perform(clearText());
        }
    }
}
