package com.orgzly.android.espresso;

import android.content.pm.ActivityInfo;
import android.support.test.InstrumentationRegistry;
import android.support.test.espresso.DataInteraction;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.ViewInteraction;
import android.support.test.espresso.action.CloseKeyboardAction;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.test.rule.ActivityTestRule;
import android.view.View;
import android.widget.ListView;
import android.widget.NumberPicker;
import android.widget.Spinner;
import com.orgzly.R;
import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.TypeSafeMatcher;

import static android.support.test.espresso.Espresso.*;
import static android.support.test.espresso.Espresso.pressBack;
import static android.support.test.espresso.action.ViewActions.*;
import static android.support.test.espresso.matcher.RootMatchers.isDialog;
import static android.support.test.espresso.matcher.ViewMatchers.*;
import static org.hamcrest.CoreMatchers.allOf;
import static org.hamcrest.Matchers.*;

/*
 * Few espresso-related notes:
 *
 * - closeSoftKeyboardWithDelay() is often used, as on some devices keyboard will cover the view
 *   which is supposed to be clicked next, causing java.lang.SecurityException to get thrown.
 *
 * - replaceText() is preferred over typeText() as it is much faster.
 */
class EspressoUtils {
    static final int[] SETTINGS_REVERSED_NOTE_CLICK_ACTION = { 0, 0 };

    static final int[] SETTINGS_DISPLAY_CONTENT = { 1, 10 };
    static final int[] SETTINGS_STATE_KEYWORDS = { 1, 16 };
    static final int[] SETTINGS_DEFAULT_PRIORITY = { 1, 17 };
    static final int[] SETTINGS_LOWEST_PRIORITY = { 1, 18 };
    static final int[] SETTINGS_NEW_NOTE_STATE = { 1, 20 };
    static final int[] SETTINGS_CREATED_AT = { 1, 22 };

    static final int[] SETTINGS_REPOS = { 4, 0 };
    static final int[] SETTINGS_AUTO_SYNC_TOGGLE = { 4, 1, 1 };
    static final int[] SETTINGS_AUTO_SYNC_NOTE_CREATED = { 4, 1, 2 };

    static final int[] IMPORT_GETTING_STARTED = { 5, 0 };
    static final int[] SETTINGS_CLEAR_DATABASE = { 5, 1 };


    static void tapToSetting(int[] setting) {
        for (int s: setting) {
            onListItem(s).perform(click());
        }
    }

    static void tapLastSetting(int[] setting) {
        onListItem(setting[setting.length-1]).perform(click());
    }

    /**
     */
    @SuppressWarnings("unchecked")
    public static DataInteraction onListItem(int pos) {
        return onData(anything())
                .inAdapterView(allOf(withId(android.R.id.list), isDisplayed()))
                .atPosition(pos);
    }

    static ViewInteraction onSnackbar() {
        return onView(withId(android.support.design.R.id.snackbar_text));
    }

    /*
     * Regular expression matching.
     * https://github.com/hamcrest/JavaHamcrest/issues/65
     */
//    static TypeSafeMatcher<String> withPattern(final String pattern) {
//        checkNotNull(pattern);
//
//        return new TypeSafeMatcher<String>() {
//            @Override
//            public boolean matchesSafely(String s) {
//                return Pattern.compile(pattern).matcher(s).matches();
//            }
//
//            @Override
//            public void describeTo(Description description) {
//                description.appendText("a string matching the pattern '" + pattern + "'");
//            }
//        };
//    }

    /**
     * Item could either be on the action bar (visible) or in the overflow menu.
     */
    static void onActionItemClick(int id, int resourceId) {
        try {
            onView(withId(id)).perform(click());

        } catch (Exception e) {
            e.printStackTrace();

            // Open the overflow menu OR open the options menu,
            // depending on if the device has a hardware or software overflow menu button.
            openActionBarOverflowOrOptionsMenu(InstrumentationRegistry.getTargetContext());
            onView(withText(resourceId)).perform(click());
        }
    }

