package com.raizlabs.universaladapter.converter;

import android.support.annotation.NonNull;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.Spinner;

import com.raizlabs.coreutils.threading.ThreadingUtils;
import com.raizlabs.coreutils.util.observable.lists.ListObserver;
import com.raizlabs.coreutils.util.observable.lists.ListObserverListener;
import com.raizlabs.coreutils.util.observable.lists.SimpleListObserverListener;
import com.raizlabs.universaladapter.UniversalAdapterUtils;
import com.raizlabs.universaladapter.ViewHolder;

/**
 * Class which dynamically converts a {@link UniversalAdapter} into a
 * {@link BaseAdapterConverter}. This keeps a binding to the
 * {@link UniversalAdapter} so it will be notified of data changes made to the
 * outer adapter.
 *
 * @param <Item>   The type of item that views will represent.
 * @param <Holder> The type of the {@link ViewHolder} that will be used to hold
 *                 views.
 */
public class BaseAdapterConverter<Item, Holder extends ViewHolder>
        extends BaseAdapter implements UniversalConverter<Item, Holder> {

    private UniversalAdapter<Item, Holder> universalAdapter;

    /**
     * Creates a {@link BaseAdapterConverter} converting the given {@link UniversalAdapter} into a {@link BaseAdapter}.
     *
     * @param universalAdapter The adapter to convert.
     * @param adapterView      The {@link AdapterView} to bind to. This may be null, but it is heavily recommended that
     *                         it isn't. If null is passed, you'll likely want to call
     *                         {@link #bindToAdapterView(AdapterView)} - see its documentation for more details.
     */
    public BaseAdapterConverter(@NonNull
                                UniversalAdapter<Item, Holder> universalAdapter,
                                AdapterView<? super BaseAdapter> adapterView) {
        universalAdapter.checkIfBoundAndSet();
        setAdapter(universalAdapter);

        bindToAdapterView(adapterView);

        notifyDataSetChanged();
    }

    // region Inherited Methods

    @Override
    public UniversalAdapter<Item, Holder> getAdapter() {
        return universalAdapter;
    }

    @Override
    public void cleanup() {
        getAdapter().getListObserver().removeListener(internalListObserverListener);
    }

    @Override
    public void setItemClickedListener(ItemClickedListener<Item, Holder> itemClickedListener) {
        getAdapter().setItemClickedListener(itemClickedListener);
    }

    @Override
    public void setItemLongClickedListener(ItemLongClickedListener<Item, Holder> longClickedListener) {
        getAdapter().setItemLongClickedListener(longClickedListener);
    }

    @Override
    public void setHeaderClickedListener(HeaderClickedListener headerClickedListener) {
        getAdapter().setHeaderClickedListener(headerClickedListener);
    }

    @Override
    public void setFooterClickedListener(FooterClickedListener footerClickedListener) {
        getAdapter().setFooterClickedListener(footerClickedListener);
    }

    @Override
    public void setHeaderLongClickedListener(HeaderLongClickedListener headerLongClickedListener) {
        getAdapter().setHeaderLongClickedListener(headerLongClickedListener);
    }

    @Override
    public void setFooterLongClickedListener(FooterLongClickedListener footerLongClickedListener) {
        getAdapter().setFooterLongClickedListener(footerLongClickedListener);
    }

    @Override
    public void setAdapter(@NonNull
                           UniversalAdapter<Item, Holder> universalAdapter) {
        if (getAdapter() != null) {
            getAdapter().getListObserver().removeListener(internalListObserverListener);
        }

        this.universalAdapter = universalAdapter;
        universalAdapter.getListObserver().addListener(internalListObserverListener);
    }

    @Override
    public void notifyDataSetChanged() {
        getAdapter().notifyDataSetChanged();
    }

    @Override
    public int getViewTypeCount() {
        return getAdapter().getInternalItemViewTypeCount();
    }

    @Override
    public int getItemViewType(int position) {
        return getAdapter().getInternalItemViewType(position);
    }

    @Override
    public int getCount() {
        return getAdapter().getInternalCount();
    }

    @Override
    public Item getItem(int position) {
        return getAdapter().get(position);
    }

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

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        if (convertView != null) {
            viewHolder = getViewHolder(convertView);
        }

        if (viewHolder == null) {
            int viewType = getItemViewType(position);
            viewHolder = getAdapter().createViewHolder(parent, viewType);
            setViewHolder(viewHolder.itemView, viewHolder);
        }

        getAdapter().bindViewHolder(viewHolder, position);

        return viewHolder.itemView;
    }

    @Override
    public View getDropDownView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder = null;
        if (convertView != null) {
            viewHolder = getViewHolder(convertView);
        }

        if (viewHolder == null) {
            int viewType = getItemViewType(position);
            viewHolder = getAdapter().createDropDownViewHolder(parent, viewType);
            setViewHolder(viewHolder.itemView, viewHolder);
        }

        getAdapter().bindDropDownViewHolder(viewHolder, position);

        return viewHolder.itemView;
    }

    @Override
    public boolean isEnabled(int position) {
        return getAdapter().internalIsEnabled(position);
    }

    @Override
    public boolean areAllItemsEnabled() {
        return getAdapter().areAllItemsEnabled();
    }

    // endregion Inherited Methods

    // region Instance Methods

    /**
     * Binds this adapter to the given {@link AdapterView}, setting it as its adapter. This should be done by
     * construction or immediately after, before this adapter is used. This mechanism sets this class as the view's
     * adapter and permits certain functionality such as click events. Without it, this class will still function as
     * a normal {@link BaseAdapter}, but additional functionality may not work. Ignore this step at your own risk.
     *
     * @param adapterView The {@link AdapterView} to bind to.
     */
    public void bindToAdapterView(AdapterView<? super BaseAdapter> adapterView) {
        if (adapterView != null) {
            adapterView.setAdapter(this);

            // Spinners don't like on item click listeners.
            // We will still delegate calls to it since you're clicking on an item to select it...
            if (!(adapterView instanceof Spinner)) {
                adapterView.setOnItemClickListener(internalItemClickListener);
            } else {
                adapterView.setOnItemSelectedListener(internalItemSelectedListener);
            }

            adapterView.setOnItemLongClickListener(internalLongClickListener);
        }
    }

    protected void superNotifyDataSetChanged() {
        super.notifyDataSetChanged();
    }

    protected Holder getViewHolder(View view) {
        try {
            return UniversalAdapterUtils.getViewHolder(view);
        } catch (ClassCastException ex) {
            // Don't care. Just don't crash. We'll just ignore convertView.
        }

        return null;
    }

    /**
     * Attaches the view holder to the specified {@link View}
     *
     * @param view   The view to attach the holder to.
     * @param holder The holder to attach to the view.
     */
    protected void setViewHolder(View view, ViewHolder holder) {
        UniversalAdapterUtils.setViewHolder(view, holder);
    }

    /**
     * Calls {@link #superNotifyDataSetChanged()} on the UI thread.
     */
    protected void superNotifyDataSetChangedOnUIThread() {
        ThreadingUtils.runOnUIThread(superDataSetChangedRunnable);
    }

    // endregion Instance Methods

    // region Anonymous Classes

    private final Runnable superDataSetChangedRunnable = new Runnable() {
        @Override
        public void run() {
            superNotifyDataSetChanged();
        }
    };

    private final ListObserverListener<Item> internalListObserverListener = new SimpleListObserverListener<Item>() {
        @Override
        public void onGenericChange(ListObserver<Item> listObserver) {
            superNotifyDataSetChangedOnUIThread();
        }
    };

    private final AdapterView.OnItemSelectedListener internalItemSelectedListener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            getAdapter().onItemClicked(position, view);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            // ignored.
        }
    };

    private final AdapterView.OnItemClickListener internalItemClickListener = new AdapterView.OnItemClickListener() {
        @SuppressWarnings("unchecked")
        @Override
        public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
            getAdapter().onItemClicked(position, view);
        }
    };

    private final AdapterView.OnItemLongClickListener internalLongClickListener = new AdapterView.OnItemLongClickListener() {
        @Override
        public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
            return getAdapter().onItemLongClicked(position, view);
        }
    };

    // endregion Anonymous Classes
}