package com.cardinalsolutions.sectioned_adapter;

import android.content.Context;
import android.graphics.Color;
import android.support.annotation.IntDef;
import android.support.annotation.LayoutRes;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.TextView;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * A {@link RecyclerView.Adapter} that is capable of automatically generating section header views
 * and inserting them into a list of {@link Categorizable} data.
 * <p/>
 * <p>
 * Subclasses should implement <code>onCreateItemViewHolder()</code> to return a
 * {@link android.support.v7.widget.RecyclerView.ViewHolder} for their data object and then
 * implement <code>onBindItemViewHolder()</code> to bind the data object to the view.
 * </p>
 * <p>
 * Calling <code>setItemList()</code> with a list of <code>Categorizable</code> objects will
 * automatically generate and insert headers at the appropriate positions.  The list of items
 * does not need to be sorted.
 * </p>
 *
 * @author Alex Morgan {[email protected]}
 */
public abstract class SectionedAdapter<T extends Categorizable> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    @IntDef({TYPE_ITEM, TYPE_SECTION_HEADER})
    @Retention(RetentionPolicy.SOURCE)
    public @interface ViewType {
    }

    private static final int TYPE_ITEM = 0;
    private static final int TYPE_SECTION_HEADER = 1;

    private List<Object> itemList;

    @LayoutRes
    private int headerLayoutResource = -1;

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        switch (this.getItemViewType(position)) {
            case TYPE_SECTION_HEADER: {
                SectionHeader header = (SectionHeader) this.itemList.get(position);
                ((SectionViewHolder) holder).titleView.setText(header.title);
                break;
            }
            case TYPE_ITEM: {
                this.onBindItemViewHolder(holder, (T) this.itemList.get(position), this.getItemViewType(position));
                break;
            }
            default: {
                throw new RuntimeException("SectionedAdapter could not bind the requested view type.");
            }
        }
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, @ViewType int viewType) {
        switch (viewType) {
            case TYPE_SECTION_HEADER: {
                View view;
                if (this.headerLayoutResource == -1) {
                    view = this.createStandardHeaderView(parent);
                } else {
                    view = this.createCustomHeaderView(parent, this.headerLayoutResource);
                }
                return new SectionViewHolder(view);
            }
            case TYPE_ITEM: {
                return this.onCreateItemViewHolder(parent, viewType);
            }
            default: {
                throw new RuntimeException("SectionedAdapter could not create a ViewHolder for the requested view type.");
            }
        }
    }

    @Override
    @ViewType
    public int getItemViewType(int position) {
        if (itemList.get(position) instanceof Categorizable) {
            return TYPE_ITEM;
        } else if (itemList.get(position) instanceof SectionHeader) {
            return TYPE_SECTION_HEADER;
        } else {
            throw new RuntimeException("SectionedAdapter was unable to determine the view type for the item at position " + position);
        }
    }

    @Override
    public int getItemCount() {
        return itemList.size();
    }

    /**
     * Sets the list of items to be displayed in the adapter.  This method automatically takes care
     * of sorting the data according to category and inserts header views at the beginning of each
     * category.
     *
     * @param itemList a list of {@link Categorizable} objects to be displayed in sections
     */
    public void setItemList(List<? extends Categorizable> itemList) {
        Collections.sort(itemList, new Comparator<Categorizable>() {
            @Override
            public int compare(Categorizable lhs, Categorizable rhs) {
                return String.CASE_INSENSITIVE_ORDER.compare(lhs.getCategory(), rhs.getCategory());
            }
        });
        this.itemList = SectionGenerator.getSectionsForItems(itemList);
        this.notifyDataSetChanged();
    }

    /**
     * Sets a layout resource to inflate when creating the headers for sections.  The specified
     * layout must contain a TextView with the id <code>android.R.id.title</code> or else an
     * exception will be thrown when the ViewHolder is later bound.
     *
     * @param headerResource the resource ID
     */
    protected void setCustomHeaderLayout(@LayoutRes int headerResource) {
        this.headerLayoutResource = headerResource;
    }

    /**
     * This method is called when the adapter needs to bind data for a data object into a view holder.
     *
     * @param holder   a {@link android.support.v7.widget.RecyclerView.ViewHolder} subclass of the
     *                 same type supplied by <code>onCreateItemViewHolder()</code>
     * @param item     the data object to bind
     * @param viewType the type of view that should be bound
     */
    public abstract void onBindItemViewHolder(RecyclerView.ViewHolder holder, T item, @ViewType int viewType);

    /**
     * This method is called to create a {@link android.support.v7.widget.RecyclerView.ViewHolder}
     * for each item in the adapter.  It should return a ViewHolder subclass suitable for binding
     * to the specified view type.
     *
     * @param parent   The ViewGroup into which the new View will be added after it is bound to an adapter position.
     * @param viewType The type of the new View
     * @return A new ViewHolder that holds a View of the given view type.
     */
    public abstract RecyclerView.ViewHolder onCreateItemViewHolder(ViewGroup parent, @ViewType int viewType);

    /**
     * A ViewHolder for holding a section header's View.
     */
    private static class SectionViewHolder extends RecyclerView.ViewHolder {
        private final TextView titleView;

        SectionViewHolder(View itemView) {
            super(itemView);
            this.titleView = (TextView) itemView.findViewWithTag("TITLE");
        }
    }

    static class SectionHeader {
        SectionHeader(String title) {
            this.title = title;
        }

        final String title;
    }

    private View createStandardHeaderView(View parent) {
        LinearLayout sectionView = new LinearLayout(parent.getContext());
        sectionView.setLayoutParams(parent.getLayoutParams());

        int horizontalPadding = this.pixelsToDp(parent.getContext(), 16);
        int verticalPadding = this.pixelsToDp(parent.getContext(), 8);
        sectionView.setPadding(horizontalPadding, verticalPadding, horizontalPadding, verticalPadding);
        TextView titleView = new TextView(parent.getContext());
        titleView.setTag("TITLE");
        titleView.setTextColor(Color.BLACK);
        sectionView.addView(titleView);
        return sectionView;
    }

    private View createCustomHeaderView(ViewGroup parent, @LayoutRes int layoutResource) {
        View view = LayoutInflater.from(parent.getContext()).inflate(layoutResource, parent, false);
        TextView titleView = (TextView) view.findViewById(android.R.id.title);
        if (titleView == null) {
            throw new RuntimeException("The header layout file MUST contain a TextView with the id `android.R.id.title`; unable to bind header views.");
        }
        titleView.setTag("TITLE");
        return view;
    }

    private int pixelsToDp(Context context, int pixels) {
        float scale = context.getResources().getDisplayMetrics().density;
        return (int) (pixels * scale + 0.5f);
    }
}