package com.dodola.bubblecloud;

import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;

import java.util.ArrayList;
import java.util.Arrays;

/**
 * Created by dodola on 15/7/23.
 */
public class BubbleCloudView<T extends Adapter> extends AdapterView<T> {

    private T mAdapter;

    private int lastX;
    private int lastY;
    private int deltaX;
    private int deltaY;
    private int scrollMoveX;
    private int scrollMoveY;
    private int scrollX;
    private int scrollY;


    private int mTouchState = TOUCH_STATE_RESTING;
    private static final int TOUCH_STATE_RESTING = 0;
    private static final int TOUCH_STATE_CLICK = 1;
    private static final int TOUCH_STATE_SCROLL = 2;
    private static final int INVALID_INDEX = -1;
    private static final int TOUCH_SCROLL_THRESHOLD = 10;

    private Rect mRect;
    private int screenW;
    private int screenH;
    private int centerW;
    private int centerH;
    private ArrayList<Integer[]> hexCube;
    private ArrayList<XY> hexCubeOrtho = new ArrayList<>();
    private ArrayList<Rrad> hexCubePolar = new ArrayList<>();
    private ArrayList<Rdr> hexCubeSphere = new ArrayList<>();
    private int sphereR;
    private int hexR;
    private int itemSize;
    private int edgeSize;
    private float animAlpha = 1;
    int scrollRangeX = 30;
    int scrollRangeY = 10;

    private void startTouch(final MotionEvent event) {
        lastX = (int) event.getX();
        lastY = (int) event.getY();
        deltaX = 0;
        deltaY = 0;
        scrollMoveX += deltaX;
        scrollMoveY += deltaY;
        scrollX = scrollMoveX;
        scrollY = scrollMoveY;
        mTouchState = TOUCH_STATE_CLICK;
    }

