/*
 *    Copyright (C) 2016 Haruki Hasegawa
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *        http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package com.h6ah4i.android.widget.advrecyclerview.headerfooter;

import android.view.ViewGroup;

import com.h6ah4i.android.widget.advrecyclerview.adapter.AdapterPathSegment;
import com.h6ah4i.android.widget.advrecyclerview.adapter.ItemIdComposer;
import com.h6ah4i.android.widget.advrecyclerview.adapter.ItemViewTypeComposer;
import com.h6ah4i.android.widget.advrecyclerview.composedadapter.ComposedAdapter;
import com.h6ah4i.android.widget.advrecyclerview.composedadapter.ComposedChildAdapterTag;

import java.util.List;

import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

/**
 * A simplified version of ComposedAdapter for creating headers and footers.
 */
public abstract class AbstractHeaderFooterWrapperAdapter<HeaderVH extends RecyclerView.ViewHolder, FooterVH extends RecyclerView.ViewHolder> extends ComposedAdapter {
    /**
     * Segment type: header items
     */
    public static final int SEGMENT_TYPE_HEADER = 0;

    /**
     * Segment type: normal items
     */
    public static final int SEGMENT_TYPE_NORMAL = 1;

    /**
     * Segment type: footer items
     */
    public static final int SEGMENT_TYPE_FOOTER = 2;

    private RecyclerView.Adapter mHeaderAdapter;
    private RecyclerView.Adapter mWrappedAdapter;
    private RecyclerView.Adapter mFooterAdapter;

    private ComposedChildAdapterTag mHeaderAdapterTag;
    private ComposedChildAdapterTag mWrappedAdapterTag;
    private ComposedChildAdapterTag mFooterAdapterTag;

    /**
     * Constructor
     *
     */
    public AbstractHeaderFooterWrapperAdapter() {
    }

    /**
     *
     * @param adapter Wrapped contents adapter.
     */
    @SuppressWarnings("unchecked")
    @NonNull
    public AbstractHeaderFooterWrapperAdapter setAdapter(@NonNull RecyclerView.Adapter<? extends RecyclerView.ViewHolder> adapter) {
        if (mWrappedAdapter != null) {
            throw new IllegalStateException("setAdapter() can call only once");
        }

        mWrappedAdapter = adapter;
        mHeaderAdapter = onCreateHeaderAdapter();
        mFooterAdapter = onCreateFooterAdapter();

        final boolean hasStableIds = adapter.hasStableIds();
        mHeaderAdapter.setHasStableIds(hasStableIds);
        mFooterAdapter.setHasStableIds(hasStableIds);
        setHasStableIds(hasStableIds);

        mHeaderAdapterTag = addAdapter(mHeaderAdapter);
        mWrappedAdapterTag = addAdapter(mWrappedAdapter);
        mFooterAdapterTag = addAdapter(mFooterAdapter);

        return this;
    }

    @Override
    protected void onRelease() {
        super.onRelease();

        mHeaderAdapterTag = null;
        mWrappedAdapterTag = null;
        mFooterAdapterTag = null;
        mHeaderAdapter = null;
        mWrappedAdapter = null;
        mFooterAdapter = null;
    }

    /**
     * Returns a newly created adapter for header items.
     *
     * @return Adapter for header items
     */
    @NonNull
    protected RecyclerView.Adapter onCreateHeaderAdapter() {
        return new BaseHeaderAdapter(this);
    }

    /**
     * Returns a newly created adapter for the footer items.
     *
     * @return Adapter for the footer items
     */
    @NonNull
    protected RecyclerView.Adapter onCreateFooterAdapter() {
        return new BaseFooterAdapter(this);
    }

    /**
     * Returns the adapter for the header items.
     *
     * @return Adapter for the header items
     */
    @Nullable
    public RecyclerView.Adapter getHeaderAdapter() {
        return mHeaderAdapter;
    }

    /**
     * Returns the adapter for the footer items.
     *
     * @return Adapter for the footer items
     */
    @Nullable
    public RecyclerView.Adapter getFooterAdapter() {
        return mFooterAdapter;
    }

    /**
     * Returns underlying adapter.
     * @return The underlying adapter instance
     */
    @Nullable
    public RecyclerView.Adapter getWrappedAdapter() {
        return mWrappedAdapter;
    }


    /**
     * Returns the path segment for the underlying adapter.
     *
     * @return AdapterPathSegment for the wrapped adapter
     */
    @NonNull
    public AdapterPathSegment getWrappedAdapterSegment() {
      return new AdapterPathSegment(mWrappedAdapter, mWrappedAdapterTag);
    }

