/*
 * Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
 *
 * This file is part of Loop Habit Tracker.
 *
 * Loop Habit Tracker is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Loop Habit Tracker is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.isoron.uhabits.activities.common.views;

import android.animation.*;
import android.content.*;
import android.os.*;
import android.util.*;
import android.view.*;
import android.widget.*;

public abstract class ScrollableChart extends View
    implements GestureDetector.OnGestureListener,
               ValueAnimator.AnimatorUpdateListener
{

    private int dataOffset;

    private int scrollerBucketSize = 1;

    private int direction = 1;

    private GestureDetector detector;

    private Scroller scroller;

    private ValueAnimator scrollAnimator;

    private ScrollController scrollController;

    private int maxDataOffset = 10000;

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

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

    public int getDataOffset()
    {
        return dataOffset;
    }

    @Override
    public void onAnimationUpdate(ValueAnimator animation)
    {
        if (!scroller.isFinished())
        {
            scroller.computeScrollOffset();
            updateDataOffset();
        }
        else
        {
            scrollAnimator.cancel();
        }
    }

    @Override
    public boolean onDown(MotionEvent e)
    {
        return true;
    }

    @Override
    public boolean onFling(MotionEvent e1,
                           MotionEvent e2,
                           float velocityX,
                           float velocityY)
    {
        scroller.fling(scroller.getCurrX(), scroller.getCurrY(),
            direction * ((int) velocityX) / 2, 0, 0, getMaxX(), 0, 0);
        invalidate();

        scrollAnimator.setDuration(scroller.getDuration());
        scrollAnimator.start();
        return false;
    }

    private int getMaxX()
    {
        return maxDataOffset * scrollerBucketSize;
    }

    @Override
    public void onRestoreInstanceState(Parcelable state)
    {
        if(!(state instanceof BundleSavedState))
        {
            super.onRestoreInstanceState(state);
            return;
        }

        BundleSavedState bss = (BundleSavedState) state;
        int x = bss.bundle.getInt("x");
        int y = bss.bundle.getInt("y");
        direction = bss.bundle.getInt("direction");
        dataOffset = bss.bundle.getInt("dataOffset");
        maxDataOffset = bss.bundle.getInt("maxDataOffset");
        scroller.startScroll(0, 0, x, y, 0);
        scroller.computeScrollOffset();
        super.onRestoreInstanceState(bss.getSuperState());
    }

    @Override
    public Parcelable onSaveInstanceState()
    {
        Parcelable superState = super.onSaveInstanceState();
        Bundle bundle = new Bundle();
        bundle.putInt("x", scroller.getCurrX());
        bundle.putInt("y", scroller.getCurrY());
        bundle.putInt("dataOffset", dataOffset);
        bundle.putInt("direction", direction);
        bundle.putInt("maxDataOffset", maxDataOffset);
        return new BundleSavedState(superState, bundle);
    }

    @Override
    public boolean onScroll(MotionEvent e1, MotionEvent e2, float dx, float dy)
    {
        if (scrollerBucketSize == 0) return false;

        if (Math.abs(dx) > Math.abs(dy))
        {
            ViewParent parent = getParent();
            if (parent != null) parent.requestDisallowInterceptTouchEvent(true);
        }


        dx = - direction * dx;
        dx = Math.min(dx, getMaxX() - scroller.getCurrX());
        scroller.startScroll(scroller.getCurrX(), scroller.getCurrY(), (int) dx,
            (int) dy, 0);

        scroller.computeScrollOffset();
        updateDataOffset();
        return true;
    }

    @Override
    public void onShowPress(MotionEvent e)
    {

    }

    @Override
    public boolean onSingleTapUp(MotionEvent e)
    {
        return false;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event)
    {
        return detector.onTouchEvent(event);
    }

    public void setScrollDirection(int direction)
    {
        if (direction != 1 && direction != -1)
            throw new IllegalArgumentException();
        this.direction = direction;
    }

    @Override
    public void onLongPress(MotionEvent e)
    {

    }

    public void setMaxDataOffset(int maxDataOffset)
    {
        this.maxDataOffset = maxDataOffset;
        this.dataOffset = Math.min(dataOffset, maxDataOffset);
        scrollController.onDataOffsetChanged(this.dataOffset);
        postInvalidate();
    }

    public void setScrollController(ScrollController scrollController)
    {
        this.scrollController = scrollController;
    }

    public void setScrollerBucketSize(int scrollerBucketSize)
    {
        this.scrollerBucketSize = scrollerBucketSize;
    }

    private void init(Context context)
    {
        detector = new GestureDetector(context, this);
        scroller = new Scroller(context, null, true);
        scrollAnimator = ValueAnimator.ofFloat(0, 1);
        scrollAnimator.addUpdateListener(this);
        scrollController = new ScrollController() {};
    }

    private void updateDataOffset()
    {
        int newDataOffset = scroller.getCurrX() / scrollerBucketSize;
        newDataOffset = Math.max(0, newDataOffset);
        newDataOffset = Math.min(maxDataOffset, newDataOffset);

        if (newDataOffset != dataOffset)
        {
            dataOffset = newDataOffset;
            scrollController.onDataOffsetChanged(dataOffset);
            postInvalidate();
        }
    }

    public interface ScrollController
    {
        default void onDataOffsetChanged(int newDataOffset) {}
    }
}
