package com.skocken.efficientadapter.lib.viewholder;

import com.skocken.efficientadapter.lib.adapter.EfficientAdapter;
import com.skocken.efficientadapter.lib.util.EfficientCacheView;
import com.skocken.efficientadapter.lib.util.ViewHelper;

import android.content.Context;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import androidx.annotation.ColorInt;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.RecyclerView;
import android.view.View;

import java.lang.ref.WeakReference;

public abstract class EfficientViewHolder<T> extends RecyclerView.ViewHolder {

    private final EfficientCacheView mCacheView;

    private WeakReference<EfficientAdapter<T>> mAdapterRef;

    private ViewHolderClickListener mViewHolderClickListener;
    private ViewHolderLongClickListener mViewHolderLongClickListener;

    private int mLastBindPosition = -1;

    @Nullable
    private T mObject;

    /**
     * @param itemView the root view of the view holder. This parameter cannot be null.
     *
     * @throws NullPointerException if the view is null
     */
    public EfficientViewHolder(View itemView) {
        super(itemView);
        mCacheView = createCacheView(itemView);
    }

    EfficientCacheView createCacheView(View itemView) {
        return new EfficientCacheView(itemView);
    }

    public void setAdapter(EfficientAdapter<T> adapter) {
        if (adapter != getAdapter()) {
            mAdapterRef = new WeakReference<>(adapter);
        }
    }

    @Nullable
    public EfficientAdapter<T> getAdapter() {
        return mAdapterRef == null ? null : mAdapterRef.get();
    }

    /**
     * Method called when we need to update the view hold by this class.
     *
     * @param item the object subject of this update
     */
    public void onBindView(@Nullable T item, int position) {
        mObject = item;
        mLastBindPosition = position;
        updateView(mCacheView.getView().getContext(), mObject);
    }

    /**
     * Method called when we need to update the view hold by this class.
     *
     * @param context context of the root view
     * @param item  the object subject of this update
     */
    protected abstract void updateView(@NonNull Context context, @Nullable T item);

    /**
     * Get the last object set to this viewholder
     */
    @Nullable
    public T getObject() {
        return mObject;
    }

    /**
     * @return the getAdapterPosition() if available, or the last bind position otherwise
     */
    public int getLastBindPosition() {
        int adapterPosition = getAdapterPosition();
        if (adapterPosition == -1) {
            return mLastBindPosition;
        } else {
            return adapterPosition;
        }
    }

    /**
     * Get the root view for the ViewHolder (the one passed into the constructor)
     *
     * @return The ViewHolder's root view.
     */
    @NonNull
    public View getView() {
        return mCacheView.getView();
    }

    /**
     * Returns the context the view is running in, through which it can
     * access the current theme, resources, etc.
     *
     * @return The view's Context.
     */
    @NonNull
    public Context getContext() {
        return mCacheView.getView().getContext();
    }

    /**
     * Returns the resources associated with this view.
     *
     * @return Resources object.
     */
    @NonNull
    public Resources getResources() {
        return mCacheView.getView().getResources();
    }

    /**
     * Called when a view created by the adapter has been recycled.
     */
    public void onViewRecycled() {
    }

    /**
     * Called when a view created by the adapter has been attached to a window.
     */
    public void onViewAttachedToWindow() {
    }

    /**
     * Called when a view created by the adapter has been detached from its window.
     */
    public void onViewDetachedFromWindow() {
    }

    /**
     * Determine if a click listener should be automatically added to the view of this view holder
     *
     * @return true you want to have this view clickable
     */
    public boolean isClickable() {
        return true;
    }

    /**
     * Determine if a long click listener should be automatically added to the view of this view
     * holder
     *
     * @return true you want to have this view clickable
     */
    public boolean isLongClickable() {
        return true;
    }

    /**
     * Get the OnClickListener to call when the user click on the item.
     * @param adapterHasListener true if the calling adapter has a global listener on the item.
     * @return the click listener to be put into the view.
     */
    public View.OnClickListener getOnClickListener(boolean adapterHasListener) {
        if (isClickable() && adapterHasListener) {
            if (mViewHolderClickListener == null) {
                mViewHolderClickListener = new ViewHolderClickListener<>(this);
            }
        } else {
            mViewHolderClickListener = null;
        }
        return mViewHolderClickListener;
    }

