package cn.zhouchaoyuan.excelpanel;

import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.AttributeSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
import android.widget.FrameLayout;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import cn.zhouchaoyuan.utils.Utils;

/**
 * Created by zhouchaoyuan on 2016/12/11.
 * <p>
 * A widget like Excel which can scroll in all directions but it have not split line.
 * Your adapter extends {@link cn.zhouchaoyuan.excelpanel.BaseExcelPanelAdapter} can provide data to excelPanel.
 * If you set OnLoadMoreListener and load historical data in onLoadHistory(), you must call {@link #addHistorySize(int) addHistorySize(int)}
 * to tell ExcelPanel how many pages you have been added.
 * If you want to reset ExcelPanel,just call {@link #reset() reset()}
 * </p>
 */

public class ExcelPanel extends FrameLayout implements ViewTreeObserver.OnGlobalLayoutListener {

    public static final int TAG_KEY = R.id.lib_excel_panel_tag_key;
    public static final int DEFAULT_LENGTH = 56;
    public static final int LOADING_VIEW_WIDTH = 30;

    private int leftCellWidth;
    private int topCellHeight;
    private int normalCellWidth;
    private int loadingViewWidth;
    private int amountAxisX = 0;
    private int amountAxisY = 0;
    private int dividerHeight;
    private boolean hasHeader;
    private boolean hasFooter;
    private boolean dividerLineVisible;

    protected View dividerLine;
    protected RecyclerView mRecyclerView;
    protected RecyclerView topRecyclerView;
    protected RecyclerView leftRecyclerView;
    protected BaseExcelPanelAdapter excelPanelAdapter;
    private static Map<Integer, Integer> indexHeight;
    private List<OnScrollListener> mScrollListeners;

    private OnLoadMoreListener onLoadMoreListener;

    public interface OnLoadMoreListener {
        /**
         * when the loading icon appeared, this method may be called many times
         */
        void onLoadMore();

        /**
         * when the loading icon appeared, this method may be called many times. The excelPanel will dislocation
         * when the data have been added, you must call {@link #addHistorySize(int) addHistorySize(int)}.
         */
        void onLoadHistory();
    }

    public ExcelPanel(Context context) {
        this(context, null);
    }

    public ExcelPanel(Context context, AttributeSet attrs) {
        super(context, attrs);
        TypedArray a = getContext().getTheme().obtainStyledAttributes(
                attrs,
                R.styleable.ExcelPanel,
                0, 0);
        try {
            leftCellWidth = (int) a.getDimension(R.styleable.ExcelPanel_left_cell_width, Utils.dp2px(DEFAULT_LENGTH, getContext()));
            topCellHeight = (int) a.getDimension(R.styleable.ExcelPanel_top_cell_height, Utils.dp2px(DEFAULT_LENGTH, getContext()));
            normalCellWidth = (int) a.getDimension(R.styleable.ExcelPanel_normal_cell_width, Utils.dp2px(DEFAULT_LENGTH, getContext()));
        } finally {
            a.recycle();
        }
        indexHeight = new TreeMap<>();
        loadingViewWidth = Utils.dp2px(LOADING_VIEW_WIDTH, getContext());
        initWidget();
    }