    static void toLandscape(ActivityTestRule activityRule) {
        toOrientation(activityRule, ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
    }

    static void toPortrait(ActivityTestRule activityRule) {
        toOrientation(activityRule, ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
    }

    private static void toOrientation(ActivityTestRule activityRule, int requestedOrientation) {
        activityRule.getActivity().setRequestedOrientation(requestedOrientation);

        /* Not pretty, but it does seem to fix testFragments from randomly failing. */
        try {
            // Thread.sleep(1000);
            Thread.sleep(750);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    static DataInteraction onSpinnerString(String value) {
        return onData(allOf(instanceOf(String.class), is(value))).inRoot(not(isDialog()));
    }

    static void settingsSetTodoKeywords(String keywords) {
        settingsSetKeywords(R.id.todo_states, keywords);
    }

    static void settingsSetDoneKeywords(String keywords) {
        settingsSetKeywords(R.id.done_states, keywords);
    }

    private static void settingsSetKeywords(int viewId, String keywords) {
        onActionItemClick(R.id.activity_action_settings, R.string.settings);

        EspressoUtils.tapToSetting(EspressoUtils.SETTINGS_STATE_KEYWORDS);

        onView(withId(viewId)).perform(replaceText(keywords), closeSoftKeyboardWithDelay());
        onView(withText(R.string.ok)).perform(click());
        onView(withText(R.string.yes)).perform(click());

        pressBack();
        pressBack();
    }

    static void searchForText(String str) {
        onView(allOf(withId(R.id.activity_action_search), isDisplayed())).perform(click());
        onView(withHint(R.string.search_hint)).perform(replaceText(str), pressKey(66));

        /* TODO: Ugh. */
        try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); }
    }

    /**
     * Give keyboard time to close, to avoid java.lang.SecurityException
     * if hidden button is clicked next.
     */
    static ViewAction closeSoftKeyboardWithDelay() {
        return new ViewAction() {
            /**
             * The delay time to allow the soft keyboard to dismiss.
             */
            private static final long KEYBOARD_DISMISSAL_DELAY_MILLIS = 1000L;

            /**
             * The real {@link CloseKeyboardAction} instance.
             */
            private final ViewAction mCloseSoftKeyboard = new CloseKeyboardAction();

            @Override
            public Matcher<View> getConstraints() {
                return mCloseSoftKeyboard.getConstraints();
            }

            @Override
            public String getDescription() {
                return mCloseSoftKeyboard.getDescription();
            }

            @Override
            public void perform(final UiController uiController, final View view) {
                mCloseSoftKeyboard.perform(uiController, view);
                uiController.loopMainThreadForAtLeast(KEYBOARD_DISMISSAL_DELAY_MILLIS);
            }
        };
    }

    /**
     * Matcher for ListView with exactly specified number of items.
     */
    static TypeSafeMatcher<View> listViewItemCount(final int count) {
        return new TypeSafeMatcher<View>() {
            @Override
            public boolean matchesSafely(View view) {
                return count == ((ListView) view).getCount();
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("a ListView which contains " + count + " item(s)");
            }
        };
    }

    /**
     * Matcher for Spinner with exactly specified number of items.
     */
    static TypeSafeMatcher<View> spinnerItemCount(final int count) {
        return new TypeSafeMatcher<View>() {
            @Override
            public boolean matchesSafely(View view) {
                return count == ((Spinner) view).getCount();
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("a Spinner which contains " + count + " item(s)");
            }
        };
    }

    /**
     * Checks if view has a background set.
     * Used for checking if note is selected.
     */
    static Matcher<View> isHighlighted() {
        return new TypeSafeMatcher<View>() {
            @Override
            public boolean matchesSafely(View view) {
                return view.getBackground() != null;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("a View which is highlighted");
            }
        };
    }

    /**
     * Set value for {@link NumberPicker}
     */
    public static ViewAction setNumber(final int n) {
        return new ViewAction() {
            @Override
            public void perform(UiController uiController, View view) {
                ((NumberPicker) view).setValue(n);
            }

            @Override
            public String getDescription() {
                return "Set NumberPicker value";
            }

            @Override
            public Matcher<View> getConstraints() {
                return ViewMatchers.isAssignableFrom(NumberPicker.class);
            }
        };
    }
}