    /**
     * Returns the path segment for the header adapter.
     *
     * @return AdapterPathSegment for the header adapter
     */
    @NonNull
    public AdapterPathSegment getHeaderSegment() {
      return new AdapterPathSegment(mHeaderAdapter, mHeaderAdapterTag);
    }

    /**
     * Returns the path segment for the footer adapter.
     *
     * @return AdapterPathSegment for the footer adapter
     */
    @NonNull
    public AdapterPathSegment getFooterSegment() {
      return new AdapterPathSegment(mFooterAdapter, mFooterAdapterTag);
    }

    /**
     * Called when RecyclerView needs a new {@link RecyclerView.ViewHolder} of the
     * given type to represent a header item.
     *
     * @param parent   The ViewGroup into which the new View will be added after it is bound to
     *                 a header adapter position.
     * @param viewType The view type of the new header View.
     * @return A new ViewHolder for the header that holds a View of the given view type.
     * @see RecyclerView.Adapter#onCreateViewHolder(ViewGroup, int)
     * @see #getHeaderItemViewType(int)
     * @see #onBindHeaderItemViewHolder(RecyclerView.ViewHolder, int)
     */
    @NonNull
    public abstract HeaderVH onCreateHeaderItemViewHolder(@NonNull ViewGroup parent, int viewType);

    /**
     * Called when RecyclerView needs a new {@link RecyclerView.ViewHolder} of the
     * given type to represent a footer item.
     *
     * @param parent   The ViewGroup into which the new View will be added after it is bound to
     *                 a footer adapter position.
     * @param viewType The view type of the new footer View.
     * @return A new ViewHolder for the footer that holds a View of the given view type.
     * @see RecyclerView.Adapter#onCreateViewHolder(ViewGroup, int)
     * @see #getFooterItemViewType(int)
     * @see #onBindFooterItemViewHolder(RecyclerView.ViewHolder, int)
     */
    @NonNull
    public abstract FooterVH onCreateFooterItemViewHolder(@NonNull ViewGroup parent, int viewType);

    /**
     * Called by RecyclerView to display the data at the specified position. This method should
     * update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect the header item
     * at the given position.
     *
     * @param holder The ViewHolder which should be updated to represent the contents of the
     *        item at the given position in the data set.
     * @param localPosition The position of the item within the header adapter's data set.
     * @see RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)
     */
    public abstract void onBindHeaderItemViewHolder(@NonNull HeaderVH holder, int localPosition);

    /**
     * Called by RecyclerView to display the data at the specified position. This method should
     * update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect the footer item
     * at the given position.
     *
     * @param holder The ViewHolder which should be updated to represent the contents of the
     *        item at the given position in the data set.
     * @param localPosition The position of the item within the footer adapter's data set.
     * @see RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int)
     */
    public abstract void onBindFooterItemViewHolder(@NonNull FooterVH holder, int localPosition);

    /**
     * Called by RecyclerView to display the data at the specified position. This method should
     * update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect the header item
     * at the given position.
     *
     * @param holder The ViewHolder which should be updated to represent the contents of the
     *        item at the given position in the data set.
     * @param localPosition The position of the item within the header adapter's data set.
     * @param payloads A non-null list of merged payloads. Can be empty list if requires full
     *                 update.
     * @see RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int, List)
     */
    public void onBindHeaderItemViewHolder(@NonNull HeaderVH holder, int localPosition, List<Object> payloads) {
        onBindHeaderItemViewHolder(holder, localPosition);
    }

    /**
     * Called by RecyclerView to display the data at the specified position. This method should
     * update the contents of the {@link RecyclerView.ViewHolder#itemView} to reflect the footer item
     * at the given position.
     *
     * @param holder The ViewHolder which should be updated to represent the contents of the
     *        item at the given position in the data set.
     * @param localPosition The position of the item within the footer adapter's data set.
     * @param payloads A non-null list of merged payloads. Can be empty list if requires full
     *                 update.
     * @see RecyclerView.Adapter#onBindViewHolder(RecyclerView.ViewHolder, int, List)
     */
    public void onBindFooterItemViewHolder(@NonNull FooterVH holder, int localPosition, List<Object> payloads) {
        onBindFooterItemViewHolder(holder, localPosition);
    }

     /**
     * Returns the total number of items in the data set hold by the header adapter.
     *
     * @return The total number of items in the header adapter.
     * @see RecyclerView.Adapter#getItemCount()
     */
    public abstract int getHeaderItemCount();

    /**
     * Returns the total number of items in the data set hold by the footer adapter.
     *
     * @return The total number of items in the footer adapter.
     * @see RecyclerView.Adapter#getItemCount()
     */
    public abstract int getFooterItemCount();

