package ch.ihl.runnest.espresso;


import android.os.SystemClock;
import android.support.test.espresso.PerformException;
import android.support.test.espresso.UiController;
import android.support.test.espresso.ViewAction;
import android.support.test.espresso.util.TreeIterables;
import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
import android.view.View;

import com.example.android.multidex.ch.ihl.runnest.AppRunnest.R;

import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.FixMethodOrder;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.runner.Description;
import org.junit.runner.RunWith;
import org.junit.runners.MethodSorters;
import org.junit.runners.model.Statement;

import java.util.concurrent.TimeoutException;

import ch.ihl.runnest.Activities.SideBarActivity;
import ch.ihl.runnest.AppRunnest;
import ch.ihl.runnest.Model.TestUser;

import static android.support.test.espresso.Espresso.onView;
import static android.support.test.espresso.assertion.ViewAssertions.matches;
import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
import static android.support.test.espresso.matcher.ViewMatchers.withId;

@RunWith(AndroidJUnit4.class)
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
abstract class EspressoTest {

    private class Retry implements TestRule {
        private final int retryCount;

        private Retry() {
            this.retryCount = 3;
        }

        public Statement apply(Statement base, Description description) {
            return statement(base, description);
        }

        private Statement statement(final Statement base, final Description description) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    Throwable caughtThrowable = null;
                    for (int i = 0; i < retryCount; i++) {
                        try {
                            base.evaluate();
                            return;
                        } catch (Throwable t) {
                            caughtThrowable = t;
                            System.err.println(description.getDisplayName() + ": run " + (i+1) + " failed");
                        }
                    }
                    System.err.println(description.getDisplayName() + ": giving up after " + retryCount + " failures");
                    throw caughtThrowable;
                }
            };
        }
    }

    @Rule
    public final ActivityTestRule<SideBarActivity> activityRule = new ActivityTestRule<>(
            SideBarActivity.class);

    @Rule
    public Retry retry = new Retry();

    @Before
    public void setUpApp() {
        ((AppRunnest) activityRule.getActivity().getApplication()).setUser(new TestUser());
        ((AppRunnest) activityRule.getActivity().getApplication()).setTestSession(true);
        ((AppRunnest) activityRule.getActivity().getApplication()).setNetworkHandler();
        onView(isRoot()).perform(waitForMatch(withId(R.id.main_layout), EspressoTest.UI_TEST_TIMEOUT));
    }

    static final int UI_TEST_TIMEOUT = 10000;
    static final int FIREBASE_DURATION = 3000;
    static final int TIME_CHALLENGE_DURATION = 15000;
    static final int MOCK_LOCATION_DURATION = 5000;

    public static ViewAction waitForMatch(final Matcher<View> aViewMatcher, final long timeout) {
        return new ViewAction() {
            @Override
            public Matcher<View> getConstraints() {
                return isRoot();
            }

            @Override
            public String getDescription() {
                return "Waiting for view matching " + aViewMatcher;
            }

            @Override
            public void perform(UiController uiController, View view) {
                uiController.loopMainThreadUntilIdle();

                final long startTime = System.currentTimeMillis();
                final long endTime = startTime + timeout;

                do {
                    for (View child : TreeIterables.breadthFirstViewTraversal(view)) {
                        if (aViewMatcher.matches(child)) {
                            // found
                            return;
                        }
                    }


                    uiController.loopMainThreadForAtLeast(50);
                } while (System.currentTimeMillis() < endTime);

                //The action has timed out.
                throw new PerformException.Builder()
                        .withActionDescription(getDescription())
                        .withViewDescription("")
                        .withCause(new TimeoutException())
                        .build();
            }
        };
    }

    public static void tryIsDisplayed(final Matcher<View> viewMatcher, long timeout) {
        final long startTime = System.currentTimeMillis();
        final long endTime = startTime + timeout;

        do {
            try {
                //onView(viewMatcher).perform(click());
                onView(viewMatcher).check(matches(isDisplayed()));
                return;
            } catch (Exception e) {
                SystemClock.sleep(500);
            }
        } while (System.currentTimeMillis() < endTime);

        //Try one last time and throw the Exception
        onView(viewMatcher).check(matches(isDisplayed()));
    }
}