package de.nenick.espressomacchiato.matchers.support;

import android.content.res.Resources;
import android.support.annotation.Nullable;
import android.support.test.espresso.matcher.ViewMatchers;
import android.support.v7.widget.RecyclerView;
import android.view.View;

import junit.framework.AssertionFailedError;

import org.hamcrest.Description;
import org.hamcrest.Matcher;
import org.hamcrest.Matchers;
import org.hamcrest.TypeSafeMatcher;

public class EspRecyclerViewMatcher {

    private final Matcher<View> recyclerViewMatcher;
    private boolean allowItemNotFound = false;
    private boolean failWhenViewExist = false;

    public static EspRecyclerViewMatcher withRecyclerView(Matcher<View> recyclerViewMatcher) {
        return new EspRecyclerViewMatcher(recyclerViewMatcher);
    }

    public EspRecyclerViewMatcher(Matcher<View> recyclerViewMatcher) {
        this.recyclerViewMatcher = recyclerViewMatcher;
    }

    public EspRecyclerViewMatcher ignoreItemNotFound(boolean allow) {
        allowItemNotFound = allow;
        return this;
    }

    public Matcher<View> atIndex(final int position) {
        return atChildIndexOnView(position, null);
    }

    public Matcher<View> atChildIndexOnView(final int index, @Nullable final Matcher<View> childMatcher) {

        return new TypeSafeMatcher<View>() {
            Resources resources = null;
            View itemView;

            public void describeTo(Description description) {
                recyclerViewMatcher.describeTo(description);
                if (childMatcher != null) {
                    childMatcher.describeTo(description);
                }
            }

            public boolean matchesSafely(View view) {
                this.resources = view.getResources();

                if (itemView == null && (itemView = findExpectedItemView(view, index)) == null) {
                    return false;
                }

                if (childMatcher == null) {
                    return view == itemView;
                }

                Matcher<View> descendantOfSelectedItem = ViewMatchers.isDescendantOfA(Matchers.equalTo(itemView));
                return Matchers.allOf(descendantOfSelectedItem, childMatcher).matches(view);
            }

            /**
             * Checks all current displayed items if they match with expected item.
             */
            private
            @Nullable
            View findExpectedItemView(View possibleRecyclerView, int expectedAdapterIndex) {
                // we need the recycler view to access all current displayed items
                if (!recyclerViewMatcher.matches(possibleRecyclerView)) {
                    return null;
                }
                RecyclerView recyclerView = (RecyclerView) possibleRecyclerView;

                // compare children current adapter index with expected adapter index
                for (int i = 0; i < recyclerView.getChildCount(); i++) {
                    View recyclerViewChild = recyclerView.getChildAt(i);
                    if (recyclerView.getChildAdapterPosition(recyclerViewChild) == expectedAdapterIndex) {
                        if(failWhenViewExist) {
                            throw new AssertionFailedError("Requested item should be hidden but is displayed.");
                        }

                        return recyclerViewChild;
                    }
                }

                if (allowItemNotFound) {
                    return null;
                }

                throw new AssertionFailedError("Requested item is currently not displayed. Try first scrollTo() to make the item visible.");

            }
        };
    }

    public static Matcher<View> withMinimalAdapterItemCount(final int minimalCount) {
        return new TypeSafeMatcher<View>() {

            @Override
            protected boolean matchesSafely(View item) {
                //noinspection SimplifiableIfStatement - for more readable code
                if (!(item instanceof RecyclerView)) {
                    return false;
                }
                return ((RecyclerView) item).getAdapter().getItemCount() >= minimalCount;
            }

            @Override
            public void describeTo(Description description) {
                description.appendText("Adapter has minimal ");
                description.appendValue(minimalCount);
                description.appendText(" items (to access requested index ");
                description.appendValue(minimalCount - 1);
                description.appendText(")");
            }
        };
    }

}