    /**
     * Return the stable ID for the item at <code>localPosition</code>. If {@link #hasStableIds()}
     * would return false this method should return {@link RecyclerView#NO_ID}.
     *
     * @param localPosition Header adapter position to query
     * @return the stable ID of the item at position
     * @see RecyclerView.Adapter#getItemId(int)
     */
    @IntRange(from = ItemIdComposer.MIN_WRAPPED_ID, to = ItemIdComposer.MAX_WRAPPED_ID)
    public long getHeaderItemId(int localPosition) {
        if (hasStableIds()) {
            return RecyclerView.NO_ID;
        }
        return localPosition; // This works for simple header items without structural changes
    }

    /**
     * Return the stable ID for the item at <code>localPosition</code>. If {@link #hasStableIds()}
     * would return false this method should return {@link RecyclerView#NO_ID}.
     *
     * @param localPosition Footer adapter position to query
     * @return the stable ID of the item at position
     * @see RecyclerView.Adapter#getItemId(int)
     */
    @IntRange(from = ItemIdComposer.MIN_WRAPPED_ID, to = ItemIdComposer.MAX_WRAPPED_ID)
    public long getFooterItemId(int localPosition) {
        if (hasStableIds()) {
            return RecyclerView.NO_ID;
        }
        return localPosition; // This works for simple footer items without structural changes
    }

    /**
     * Return the view type of the header item at <code>localPosition</code> for the purposes
     * of view recycling.
     *
     * <p>The default implementation of this method returns 0, making the assumption of
     * a single view type for the adapter. Unlike ListView adapters, types need not
     * be contiguous.</p>
     *
     * @param localPosition The header adapter local position to query
     * @return integer value identifying the type of the view needed to represent the item at
     *                 <code>localPosition</code>. Type codes need not be contiguous.
     */
    @IntRange(from = ItemViewTypeComposer.MIN_WRAPPED_VIEW_TYPE, to = ItemViewTypeComposer.MAX_WRAPPED_VIEW_TYPE)
    public int getHeaderItemViewType(int localPosition) {
        return 0;
    }

    /**
     * Return the view type of the footer item at <code>localPosition</code> for the purposes
     * of view recycling.
     *
     * <p>The default implementation of this method returns 0, making the assumption of
     * a single view type for the adapter. Unlike ListView adapters, types need not
     * be contiguous.</p>
     *
     * @param localPosition The footer adapter local position to query
     * @return integer value identifying the type of the view needed to represent the item at
     *                 <code>localPosition</code>. Type codes need not be contiguous.
     */
    @IntRange(from = ItemViewTypeComposer.MIN_WRAPPED_VIEW_TYPE, to = ItemViewTypeComposer.MAX_WRAPPED_VIEW_TYPE)
    public int getFooterItemViewType(int localPosition) {
        return 0;
    }

    public static class BaseHeaderAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        protected AbstractHeaderFooterWrapperAdapter mHolder;

        public BaseHeaderAdapter(AbstractHeaderFooterWrapperAdapter holder) {
            mHolder = holder;
        }

        @Override
        public int getItemCount() {
            return mHolder.getHeaderItemCount();
        }

        @Override
        public long getItemId(int position) {
            return mHolder.getHeaderItemId(position);
        }

        @Override
        public int getItemViewType(int position) {
            return mHolder.getHeaderItemViewType(position);
        }

        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            return mHolder.onCreateHeaderItemViewHolder(parent, viewType);
        }

        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            throw new IllegalStateException();
        }

        @Override
        @SuppressWarnings("unchecked")
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
            mHolder.onBindHeaderItemViewHolder(holder, position, payloads);
        }
    }

    public static class BaseFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
        protected AbstractHeaderFooterWrapperAdapter mHolder;

        public BaseFooterAdapter(AbstractHeaderFooterWrapperAdapter holder) {
            mHolder = holder;
        }

        @Override
        public int getItemCount() {
            return mHolder.getFooterItemCount();
        }

        @Override
        public long getItemId(int position) {
            return mHolder.getFooterItemId(position);
        }

        @Override
        public int getItemViewType(int position) {
            return mHolder.getFooterItemViewType(position);
        }

        @NonNull
        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
            return mHolder.onCreateFooterItemViewHolder(parent, viewType);
        }

        @Override
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
            throw new IllegalStateException();
        }

        @Override
        @SuppressWarnings("unchecked")
        public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position, @NonNull List<Object> payloads) {
            mHolder.onBindFooterItemViewHolder(holder, position, payloads);
        }
    }
}