    /**
     * Get the OnLongClickListener to call when the user long-click on the item.
     * @param adapterHasListener true if the calling adapter has a global listener on the item.
     * @return the long-click listener to be put into the view.
     */
    public View.OnLongClickListener getOnLongClickListener(boolean adapterHasListener) {
        if (isLongClickable() && adapterHasListener) {
            if (mViewHolderLongClickListener == null) {
                mViewHolderLongClickListener = new ViewHolderLongClickListener<>(this);
            }
        } else {
            mViewHolderLongClickListener = null;
        }
        return mViewHolderLongClickListener;
    }

    /**
     * Helper for {@link EfficientCacheView#clearViewsCached()}
     */
    public void clearViewsCached() {
        mCacheView.clearViewsCached();
    }

    /**
     * Helper for {@link EfficientCacheView#clearViewCached(int)}
     */
    public void clearViewCached(int viewId) {
        mCacheView.clearViewCached(viewId);
    }

    /**
     * Helper for {@link EfficientCacheView#clearViewCached(int, int)}
     */
    public void clearViewCached(int parentId, int viewId) {
        mCacheView.clearViewCached(parentId, viewId);
    }

    /**
     * Helper for {@link EfficientCacheView#findViewByIdEfficient(int)}
     */
    @Nullable
    public <T extends View> T findViewByIdEfficient(int id) {
        return mCacheView.findViewByIdEfficient(id);
    }

    /**
     * Helper for {@link EfficientCacheView#findViewByIdEfficient(int, int)}
     */
    @Nullable
    public <T extends View> T findViewByIdEfficient(int parentId, int id) {
        return mCacheView.findViewByIdEfficient(parentId, id);
    }

    /**
     * Equivalent to calling View.setVisibility
     *
     * @param viewId     The id of the view whose visibility should change
     * @param visibility The new visibility for the view
     */
    public void setVisibility(int viewId, int visibility) {
        ViewHelper.setVisibility(mCacheView, viewId, visibility);
    }

    /**
     * Equivalent to calling View.setBackground
     *
     * @param viewId   The id of the view whose background should change
     * @param drawable The new background for the view
     */
    public void setBackground(int viewId, Drawable drawable) {
        ViewHelper.setBackground(mCacheView, viewId, drawable);
    }

    /**
     * Equivalent to calling View.setBackgroundColor
     *
     * @param viewId The id of the view whose background should change
     * @param color  The new background color for the view
     */
    public void setBackgroundColor(int viewId, @ColorInt int color) {
        ViewHelper.setBackgroundColor(mCacheView, viewId, color);
    }

    /**
     * Equivalent to calling View.setBackgroundResource
     *
     * @param viewId The id of the view whose background should change
     * @param resid  The new background resource for the view
     */
    public void setBackgroundResource(int viewId, @DrawableRes int resid) {
        ViewHelper.setBackgroundResource(mCacheView, viewId, resid);
    }

    /**
     * Equivalent to calling View.setTag
     *
     * @param viewId The id of the view whose tag should change
     * @param tag    An Object to tag the view with
     */
    public void setTag(int viewId, Object tag) {
        ViewHelper.setTag(mCacheView, viewId, tag);
    }

    /**
     * Equivalent to calling View.setTag
     *
     * @param viewId The id of the view whose tag should change
     * @param key    The key identifying the tag
     * @param tag    An Object to tag the view with
     */
    public void setTag(int viewId, int key, Object tag) {
        ViewHelper.setTag(mCacheView, viewId, key, tag);
    }

    /**
     * Equivalent to calling TextView.setText
     *
     * @param viewId The id of the view whose text should change
     * @param text   The new text for the view
     */
    public void setText(int viewId, CharSequence text) {
        ViewHelper.setText(mCacheView, viewId, text);
    }

    /**
     * Equivalent to calling TextView.setText
     *
     * @param viewId The id of the view whose text should change
     * @param resid  The new text for the view
     */
    public void setText(int viewId, @StringRes int resid) {
        ViewHelper.setText(mCacheView, viewId, resid);
    }