    private void endTouch() {
        int veloX = deltaX;
        int veloY = deltaY;
        final int distanceX = veloX * 10;
        final int distanceY = veloY * 10;

        ValueAnimator endAnim = ValueAnimator.ofFloat(0, 1);
        endAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {

                scrollMoveX = scrollX;
                scrollMoveY = scrollY;
                int steps = 16;
                int step = (int) (steps * animation.getAnimatedFraction());

                int inertiaX = (int) (easeOutCubic
                        (step, 0, distanceX, steps) - easeOutCubic((step - 1), 0, distanceX, steps));
                int inertiaY = (int) (easeOutCubic
                        (step, 0, distanceY, steps) - easeOutCubic((step - 1), 0, distanceY, steps));

                scrollX += inertiaX;
                scrollY += inertiaY;

                if (scrollX > scrollRangeX) {
                    scrollX -= (scrollX - scrollRangeX) / 4;
                } else if (scrollX < -scrollRangeX) {
                    scrollX -= (scrollX + scrollRangeX) / 4;
                }

                if (scrollY > scrollRangeY) {
                    scrollY -= (scrollY - scrollRangeY) / 4;
                } else if (scrollY < -scrollRangeY) {
                    scrollY -= (scrollY + scrollRangeY) / 4;
                }
                iconMapRefresh(sphereR, hexR, scrollX, scrollY);
                requestLayout();

            }
        });
        endAnim.setDuration(300);
        endAnim.start();

        mTouchState = TOUCH_STATE_RESTING;
    }


    public BubbleCloudView(Context context) {
        super(context);
        init();
    }

    public BubbleCloudView(Context context, AttributeSet attrs) {
        super(context, attrs);
        init();
    }

    public BubbleCloudView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init();
    }

    @Override
    public T getAdapter() {
        return mAdapter;
    }

    @Override
    public void setAdapter(T adapter) {
        mAdapter = adapter;
        removeAllViewsInLayout();
        requestLayout();
    }

    @Override
    public View getSelectedView() {
        return null;

    }

    @Override
    public void setSelection(int position) {
        
    }

    @Override
    public boolean onInterceptTouchEvent(final MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startTouch(event);
                return false;

            case MotionEvent.ACTION_MOVE:
                return startScrollIfNeeded(event);

            default:
                endTouch();
                return false;
        }
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                startTouch(event);
                break;

            case MotionEvent.ACTION_MOVE:
                Log.d("BubbleCloudView", "===ActionMove===");

                if (mTouchState == TOUCH_STATE_CLICK) {
                    startScrollIfNeeded(event);
                }
                if (mTouchState == TOUCH_STATE_SCROLL) {
                    scrollContainer((int) event.getX(), (int) event.getY());
                    Log.d("BubbleCloudView", "===mTouchScroll===");
                }
                break;

            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                if (mTouchState == TOUCH_STATE_CLICK) {
                    clickChildAt((int) event.getX(), (int) event.getY());
                }
                endTouch();
                break;

            default:
                endTouch();
                break;
        }
        return true;

    }

    private boolean startScrollIfNeeded(final MotionEvent event) {
        final int xPos = (int) event.getX();
        final int yPos = (int) event.getY();
        if (xPos < lastX - TOUCH_SCROLL_THRESHOLD
                || xPos > lastX + TOUCH_SCROLL_THRESHOLD
                || yPos < lastY - TOUCH_SCROLL_THRESHOLD
                || yPos > lastY + TOUCH_SCROLL_THRESHOLD) {
            mTouchState = TOUCH_STATE_SCROLL;
            return true;
        }
        return false;
    }

    private void scrollContainer(int x, int y) {


        deltaX = x - lastX;
        deltaY = y - lastY;

        lastX = x;
        lastY = y;

        scrollMoveX += deltaX;
        scrollMoveY += deltaY;

        scrollX = scrollMoveX;
        scrollY = scrollMoveY;


        if (scrollMoveX > scrollRangeX) {
            scrollX = scrollRangeX + (scrollMoveX - scrollRangeX) / 2;
        } else if (scrollX < -scrollRangeX) {
            scrollX = -scrollRangeX + (scrollMoveX + scrollRangeX) / 2;
        }

        if (scrollMoveY > scrollRangeY) {
            scrollY = scrollRangeY + (scrollMoveY - scrollRangeY) / 2;
        } else if (scrollY < -scrollRangeY) {
            scrollY = -scrollRangeY + (scrollMoveY + scrollRangeY) / 2;
        }


        iconMapRefresh(sphereR, hexR,
                scrollX,
                scrollY
        );
        requestLayout();
    }

    private void clickChildAt(final int x, final int y) {
        final int index = getContainingChildIndex(x, y);
        if (index != INVALID_INDEX) {
            final View itemView = getChildAt(index);
            final int position = index;
            final long id = mAdapter.getItemId(position);
            performItemClick(itemView, position, id);
        }
    }

    private int getContainingChildIndex(final int x, final int y) {
        if (mRect == null) {
            mRect = new Rect();
        }
        for (int index = 0; index < getChildCount(); index++) {
            getChildAt(index).getHitRect(mRect);
            if (mRect.contains(x, y)) {
                return index;
            }
        }
        return INVALID_INDEX;
    }

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);

        if (mAdapter == null) {
            return;
        }

        if (getChildCount() == 0) {
            int position = 0;
            while (position < mAdapter.getCount()) {
                View newBottomChild = mAdapter.getView(position, null, this);
                addAndMeasureChild(newBottomChild);
                position++;
            }
        }

        positionItems();
    }

    private void addAndMeasureChild(View child) {
        LayoutParams params = child.getLayoutParams();
        if (params == null) {
            params = new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
        }
        addViewInLayout(child, -1, params, true);
        child.measure(MeasureSpec.EXACTLY | itemSize, MeasureSpec.EXACTLY | itemSize);
    }

    private void positionItems() {

        for (int index = 0; index < getChildCount(); index++) {
            View child = getChildAt(index);

            final XY xy = hexCubeOrtho.get(index);

            int width = child.getMeasuredWidth();
            int height = child.getMeasuredHeight();
            int offsetX = centerW - edgeSize;
            int offsetY = centerH - edgeSize;

            child.layout((int) xy.x + offsetX, (int) xy.y + offsetY, (int) xy.x + offsetX + width, (int) xy.y + offsetY + height);
            child.setScaleX(xy.scale);
            child.setScaleY(xy.scale);
            child.setAlpha(animAlpha);
        }
    }

    private void init() {

        this.hexCube = new ArrayList<>();
        for (int i = 0; i < 4; i++)
            for (int j = -i; j <= i; j++)
                for (int k = -i; k <= i; k++)
                    for (int l = -i; l <= i; l++)
                        if (Math.abs(j) + Math.abs(k) + Math.abs(l) == i * 2 && j + k + l == 0) {
                            final Integer[] integers = {j, k, l};
                            this.hexCube.add(integers);
                            Log.d("BubbleCloudView", "hexCube:" + Arrays.deepToString(integers));
                        }

        this.screenW = getResources().getDimensionPixelSize(R.dimen.screenw);
        this.screenH = getResources().getDimensionPixelSize(R.dimen.screenh);
        this.sphereR = getResources().getDimensionPixelSize(R.dimen.sphereR);
        this.hexR = getResources().getDimensionPixelSize(R.dimen.hexR);
        this.itemSize = getResources().getDimensionPixelSize(R.dimen.item_size);
        this.edgeSize = getResources().getDimensionPixelSize(R.dimen.edge_size);
        this.centerW = this.screenW / 2;
        this.centerH = this.screenH / 2;
        iconMapRefresh(sphereR, hexR + 100,
                0,
                0);
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        //开启动画
        startEnterAnim();
    }

    private void iconMapRefresh(float sphereR, float hexR, float scrollX, float scrollY) {
//        Log.d("BubbleCloudView", "sphereR:" + sphereR + ",hexR:" + hexR + ",scrollX:" + scrollX + ",scrollY:" + scrollY);

        hexCubeOrtho.clear();
        hexCubePolar.clear();
        hexCubeSphere.clear();

        for (int i = 0; i < hexCube.size(); i++) {
            final Integer[] integers = this.hexCube.get(i);
            XY tempxy = new XY();
            tempxy.x = (integers[1] + integers[0] / 2f) * hexR + scrollX;
            tempxy.y = (float) (Math.sqrt(3) / 2 * integers[0] * hexR + scrollY);
            hexCubeOrtho.add(tempxy);
        }

        for (int i = 0; i < hexCubeOrtho.size(); i++) {
            final XY hexCubexy = hexCubeOrtho.get(i);
            hexCubePolar.add(ortho2polar(hexCubexy.x, hexCubexy.y));
        }


        for (int i = 0; i < hexCubePolar.size(); i++) {

            final Rrad rrad = hexCubePolar.get(i);

            float rad = rrad.r / sphereR;
            float r, deepth;
            if (rad < Math.PI / 2) {
                r = rrad.r * swing((float) (rad / (Math.PI / 2)), 1.5f, -0.5f, 1f);
                deepth = easeInOutCubic((float) (rad / (Math.PI / 2)), 1f, -0.5f, 1f);
            } else {
                r = rrad.r;
                deepth = easeInOutCubic(1f, 1f, -0.5f, 1f);
            }

            Rdr rdr = new Rdr();
            rdr.r = r;
            rdr.deepth = deepth;
            rdr.rad = rrad.rad;
            hexCubeSphere.add(rdr);
        }
//        Log.d("BubbleCloudView", "iconMapRefresh:resultMap1====" + hexCubeSphere);

        hexCubeOrtho.clear();
        for (int i = 0; i < hexCubeSphere.size(); i++) {
            final Rdr rdr = hexCubeSphere.get(i);
            hexCubeOrtho.add(polar2ortho(rdr.r, rdr.rad));
        }

        for (int i = 0; i < hexCubeOrtho.size(); i++) {
            final XY xy = hexCubeOrtho.get(i);
            xy.x = Math.round(xy.x * 10) / 10;
            xy.y = (float) (Math.round(xy.y * 10) / 10 * 1.14);
        }


        final float edge = edgeSize;
        for (int i = 0; i < hexCubeOrtho.size(); i++) {
            final XY xy = hexCubeOrtho.get(i);
            final Rdr rdr = hexCubeSphere.get(i);
            if (Math.abs(xy.x) > this.screenW / 2 - edge || Math.abs(xy.y) > this.screenH / 2 - edge) {
                xy.scale = rdr.deepth * 0.4f;
            } else if (Math.abs(xy.x) > this.screenW / 2 - 2 * edge && Math.abs(xy.y) > this.screenH / 2 - 2 * edge) {
                xy.scale = Math.min(rdr.deepth * easeInOutSine(this.screenW / 2 - Math.abs(xy.x) - edge, 0.4f, 0.6f, edge), rdr.deepth * easeInOutSine(this.screenH / 2 - Math.abs(xy.y) - edge, 0.3f, 0.7f, edge));
            } else if (Math.abs(xy.x) > this.screenW / 2 - 2 * edge) {
                xy.scale = rdr.deepth * easeOutSine(this.screenW / 2 - Math.abs(xy.x) - edge, 0.4f, 0.6f, edge);
            } else if (Math.abs(xy.y) > this.screenH / 2 - 2 * edge) {
                xy.scale = rdr.deepth * easeOutSine(this.screenH / 2 - Math.abs(xy.y) - edge, 0.4f, 0.6f, edge);
            } else {
                xy.scale = rdr.deepth;
            }
        }

        for (int i = 0; i < hexCubeOrtho.size(); i++) {
            final XY xy = hexCubeOrtho.get(i);
            if (xy.x < -this.screenW / 2 + 2 * edge) {
                xy.x += easeInSine(this.screenW / 2 - Math.abs(xy.x) - 2 * edge, 0, 6f, 2 * edge);
            } else if (xy.x > this.screenW / 2 - 2 * edge) {
                xy.x += easeInSine(this.screenW / 2 - Math.abs(xy.x) - 2 * edge, 0, -6f, 2 * edge);
            }
            if (xy.y < -this.screenH / 2 + 2 * edge) {
                xy.y += easeInSine(this.screenH / 2 - Math.abs(xy.y) - 2 * edge, 0, 8f, 2 * edge);
            } else if (xy.y > this.screenH / 2 - 2 * edge) {
                xy.y += easeInSine(this.screenH / 2 - Math.abs(xy.y) - 2 * edge, 0, -8f, 2 * edge);
            }
        }
//        Log.d("BubbleCloudView", "iconMapRefresh:resultMap2====" + hexCubeOrtho);

    }

    private float easeInSine(float t, float b, float c, float d) {
        return (float) (-c * Math.cos(t / d * (Math.PI / 2)) + c + b);
    }

    private float swing(float t, float b, float c, float d) {
        return -c * (t /= d) * (t - 2) + b;
    }

    private float easeInOutCubic(float t, float b, float c, float d) {
        if ((t /= d / 2) < 1)
            return c / 2 * t * t * t + b;
        return c / 2 * ((t -= 2) * t * t + 2) + b;
    }

    private float easeInOutSine(float t, float b, float c, float d) {
        return (float) (-c / 2 * (Math.cos(Math.PI * t / d) - 1) + b);
    }

    private float easeOutSine(float t, float b, float c, float d) {
        return (float) (c * Math.sin(t / d * (Math.PI / 2)) + b);
    }

    private XY polar2ortho(float r, float rad) {
        float x = (float) (r * Math.cos(rad));
        float y = (float) (r * Math.sin(rad));
        XY tempxy = new XY();
        tempxy.x = x;
        tempxy.y = y;
        return tempxy;
    }

    private Rrad ortho2polar(float x, float y) {
        float r = (float) Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
        float rad = (float) Math.atan2(y, x);
        Rrad temprad = new Rrad();
        temprad.r = r;
        temprad.rad = rad;
        return temprad;
    }

    private void startEnterAnim() {
        ValueAnimator startAnim = ValueAnimator.ofFloat(0, 1);
        startAnim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                final float v = easeOutCubic((float) animation.getAnimatedValue() * 36, hexR * 2, -hexR, 36f);
                iconMapRefresh(sphereR, v, 0, 0);
                animAlpha = animation.getAnimatedFraction();
                requestLayout();
            }
        });
        startAnim.setDuration(1000);
        startAnim.start();
    }

    private float easeOutCubic(float t, float b, float c, float d) {
        return c * ((t = t / d - 1) * t * t + 1) + b;
    }

    private class XY {
        float x, y, scale;

        @Override
        public String toString() {
            return "XY{" +
                    "scale=" + scale +
                    ", x=" + x +
                    ", y=" + y +
                    '}';
        }
    }

    private class Rrad {
        float r, rad;

        @Override
        public String toString() {
            return "Rrad{" +
                    "r=" + r +
                    ", rad=" + rad +
                    '}';
        }
    }

    private class Rdr {
        float r, deepth, rad;

        @Override
        public String toString() {
            return "Rdr{" +
                    "deepth=" + deepth +
                    ", r=" + r +
                    ", rad=" + rad +
                    '}';
        }
    }


}