package net.xpece.material.navigationdrawer.list;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.ContextThemeWrapper;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.AdapterView;
import android.widget.ListView;

import net.xpece.material.navigationdrawer.R;
import net.xpece.material.navigationdrawer.descriptors.CompositeNavigationItemDescriptor;
import net.xpece.material.navigationdrawer.descriptors.NavigationItemDescriptor;
import net.xpece.material.navigationdrawer.descriptors.NavigationSectionDescriptor;
import net.xpece.material.navigationdrawer.internal.Utils;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by pechanecjr on 14. 12. 2014.
 */
abstract class NavigationListFragmentDelegate implements
    AdapterView.OnItemClickListener, NavigationListFragmentImpl {
    public static final String TAG = NavigationListFragmentDelegate.class.getSimpleName();

    private static final NavigationListFragmentCallbacks DUMMY_CALLBACKS = new NavigationListFragmentCallbacks() {
        @Override
        public void onNavigationItemSelected(View view, int position, int id, NavigationItemDescriptor item) {
            //
        }
    };
    private NavigationListFragmentCallbacks mCallbacks = DUMMY_CALLBACKS;

    private View mView;
    private ListView mListView;
    private ViewGroup mPinnedContainer;
    private View mPinnedDivider;

    private int mTheFix = 0;
    private boolean mShouldPinnedSectionHaveBackground = false;
    private Drawable mBackground = null;
    private Drawable mPinnedSectionBackground = null;

    private int mTheme;
    private LayoutInflater mInflater;

    private final ViewTreeObserver.OnGlobalLayoutListener mPinnedContainerOnGlobalLayoutListener = new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            // Problem: in portrait the list has extra space at the bottom (5dp, 8px)
            // Solution: calculate the extra space and subtract it from padding
            int fix = 0;
            final int paddingBottom = mListView.getPaddingBottom();
//            final int lastVisible = mListView.getLastVisiblePosition();
//            final int lastPosition = mListView.getAdapter().getCount() - 1; // mAdapter.getCount() - 1;
////      timber("listVisible=%s, lastPosition=%s", lastVisible, lastPosition);
//            if (lastVisible == lastPosition) {
//                final int listHeight = mListView.getMeasuredHeight() - paddingBottom - mListView.getPaddingTop();
//                final int lastBottom = mListView.getChildAt(mListView.getLastVisiblePosition() - mListView.getFirstVisiblePosition()).getBottom();
////        timber("listHeight=%s, lastBottom=%s", listHeight, lastBottom);
//                // if last item ends before the list ends there's extra space
//                if (lastBottom < listHeight) {
//                    if (paddingBottom == 0) mTheFix = Math.max(0, listHeight - lastBottom);
//                    fix = mTheFix;
////          timber("extraSpace=" + fix);
//                }
//            }
            // Cause: Transparent ColorDrawable used as divider had intrinsic height equal to -1
            //        which effectively moved each item 1px upwards.

            // modify padding only after pinned section has been measured and it changed
            // padding = pinned section height - listview extra space - 1dp divider alignment
            final int pinnedHeight = mPinnedContainer.getMeasuredHeight();
//      timber("pinnedHeight=%s", pinnedHeight);
            final int targetPadding = pinnedHeight - fix - Utils.dpToPixelOffset(getActivity(), 1);
//      timber("targetPadding=%s, paddingBottom=%s", targetPadding, paddingBottom);
            if (paddingBottom != targetPadding) {
                mListView.setPadding(0, 0, 0, targetPadding);
            }

            // if pinned section is at the very bottom elevate it
            if (getView() == null) return;
            final int parentHeight = getView().getHeight();
            final int pinnedBottom = mPinnedContainer.getBottom();
            if (pinnedBottom >= parentHeight) {
                // there is not enough room, the section will be pinned
                int colorBackground = Utils.getColor(getView().getContext(), android.R.attr.colorBackground, 0);
                if (Build.VERSION.SDK_INT < 21 || (colorBackground & 0xffffff) < 0xffffff / 2) {
                    // on API lower than 21 and on dark theme show the line instead of shadow
                    Utils.setElevation(mPinnedContainer, 0);
                    mPinnedDivider.setVisibility(View.VISIBLE);
                    mShouldPinnedSectionHaveBackground = true; // the views would be seen about to scroll in
                } else {
                    // on light theme on API 21 show shadow instead of line
                    Utils.setElevation(mPinnedContainer, getActivity().getResources().getDimension(R.dimen.mnd_unit));
                    mPinnedDivider.setVisibility(View.GONE);
                    mShouldPinnedSectionHaveBackground = true;
                }
            } else {
                // there is enough room, the section will not be pinned
                Utils.setElevation(mPinnedContainer, 0);
                mPinnedDivider.setVisibility(View.VISIBLE);
                mShouldPinnedSectionHaveBackground = false;
            }
            updatePinnedSectionBackground();

            if (paddingBottom == targetPadding) {
                // my work here is done
                Utils.removeOnGlobalLayoutListener(mPinnedContainer, mPinnedContainerOnGlobalLayoutListener);
            }
        }
    };

    private NavigationListAdapter mAdapter;
    private int mLastSelected = -1;
    private boolean mReselectEnabled = true;

    private List<NavigationSectionDescriptor> mSections = new ArrayList<>(0);
    private List<CompositeNavigationItemDescriptor> mPinnedSection = null;
    private View mHeader = null;

    public NavigationListFragmentDelegate() {
    }

    public abstract Activity getActivity();

    public abstract View getView();

    @Override
    public void onAttach(Activity activity) {
        mCallbacks = (NavigationListFragmentCallbacks) activity;
    }

    @Override
    public void onDetach() {
        mCallbacks = DUMMY_CALLBACKS;
    }

    @Override
    public void onSaveInstanceState(Bundle outState) {
        outState.putInt("mLastSelected", mLastSelected);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        if (mTheme > 0) {
            Context newContext = new ContextThemeWrapper(inflater.getContext(), mTheme);
            inflater = inflater.cloneInContext(newContext);
        }
        mInflater = inflater;

        mView = inflater.inflate(R.layout.mnd_list, container, false);
        mListView = (ListView) mView.findViewById(R.id.mnd_list);
        mPinnedContainer = (ViewGroup) mView.findViewById(R.id.mnd_section_pinned);
        mPinnedDivider = mView.findViewById(R.id.mnd_divider_pinned);
        return mView;
    }

    @Override
    public void onViewCreated(final View view, @Nullable Bundle savedInstanceState) {
        Context context = view.getContext();

        mPinnedDivider.setBackgroundColor(Utils.createDividerColor(context));

        mListView.setOnItemClickListener(this);
        mListView.setSelector(Utils.getSelectorDrawable(context));

        if (savedInstanceState != null) {
            mLastSelected = savedInstanceState.getInt("mLastSelected");
        }
    }

    @Override
    public void onDestroyView() {
        mInflater = null;

        mView = null;
        mListView = null;
        mPinnedContainer = null;
        mPinnedDivider = null;
    }

    @Override
    public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
        TypedArray a = activity.obtainStyledAttributes(attrs, R.styleable.NavigationListFragment);
        mTheme = a.getResourceId(R.styleable.NavigationListFragment_android_theme, 0);
        a.recycle();
    }

    @Override
    public LayoutInflater getLayoutInflater2() {
        return mInflater;
    }

    @Override
    public void setItems(List<? extends CompositeNavigationItemDescriptor> items) {
        NavigationSectionDescriptor section = new NavigationSectionDescriptor().addItems(items);
        List<NavigationSectionDescriptor> sections = new ArrayList<>(1);
        sections.add(section);
        setSections(sections);
    }

    /**
     * Set all sections that would be shown in the navigation list except optional pinned section.
     *
     * @param sections
     */
    @Override
    public void setSections(List<NavigationSectionDescriptor> sections) {
        mSections = sections;
        updateSections();
    }

    private void updateSections() {
        if (getView() == null) return;

        mAdapter = new NavigationListAdapter(mSections);
        mAdapter.setReselectEnabled(mReselectEnabled);
        mAdapter.setActivatedItem(mLastSelected - getHeaderViewsCount());
        mListView.setAdapter(mAdapter);

        mPinnedContainer.getViewTreeObserver().addOnGlobalLayoutListener(mPinnedContainerOnGlobalLayoutListener);
    }

    /**
     * Use this method to set a section that would be pinned at the bottom of the screen when there's
     * not enough room for the whole list to show. Typically this section would contain
     * {@code Settings} and {@code Help & feedback} menu items.
     *
     * @param section
     */
    @Override
    public void setPinnedSection(NavigationSectionDescriptor section) {
        if (section == null || !section.equals(mPinnedSection)) {
            mPinnedSection = section;
            updatePinnedSection();
        }
    }

    private void updatePinnedSection() {
        if (getView() == null) return;

        Drawable selector = Utils.getSelectorDrawable(getView().getContext());

        final int offset = 2; // plus 1 for the divider view plus 1 for padding
        int targetCount = mPinnedSection == null ? 0 : mPinnedSection.size();
//    while (mPinnedContainer.getChildCount() > targetCount + offset) {
//      View view = mPinnedContainer.getChildAt(offset);
//      view.setOnClickListener(null);
//      mPinnedContainer.removeView(view);
//    }
        // TODO temporarily removing all, theres no knowing precise AbsNavItemDesc subtype
        while (mPinnedContainer.getChildCount() > offset) {
            View view = mPinnedContainer.getChildAt(offset);
            view.setOnClickListener(null);
            mPinnedContainer.removeView(view);
        }

        int currentCount = mPinnedContainer.getChildCount() - offset;
        for (int i = 0; i < targetCount; i++) {
            final CompositeNavigationItemDescriptor item = mPinnedSection.get(i);
            final View view;
            if (i < currentCount) {
                view = mPinnedContainer.getChildAt(i + offset);
                item.bindView(view, false);
            } else {
                Context context = getLayoutInflater2().getContext();
                view = item.createView(context, mPinnedContainer);
                item.bindView(view, false);
                mPinnedContainer.addView(view);
            }
            Utils.setBackground(view, selector.getConstantState().newDrawable());

            final int relativePosition = i;
            view.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
//          mCallbacks.onNavigationItemSelected(v, mAdapter.getCount() + relativePosition, item.getId());
                    onItemClick(v, mAdapter.getCount() + relativePosition, item.getId(), item);
                }
            });
        }
        if (targetCount > 0) {
            mPinnedContainer.setVisibility(View.VISIBLE);
        } else {
            mPinnedContainer.setVisibility(View.GONE);
        }

        mPinnedContainer.getViewTreeObserver().addOnGlobalLayoutListener(mPinnedContainerOnGlobalLayoutListener);
    }

    /**
     * Use this method to set a header view. This header is selectable by default so you can use it as
     * a no-op close drawer button. Alternatively you would set up an {@link android.view.View.OnClickListener}
     * beforehand. <em>The header view is not managed by this class, it's completely in your hands.</em>
     *
     * @param view
     */
    @Override
    public void setHeaderView(View view, boolean clickable) {
        if (view == mHeader) return;
        if (mHeader != null) {
            mListView.removeHeaderView(mHeader);
        }
        if (view != null) {
            if (mListView != null) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
                    if (mListView.getAdapter() != null) mListView.setAdapter(null);
                    mListView.addHeaderView(view, null, clickable);
                    if (mAdapter != null) mListView.setAdapter(mAdapter);
                } else {
                    mListView.addHeaderView(view, null, clickable);
                }
            }
        }
        mHeader = view;
    }

    /**
     * Call this method when the source data set has changed, e.g. when you changed badges.
     */
    @Override
    public void notifyDataSetChanged() {
        if (mAdapter != null) {
            mAdapter.notifyDataSetChanged();
        }
    }

    /**
     * Use this to set a color as the navigation list background.
     * Remember not to use translucent or transparent colors (pinned section has to have an opaque background).
     *
     * @param color
     */
    @Override
    public void setBackgroundColor(int color) {
        if (getView() != null) {
//      mView.setBackgroundColor(color);
//      mPinnedContainer.setBackgroundColor(color);

            Drawable d = new ColorDrawable(color);
            setBackground(d);
        }
    }

    /**
     * Use this to set a drawable as the navigation list background.
     * Remember not to use vertical gradients (pinned section has to have an opaque background).
     *
     * @param drawable
     */
    @Override
    public void setBackground(Drawable drawable) {
        if (getView() != null) {
//      Utils.setBackground(mView, drawable);
//      Utils.setBackground(mPinnedContainer, drawable);

            mBackground = drawable;
            updateBackground();
        }
    }

    /**
     * Use this to resolve a drawable or color resource ID as a drawable and set it as the navigation list background.
     * Remember not to use vertical gradients (pinned section has to have an opaque background).
     *
     * @param resource
     */
    @Override
    public void setBackgroundResource(@DrawableRes @ColorRes int resource) {
        if (getView() != null) {
//      mView.setBackgroundResource(resource);
//      mPinnedContainer.setBackgroundResource(resource);

            Drawable d = Utils.getDrawableRes(mView.getContext(), resource);
            setBackground(d);
        }
    }

    /**
     * Use this to resolve an attribute as a drawable and set it as the navigation list background.
     * Remember not to use vertical gradients (pinned section has to have an opaque background).
     *
     * @param attr
     */
    @Override
    public void setBackgroundAttr(@AttrRes int attr) {
        if (getView() != null) {
            Drawable d = Utils.getDrawableAttr(mView.getContext(), attr);
            setBackground(d);
        }
    }

    /**
     * Use this method for marking selected item from outside. Typically you call this in
     * {@link android.app.Activity#onPostCreate(android.os.Bundle)} after you determine which
     * section was selected previously or by default.
     *
     * @param id
     */
    @Override
    public void setSelectedItem(int id) {
        if (mAdapter != null) {

            int position = mAdapter.getPositionById(id);
            if (position >= 0) {
                if (trySelectPosition(position)) {
                    scrollToPosition(position);
                }
            }
        } else {
            throw new IllegalStateException("No adapter yet!");
        }
    }

    private void scrollToPosition(int position) {
        int last = mListView.getLastVisiblePosition();
        int first = mListView.getFirstVisiblePosition();
        if (last < position) {
            mListView.setSelection(position - (last - first) - 1);
        } else if (first > position) {
            mListView.setSelection(position - 1);
        }
    }

    private boolean trySelectPosition(final int itemPosition) {
        final int listPosition = itemPosition + getHeaderViewsCount();
//    if (listPosition == mLastSelected) return;
        if (itemPosition < 0) {
            // this should never happen
//      selectPosition(-1, mLastSelected);
//      mAdapter.setActivatedItem(-1);
//      mLastSelected = -1;
//      return;
            throw new IllegalArgumentException("Position to select is less than 0.");
        }
        CompositeNavigationItemDescriptor item = (CompositeNavigationItemDescriptor) mAdapter.getItem(itemPosition);
        if (item != null && item.isSticky()) {
//      Utils.timber(TAG, "item=" + item + ", itemPosition=" + itemPosition + ", listPosition=" + listPosition);
            mAdapter.setActivatedItem(itemPosition);
            selectPosition(listPosition, mLastSelected);
            mLastSelected = listPosition;
            return true;
        } else {
            selectPosition(mLastSelected, listPosition);
            return false;
        }
    }

    private void selectPosition(int select, int deselect) {
        if (deselect >= 0) mListView.setItemChecked(deselect, false);
        if (select >= 0) mListView.setItemChecked(mLastSelected, true);
    }

    @Override
    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
        CompositeNavigationItemDescriptor item = (CompositeNavigationItemDescriptor) parent.getItemAtPosition(position);
        onItemClick(view, position, item != null ? item.getId() : (int) id, item);
    }

    private void onItemClick(View view, int position, int id, CompositeNavigationItemDescriptor item) {
        // header views and items from pinned section are not sticky, don't even try
        if (position < getHeaderViewsCount()) {
//        || position > mListView.getHeaderViewsCount() + mAdapter.getCount()) {
            selectPosition(mLastSelected, position);
        } else {
            final int itemPosition = position - getHeaderViewsCount();
            trySelectPosition(itemPosition);
        }

        if (item == null || !item.onClick(view)) {
            mCallbacks.onNavigationItemSelected(view, position, id, item);
        }
    }

    private int getHeaderViewsCount() {return mListView.getHeaderViewsCount();}

    private void updateBackground() {
        Utils.setBackground(mView, mBackground);
        mPinnedSectionBackground = mBackground.getConstantState().newDrawable();
        updatePinnedSectionBackground();
    }

    private void updatePinnedSectionBackground() {
        if (mShouldPinnedSectionHaveBackground) {
            Utils.setBackground(mPinnedContainer, mPinnedSectionBackground);
        } else {
            mPinnedContainer.setBackgroundResource(0);
        }
    }

    public void setReselectEnabled(boolean reselectEnabled) {
        mReselectEnabled = reselectEnabled;
        if (mAdapter != null) {
            mAdapter.setReselectEnabled(reselectEnabled);
        }
    }
}