    private void initWidget() {

        //content's RecyclerView
        mRecyclerView = createMajorContent();
        addView(mRecyclerView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
        LayoutParams mlp = (LayoutParams) mRecyclerView.getLayoutParams();
        mlp.leftMargin = leftCellWidth;
        mlp.topMargin = topCellHeight;
        mRecyclerView.setLayoutParams(mlp);

        //top RecyclerView
        topRecyclerView = createTopHeader();
        addView(topRecyclerView, new LayoutParams(LayoutParams.WRAP_CONTENT, topCellHeight));
        LayoutParams tlp = (LayoutParams) topRecyclerView.getLayoutParams();
        tlp.leftMargin = leftCellWidth;
        topRecyclerView.setLayoutParams(tlp);

        //left RecyclerView
        leftRecyclerView = createLeftHeader();
        addView(leftRecyclerView, new LayoutParams(leftCellWidth, LayoutParams.WRAP_CONTENT));
        LayoutParams llp = (LayoutParams) leftRecyclerView.getLayoutParams();
        llp.topMargin = topCellHeight;
        leftRecyclerView.setLayoutParams(llp);

        dividerLine = createDividerToLeftHeader();
        addView(dividerLine, new ViewGroup.LayoutParams(1, ViewGroup.LayoutParams.WRAP_CONTENT));
        LayoutParams lineLp = (LayoutParams) dividerLine.getLayoutParams();
        lineLp.leftMargin = leftCellWidth;
        dividerLine.setLayoutParams(lineLp);
        getViewTreeObserver().addOnGlobalLayoutListener(this);

    }

    @Override
    public void onGlobalLayout() {
        if (dividerHeight == getMeasuredHeight() && getMeasuredHeight() != 0) {
            getViewTreeObserver().removeOnGlobalLayoutListener(this);
        }
        LayoutParams lineLp1 = (LayoutParams) dividerLine.getLayoutParams();
        dividerHeight = lineLp1.height = getMeasuredHeight();
        dividerLine.setLayoutParams(lineLp1);
    }

    protected RecyclerView createTopHeader() {
        RecyclerView recyclerView = new RecyclerView(getContext());
        recyclerView.setLayoutManager(getTopLayoutManager());
        recyclerView.addOnScrollListener(contentScrollListener);
        return recyclerView;
    }

    protected RecyclerView createLeftHeader() {
        RecyclerView recyclerView = new RecyclerView(getContext());
        recyclerView.setLayoutManager(getLeftLayoutManager());
        recyclerView.addOnScrollListener(leftScrollListener);
        return recyclerView;
    }

    protected RecyclerView createMajorContent() {
        RecyclerView recyclerView = new ExcelMajorRecyclerView(getContext());
        recyclerView.setLayoutManager(getLayoutManager());
        recyclerView.addOnScrollListener(contentScrollListener);
        return recyclerView;
    }

    protected View createDividerToLeftHeader() {
        View view = new View(getContext());
        view.setVisibility(GONE);
        view.setBackgroundColor(ContextCompat.getColor(getContext(), R.color.bg_line));
        return view;
    }

    protected RecyclerView.LayoutManager getLayoutManager() {
        if (null == mRecyclerView || null == mRecyclerView.getLayoutManager()) {
            LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
            layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
            return layoutManager;
        }
        return mRecyclerView.getLayoutManager();
    }

    private RecyclerView.LayoutManager getTopLayoutManager() {
        if (null == topRecyclerView || null == topRecyclerView.getLayoutManager()) {
            LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
            layoutManager.setOrientation(LinearLayoutManager.HORIZONTAL);
            return layoutManager;
        }
        return topRecyclerView.getLayoutManager();
    }

    private RecyclerView.LayoutManager getLeftLayoutManager() {
        if (null == leftRecyclerView || null == leftRecyclerView.getLayoutManager()) {
            LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
            layoutManager.setOrientation(LinearLayoutManager.VERTICAL);
            return layoutManager;
        }
        return leftRecyclerView.getLayoutManager();
    }

    /**
     * horizontal listener
     */
    private RecyclerView.OnScrollListener contentScrollListener
            = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            amountAxisX += dx;
            fastScrollTo(amountAxisX, mRecyclerView, loadingViewWidth, hasHeader);
            fastScrollTo(amountAxisX, topRecyclerView, loadingViewWidth, hasHeader);
            if (dx == 0 && dy == 0) {
                return;
            }
            if (mScrollListeners != null) {
                for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                    OnScrollListener listener = mScrollListeners.get(i);
                    if (listener != null) {
                        listener.onScrolled(ExcelPanel.this, dx, dy);
                    }
                }
            }
            LinearLayoutManager manager = (LinearLayoutManager) recyclerView.getLayoutManager();
            int visibleItemCount = recyclerView.getChildCount();
            int totalItemCount = manager.getItemCount();
            int firstVisibleItem = manager.findFirstVisibleItemPosition();
            if (totalItemCount - visibleItemCount <= firstVisibleItem && onLoadMoreListener != null && hasFooter) {
                onLoadMoreListener.onLoadMore();
            }
            if (amountAxisX < loadingViewWidth && onLoadMoreListener != null && hasHeader) {
                onLoadMoreListener.onLoadHistory();
            }
            if (((hasHeader && amountAxisX > loadingViewWidth) || (!hasHeader && amountAxisX > 0)) && dividerLineVisible) {
                dividerLine.setVisibility(VISIBLE);
            } else {
                dividerLine.setVisibility(GONE);
            }
        }
    };

    /**
     * vertical listener
     */
    private RecyclerView.OnScrollListener leftScrollListener
            = new RecyclerView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
            super.onScrollStateChanged(recyclerView, newState);
        }

        @Override
        public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
            super.onScrolled(recyclerView, dx, dy);
            //if (dy == 0) {return;} can't do this if use reset(amountAxisY==0),excelPanel will dislocation
            amountAxisY += dy;
            if (mScrollListeners != null) {
                for (int i = mScrollListeners.size() - 1; i >= 0; i--) {
                    OnScrollListener listener = mScrollListeners.get(i);
                    if (listener != null) {
                        listener.onScrolled(ExcelPanel.this, dx, dy);
                    }
                }
            }
            for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
                if (mRecyclerView.getChildAt(i) instanceof RecyclerView) {
                    RecyclerView recyclerView1 = (RecyclerView) mRecyclerView.getChildAt(i);
                    fastScrollVertical(amountAxisY, recyclerView1);
                }
            }
            fastScrollVertical(amountAxisY, leftRecyclerView);
            if (excelPanelAdapter != null) {
                excelPanelAdapter.setAmountAxisY(amountAxisY);
            }
        }
    };

    void fastScrollVerticalLeft() {
        fastScrollVertical(amountAxisY, leftRecyclerView);
    }

    static void fastScrollVertical(int amountAxis, RecyclerView recyclerView) {
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        if (indexHeight == null) {
            indexHeight = new TreeMap<>();
            //call this method the OnScrollListener's onScrolled will be called,but dx and dy always be zero.
            linearLayoutManager.scrollToPositionWithOffset(0, -amountAxis);
        } else {
            int total = 0, count = 0;
            Iterator<Integer> iterator = indexHeight.keySet().iterator();
            while (null != iterator && iterator.hasNext()) {
                int height = indexHeight.get(iterator.next());
                if (total + height >= amountAxis) {
                    break;
                }
                total += height;
                count++;
            }
            linearLayoutManager.scrollToPositionWithOffset(count, -(amountAxis - total));
        }
    }

    private void fastScrollTo(int amountAxis, RecyclerView recyclerView, int offset, boolean hasHeader) {
        int position = 0, width = normalCellWidth;
        if (amountAxis >= offset && hasHeader) {
            amountAxis -= offset;
            position++;
        }
        position += amountAxis / width;
        amountAxis %= width;
        LinearLayoutManager linearLayoutManager = (LinearLayoutManager) recyclerView.getLayoutManager();
        //call this method the OnScrollListener's onScrolled will be called,but dx and dy always be zero.
        linearLayoutManager.scrollToPositionWithOffset(position, -amountAxis);
    }

    public void setAdapter(BaseExcelPanelAdapter excelPanelAdapter) {
        if (excelPanelAdapter != null) {
            this.excelPanelAdapter = excelPanelAdapter;
            this.excelPanelAdapter.setLeftCellWidth(leftCellWidth);
            this.excelPanelAdapter.setTopCellHeight(topCellHeight);
            this.excelPanelAdapter.setOnScrollListener(leftScrollListener);
            this.excelPanelAdapter.setExcelPanel(this);
            distributeAdapter();
        }
    }

    private void distributeAdapter() {
        if (leftRecyclerView != null) {
            leftRecyclerView.setAdapter(excelPanelAdapter.getLeftRecyclerViewAdapter());
        }
        if (topRecyclerView != null) {
            topRecyclerView.setAdapter(excelPanelAdapter.getTopRecyclerViewAdapter());
        }
        if (mRecyclerView != null) {
            mRecyclerView.setAdapter(excelPanelAdapter.getmRecyclerViewAdapter());
        }
    }

    /**
     * @param dx horizontal distance to scroll
     */
    void scrollBy(int dx) {
        contentScrollListener.onScrolled(mRecyclerView, dx, 0);
    }

    public void setOnLoadMoreListener(OnLoadMoreListener onLoadMoreListener) {
        this.onLoadMoreListener = onLoadMoreListener;
    }

    void setHasHeader(boolean hasHeader) {
        this.hasHeader = hasHeader;
    }

    void setHasFooter(boolean hasFooter) {
        this.hasFooter = hasFooter;
    }

    public boolean canChildScrollUp() {
        return amountAxisY > 0;
    }

    public void reset() {
        if (excelPanelAdapter != null) {
            excelPanelAdapter.disableFooter();
            excelPanelAdapter.disableHeader();
        }
        if (indexHeight == null) {
            indexHeight = new TreeMap<>();
        }
        indexHeight.clear();
        amountAxisY = 0;
        amountAxisX = 0;
        getViewTreeObserver().addOnGlobalLayoutListener(this);
    }

    public void addHistorySize(int size) {
        if (size > 0) {
            contentScrollListener.onScrolled(topRecyclerView, normalCellWidth * size, 0);
        }
    }

    public int findFirstVisibleItemPosition() {
        int position = -1;
        if (mRecyclerView.getLayoutManager() != null && excelPanelAdapter != null) {
            LinearLayoutManager mLinearLayoutManager = (LinearLayoutManager) mRecyclerView.getLayoutManager();
            int firstVisibleItem = mLinearLayoutManager.findFirstVisibleItemPosition();
            if (hasHeader) {
                return firstVisibleItem - 1;
            }
            return firstVisibleItem;
        }
        return position;
    }

    public void enableDividerLine(boolean visible) {
        dividerLineVisible = visible;
    }

    /**
     * use to adjust the height and width of the normal cell
     *
     * @param holder   cell's holder
     * @param position horizontal or vertical position
     */
    public void onAfterBind(RecyclerView.ViewHolder holder, int position) {
        if (holder != null && holder.itemView != null) {
            if (indexHeight == null) {
                indexHeight = new TreeMap<>();
            }
            ViewGroup.LayoutParams layoutParams = holder.itemView.getLayoutParams();
            indexHeight.put(position, layoutParams.height);
        }
    }

    public void addOnScrollListener(OnScrollListener listener) {
        if (mScrollListeners == null) {
            mScrollListeners = new ArrayList<>();
        }
        mScrollListeners.add(listener);
    }

    public void removeOnScrollListener(OnScrollListener listener) {
        if (mScrollListeners != null) {
            mScrollListeners.remove(listener);
        }
    }

    public void clearOnScrollListeners() {
        if (mScrollListeners != null) {
            mScrollListeners.clear();
        }
    }

    /**
     * An OnScrollListener can be added to a ExcelPanel to receive messages when a
     * scrolling event has occurred on that ExcelPanel.
     * <p>
     *
     * @see ExcelPanel#addOnScrollListener(OnScrollListener)
     */
    public abstract static class OnScrollListener {
        /**
         * Callback method to be invoked when the ExcelPanel has been scrolled. This will be
         * called after the scroll has completed.
         * <p>
         * This callback will also be called if visible item range changes after a layout
         * calculation. In that case, dx and dy will be 0.
         *
         * @param excelPanel The ExcelPanel which scrolled.
         * @param dx         The amount of horizontal scroll.
         * @param dy         The amount of vertical scroll.
         */
        public void onScrolled(ExcelPanel excelPanel, int dx, int dy) {
        }
    }

}