    /**
     * Equivalent to calling TextView.setTextColor
     *
     * @param viewId The id of the view whose text color should change
     * @param color  The new color for the view
     */
    public void setTextColor(int viewId, @ColorInt int color) {
        ViewHelper.setTextColor(mCacheView, viewId, color);
    }

    /**
     * Equivalent to calling TextView.setTextSize
     *
     * @param viewId The id of the view whose text size should change
     * @param size   The scaled pixel size.
     */
    public void setTextSize(int viewId, float size) {
        ViewHelper.setTextSize(mCacheView, viewId, size);
    }

    /**
     * Equivalent to calling TextView.setTextSize
     *
     * @param viewId The id of the view whose text size should change
     * @param unit   The desired dimension unit.
     * @param size   The desired size in the given units.
     */
    public void setTextSize(int viewId, int unit, float size) {
        ViewHelper.setTextSize(mCacheView, viewId, unit, size);
    }

    /**
     * Equivalent to calling ImageView.setImageResource
     *
     * @param viewId The id of the view whose image should change
     * @param resId  the resource identifier of the drawable
     */
    public void setImageViewResource(int viewId, @DrawableRes int resId) {
        ViewHelper.setImageResource(mCacheView, viewId, resId);
    }

    /**
     * Equivalent to calling ImageView.setImageDrawable
     *
     * @param viewId   The id of the view whose image should change
     * @param drawable the Drawable to set, or {@code null} to clear the
     *                 content
     */
    public void setImageDrawable(int viewId, Drawable drawable) {
        ViewHelper.setImageDrawable(mCacheView, viewId, drawable);
    }

    /**
     * Equivalent to calling ImageView.setImageUri
     *
     * @param viewId The id of the view whose image should change
     * @param uri    the Uri of an image, or {@code null} to clear the content
     */
    public void setImageUri(int viewId, @Nullable Uri uri) {
        ViewHelper.setImageUri(mCacheView, viewId, uri);
    }

    /**
     * Equivalent to calling ImageView.setImageBitmap
     *
     * @param viewId The id of the view whose image should change
     * @param bm     The bitmap to set
     */
    public void setImageBitmap(int viewId, Bitmap bm) {
        ViewHelper.setImageBitmap(mCacheView, viewId, bm);
    }

    private static class ViewHolderClickListener<T> implements View.OnClickListener {

        private WeakReference<EfficientViewHolder<T>> mViewHolderRef;

        public ViewHolderClickListener(EfficientViewHolder<T> viewHolder) {
            mViewHolderRef = new WeakReference<>(viewHolder);
        }

        @Override
        public void onClick(View v) {
            EfficientViewHolder<T> viewHolder = mViewHolderRef.get();
            if (viewHolder == null) {
                return;
            }
            EfficientAdapter<T> adapter = viewHolder.getAdapter();
            if (adapter == null) {
                return;
            }
            EfficientAdapter.OnItemClickListener<T> listener = adapter.getOnItemClickListener();
            if (listener == null) {
                return;
            }
            T object = viewHolder.getObject();
            View view = viewHolder.getView();
            int position = viewHolder.getLastBindPosition();
            listener.onItemClick(adapter, view, object, position);
        }
    }

    private static class ViewHolderLongClickListener<T> implements View.OnLongClickListener {

        private WeakReference<EfficientViewHolder<T>> mViewHolderRef;

        public ViewHolderLongClickListener(EfficientViewHolder<T> viewHolder) {
            mViewHolderRef = new WeakReference<>(viewHolder);
        }

        @Override
        public boolean onLongClick(View v) {
            EfficientViewHolder<T> viewHolder = mViewHolderRef.get();
            if (viewHolder == null) {
                return false;
            }
            EfficientAdapter<T> adapter = viewHolder.getAdapter();
            if (adapter == null) {
                return false;
            }
            EfficientAdapter.OnItemLongClickListener<T> listener = adapter.getOnItemLongClickListener();
            if (listener == null) {
                return false;
            }
            T object = viewHolder.getObject();
            View view = viewHolder.getView();
            int position = viewHolder.getLastBindPosition();
            listener.onLongItemClick(adapter, view, object, position);
            return true;
        }
    }
}