/*
 * The MIT License (MIT)
 *
 * Copyright (c) 2015 Omada Health, Inc
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package com.github.omadahealth.slidepager.lib.views;

import android.content.Context;
import android.content.res.TypedArray;
import android.databinding.DataBindingUtil;
import android.support.v4.view.ViewPager;
import android.view.LayoutInflater;
import android.view.View;

import com.daimajia.androidanimations.library.Techniques;
import com.daimajia.androidanimations.library.YoYo;
import com.daimajia.easing.Glider;
import com.daimajia.easing.Skill;
import com.github.omadahealth.slidepager.lib.R;
import com.github.omadahealth.slidepager.lib.databinding.ViewSlideBinding;
import com.github.omadahealth.slidepager.lib.interfaces.OnSlideListener;
import com.github.omadahealth.slidepager.lib.interfaces.OnSlidePageChangeListener;
import com.github.omadahealth.slidepager.lib.utils.ProgressAttr;
import com.github.omadahealth.typefaceview.TypefaceTextView;
import com.github.omadahealth.typefaceview.TypefaceType;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by stoyan on 4/7/15.
 */
public class SlideView extends AbstractSlideView {
    /**
     * The tag for logging
     */
    private static final String TAG = "SlideView";

    /**
     * The duration for the animation for {@link ViewSlideBinding#selectedDayImageView} when {@link OnSlideListener#onDaySelected(int, int)}
     * is allowed
     */
    private static final int SELECTION_ANIMATION_DURATION = 500;

    /**
     * The duration for the animation for {@link ViewSlideBinding#selectedDayImageView} when {@link OnSlideListener#onDaySelected(int, int)}
     * is not allowed
     */
    private static final long NOT_ALLOWED_SHAKE_ANIMATION_DURATION = 500;

    /**
     * An array that holds all the {@link ProgressView} for this layout
     */
    private List<ProgressView> mProgressList = new ArrayList<>(7);

    /**
     * Indicates if the user configured the style to be reanimating each time we are scrolling the {@link com.github.omadahealth.slidepager.lib.SlidePager}
     * or not.
     */
    private boolean mHasToReanimate;

    /**
     * True of we want to show {@link ViewSlideBinding#leftTextView}
     */
    private boolean mShowLeftText;

    /**
     * True of we want to show {@link ViewSlideBinding#rightTextView}
     */
    private boolean mShowRightText;

    /**
     * True of we want to shake the view in {@link #toggleSelectedViews(int)}
     */
    private boolean mShakeIfNotSelectable;

    /**
     * True if we want to show {@link #mSelectedView}
     */
    private boolean mShowSelectedBar;

    /**
     * The callback listener for when views are clicked
     */
    private OnSlideListener mCallback;

    /**
     * The default animation time
     */
    private static final int DEFAULT_PROGRESS_ANIMATION_TIME = 1000;

    /**
     * The xml attributes of this view
     */
    private TypedArray mAttributes;

    /**
     * The {@link AnimatorSet} used to animate the Slider selected day
     */
    private AnimatorSet mAnimationSet;

    /**
     * The int tag of the selected {@link ProgressView} we store so that we can
     * translate the {@link ViewSlideBinding#selectedDayImageView} to the same day when we transition between
     * different instances of this class
     */
    private static int mSelectedView;

    /**
     * The position of this page within the {@link com.github.omadahealth.slidepager.lib.SlidePager}
     */
    private int mPagePosition;

    /**
     *
     */
    private boolean mHasToSetTextOnClick;

    /**
     * The text for the {@link ViewSlideBinding#leftTextView}
     */
    private String mLeftText;

    /**
     * The text for the {@link ViewSlideBinding#rightTextView}
     */
    private String mRightText;

    /**
     * The animation time in milliseconds that we animate the progress
     */
    private int mProgressAnimationTime = DEFAULT_PROGRESS_ANIMATION_TIME;

    /**
     * A user defined {@link ViewPager.OnPageChangeListener}
     */
    private OnSlidePageChangeListener mUserPageListener;

    private boolean firstAnimation = true;

    /**
     * The binding object created in {@link #init(Context, TypedArray, int, OnSlidePageChangeListener)}
     */
    private ViewSlideBinding mBinding;

    public SlideView(Context context) {
        this(context, null, -1, null, null, null);
    }

    public SlideView(Context context, TypedArray attributes, int pagePosition, OnSlidePageChangeListener pageListener, String leftText, String rightText) {
        super(context, null);
        this.mLeftText = leftText;
        this.mRightText = rightText;
        init(context, attributes, pagePosition, pageListener);
    }

    private void loadStyledAttributes(TypedArray attributes) {
        mAttributes = attributes;
        if (mAttributes != null) {
            mHasToReanimate = mAttributes.getBoolean(R.styleable.SlidePager_slide_pager_reanimate_slide_view, true);
            mShowLeftText = attributes.getBoolean(R.styleable.SlidePager_slide_show_week, true);
            mShowRightText = attributes.getBoolean(R.styleable.SlidePager_slide_show_date, true);
            mShakeIfNotSelectable = attributes.getBoolean(R.styleable.SlidePager_slide_shake_if_not_selectable, true);
            mShowSelectedBar = attributes.getBoolean(R.styleable.SlidePager_slide_show_selected_bar, true);
            mHasToSetTextOnClick = attributes.getBoolean(R.styleable.SlidePager_slide_set_text_on_click, true);

            mBinding.selectedDayImageView.setVisibility(mShowSelectedBar ? VISIBLE : GONE);
            mBinding.leftTextView.setVisibility(mShowLeftText && mLeftText != null ? VISIBLE : GONE);
            mBinding.rightTextView.setVisibility(mShowRightText && mRightText != null ? VISIBLE : GONE);

            if (mShowLeftText && mBinding.leftTextView != null && mLeftText != null) {
                mBinding.leftTextView.setText(mLeftText, true);
            }

            if (mShowRightText && mBinding.rightTextView != null && mRightText != null) {
                mBinding.rightTextView.setText(mRightText);
            }
        }
    }

    /**
     * Bind the view and init the listeners and attrs
     *
     * @param context
     */
    private void init(Context context, TypedArray attributes, int pagePosition, OnSlidePageChangeListener pageListener) {
        if (!isInEditMode()) {
            this.mPagePosition = pagePosition;
            this.mUserPageListener = pageListener;
            this.mAttributes = attributes;

            LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
            mBinding = DataBindingUtil.inflate(inflater, R.layout.view_slide, this, true);

            mAnimationSet = new AnimatorSet();
            injectViews();
            setListeners();
            loadStyledAttributes(attributes);
        }
    }

    /**
     * Inject the views into {@link #mProgressList}
     */
    private void injectViews() {
        mProgressList.add(mBinding.progress1.loadStyledAttributes(mAttributes, mUserPageListener != null ? mUserPageListener.getDayProgress(mPagePosition, 0) : null));
        mProgressList.add(mBinding.progress2.loadStyledAttributes(mAttributes, mUserPageListener != null ? mUserPageListener.getDayProgress(mPagePosition, 1) : null));
        mProgressList.add(mBinding.progress3.loadStyledAttributes(mAttributes, mUserPageListener != null ? mUserPageListener.getDayProgress(mPagePosition, 2) : null));
        mProgressList.add(mBinding.progress4.loadStyledAttributes(mAttributes, mUserPageListener != null ? mUserPageListener.getDayProgress(mPagePosition, 3) : null));
        mProgressList.add(mBinding.progress5.loadStyledAttributes(mAttributes, mUserPageListener != null ? mUserPageListener.getDayProgress(mPagePosition, 4) : null));
        mProgressList.add(mBinding.progress6.loadStyledAttributes(mAttributes, mUserPageListener != null ? mUserPageListener.getDayProgress(mPagePosition, 5) : null));
        mProgressList.add(mBinding.progress7.loadStyledAttributes(mAttributes, mUserPageListener != null ? mUserPageListener.getDayProgress(mPagePosition, 6) : null));

        mBinding.leftTextView.setTypeface(TypefaceTextView.getFont(getContext(), TypefaceType.getTypeface(TypefaceType.getDefaultTypeface(getContext())).getAssetFileName()));
        mBinding.rightTextView.setTypeface(TypefaceTextView.getFont(getContext(), TypefaceType.getTypeface(TypefaceType.getDefaultTypeface(getContext())).getAssetFileName()));

        mBinding.selectedDayImageView.setSelectedViewId(mProgressList.get(SlideView.getSelectedView()).getId());
    }

    /**
     * Animates the translation of the {@link ViewSlideBinding#selectedDayImageView}
     *
     * @param view          The view to use to set the animation position
     * @param startPosition The starting x position for the animated view
     */
    public void animateSelectedTranslation(final View view, float startPosition) {
        final Float offset = -1f * this.getWidth() + view.getWidth() / 2 + view.getX();
        mBinding.selectedDayImageView.setTag(R.id.selected_day_image_view, offset);
        mBinding.selectedDayImageView.setSelectedViewId(view.getId());


        if (mAnimationSet == null) {
            mAnimationSet = new AnimatorSet();
        }
        mAnimationSet.playSequentially(Glider.glide(Skill.QuadEaseInOut, SELECTION_ANIMATION_DURATION, ObjectAnimator.ofFloat(mBinding.selectedDayImageView, "x", startPosition, offset)));
        mAnimationSet.setDuration(SELECTION_ANIMATION_DURATION);
        mAnimationSet.start();
    }

    /**
     * Calls {@link #animateSelectedTranslation(View, float)} with the start
     * position set to the {@link ViewSlideBinding#selectedDayImageView#getX()}
     *
     * @param view The view to use to set the animation position
     */
    public void animateSelectedTranslation(View view) {
        animateSelectedTranslation(view, mBinding.selectedDayImageView.getX());
    }

    /**
     * Set up listeners for all the views in {@link #mProgressList}
     */
    private void setListeners() {
        for (final ProgressView progressView : mProgressList) {
            if (progressView != null) {

                progressView.setOnClickListener(new OnClickListener() {
                    @Override
                    public void onClick(View view) {
                        int index = progressView.getIntTag();
                        boolean allowed = true;
                        if (mCallback != null) {
                            allowed = mCallback.isDaySelectable(mPagePosition, index);
                        }
                        if (allowed) {
                            if (mCallback != null) {
                                mCallback.onDaySelected(mPagePosition, index);
                            }
                            toggleSelectedViews(index);
                            animateSelectedTranslation(view);
                        } else {
                            if (mShakeIfNotSelectable) {
                                YoYo.with(Techniques.Shake)
                                        .duration(NOT_ALLOWED_SHAKE_ANIMATION_DURATION)
                                        .playOn(SlideView.this);
                            }
                        }
                    }
                });
            }
        }
    }

    /**
     * Animates the {@link ProgressView}s of this page and sets their attributes.
     *
     * @param listener   A listener to get {@link OnSlidePageChangeListener#getDayProgress(int, int)}
     * @param attributes The attributes to use
     */
    @SuppressWarnings("unchecked")
    @Override
    public void animatePage(OnSlidePageChangeListener listener, TypedArray attributes) {
        super.animatePage(listener, attributes);
        final List<View> children = (List<View>) getTag(R.id.slide_transformer_tag_key);
        if (children != null) {
            for (final View child : children) {
                if (child instanceof ProgressView) {
                    ProgressAttr progressAttr = listener.getDayProgress(mPagePosition, ((ProgressView) child).getIntTag());
                    ((ProgressView) child).loadStyledAttributes(attributes, progressAttr);
                    animateProgress((ProgressView) child, children, progressAttr);
                }
            }
            animateSelectedTranslation(mProgressList.get(mSelectedView));

            if (mCallback != null) {
                mCallback.onDaySelected(mPagePosition, mSelectedView);
            }
        }
    }

    /**
     * Displays or hides the streaks of all the {@link ProgressView}s
     *
     * @param show True to animate them visible, false to immediately hide them
     */
    @Override
    @SuppressWarnings("unchecked")
    public void resetStreaks(boolean show) {
        final List<View> children = (List<View>) getTag(R.id.slide_transformer_tag_key);
        if (children != null) {
            for (final View child : children) {
                if (child instanceof ProgressView) {
                    final ProgressView progressView = (ProgressView) child;
                    progressView.showStreak(show, ProgressView.STREAK.RIGHT_STREAK);
                    progressView.showStreak(show, ProgressView.STREAK.LEFT_STREAK);
                    progressView.showStreak(show, ProgressView.STREAK.CENTER_STREAK);

                    if (mHasToReanimate) {
                        progressView.showCheckMark(show);
                    }
                }
            }
        }
    }

    @Override
    @SuppressWarnings("unchecked")
    public void animateStreaks(OnSlidePageChangeListener listener, TypedArray attributes) {
        final List<View> children = (List<View>) getTag(R.id.slide_transformer_tag_key);
        if (children != null) {
            for (final View child : children) {
                if (child instanceof ProgressView) {
                    final ProgressView progressView = (ProgressView) child;
                    ProgressAttr progressAttr = listener.getDayProgress(mPagePosition, ((ProgressView) child).getIntTag());
                    ((ProgressView) child).loadStyledAttributes(attributes, progressAttr);
                    progressView.animateStreaks();
                }
            }
        }
    }

    /**
     * Resets the state of this page, usually called when we change pages in the {@link com.github.omadahealth.slidepager.lib.SlidePager}
     *
     * @param attributes The attributes to reset the views to
     */
    @SuppressWarnings("unchecked")
    public void resetPage(TypedArray attributes) {
        this.setVisibility(View.VISIBLE);
        this.setAlpha(1f);

        loadStyledAttributes(attributes);
        mSelectedView = getSelectableIndex();

        resetStreaks(false);
        getSelectedImageView().resetView();
        final List<View> children = (List<View>) getTag(R.id.slide_transformer_tag_key);
        if (children != null) {
            for (final View child : children) {
                if (child instanceof ProgressView) {
                    ProgressView progressView = (ProgressView) child;
                    progressView.reset();
                }
            }
        }
        toggleSelectedViews(mSelectedView);

    }

    /**
     * Animates the progress of a {@link ProgressView}
     *
     * @param view         The view to animate
     * @param children     The sibling views we use to evaluate streaks showing
     * @param progressAttr The {@link ProgressAttr} for this view got from the listener
     */
    private void animateProgress(ProgressView view, List<View> children, ProgressAttr progressAttr) {
        if (progressAttr != null) {
            ProgressAttr progress = progressAttr;
            view.animateProgress(0, progress, mProgressAnimationTime, children);
        }
    }

    /**
     * Public method for animating an individual {@link ProgressView}
     *
     * @param index    The index from [0,6] of the view
     * @param progress The {@link ProgressAttr} to animate
     */
    @SuppressWarnings("unchecked")
    public void animateProgressView(int index, ProgressAttr progress) {
        final List<View> children = (List<View>) getTag(R.id.slide_transformer_tag_key);
        ProgressView view = getProgressView(index);
        if (view != null && view.isShowCircularBar()) {
            view.animateProgress(view.getProgress(), progress, mProgressAnimationTime, children);
        }
    }

    /**
     * Sets the selected {@link ProgressView}
     *
     * @param selected The index of the selected view in {@link #mProgressList}
     */
    private void toggleSelectedViews(int selected) {
        if(!mHasToSetTextOnClick) {
            return;
        }
        mSelectedView = selected;
        for (ProgressView day : mProgressList) {
            if (day.getIntTag() == mSelectedView) {
                day.isSelected(true);
                if (mCallback != null) {
                    String label = mCallback.getDayTextLabel(mPagePosition, day.getIntTag());
                    if (label != null) {
                        day.setProgressText(label);
                    } else {
                        day.setProgressText();
                    }

                } else {
                    day.setProgressText();
                }
                continue;
            }
            day.isSelected(false);
            day.setProgressText();
        }
    }

    /**
     * Checks to see what index is selectable on {@link #animatePage(OnSlidePageChangeListener, TypedArray)}
     * so that we don't automatically select a non selectable date in {@link com.github.omadahealth.slidepager.lib.SlidePager#resetPage(int)}
     *
     * @return The largest index selectable before {#mSelectedView}, if not the largest index selectable in {@link SlideView} or 0 if non are
     */
    public int getSelectableIndex() {
        if (mCallback == null) {
            return mSelectedView;
        }
        if (mCallback.isDaySelectable(mPagePosition, mSelectedView)) {
            return mSelectedView;
        }

        //Try any position lower than the current one
        for (int i = mSelectedView; i >= 0; i--) {
            if (mCallback.isDaySelectable(mPagePosition, i)) {
                return i;
            }
        }

        //Try any position greater
        if (mProgressList != null) {
            for (int i = mProgressList.size() - 1; i >= mSelectedView; i--) {
                if (mCallback.isDaySelectable(mPagePosition, i)) {
                    return i;
                }
            }
        }

        //Default to 0
        return 0;
    }

    /**
     * Sets the listener for click events in this view
     *
     * @param listener
     */
    public void setListener(OnSlideListener listener) {
        this.mCallback = listener;
    }

    /**
     * Returns the {@link ProgressView} at the given index
     *
     * @param index The index from [0,6]
     * @return The view
     */
    public ProgressView getProgressView(int index) {
        if (mProgressList == null || index < 0 || index > mProgressList.size() - 1) {
            return null;
        }
        return mProgressList.get(index);
    }

    /**
     * Returns the view that displays the currently selected {@link ProgressView}
     *
     * @return
     */
    public SelectedImageView getSelectedImageView() {
        return mBinding.selectedDayImageView;
    }

    /**
     * Gets the currently selected index
     *
     * @return
     */
    public synchronized static int getSelectedView() {
        return mSelectedView;
    }

    /**
     * Sets the currently selected index
     *
     * @param selectedView From [0,6]
     */
    public synchronized static void setSelectedView(int selectedView) {
        SlideView.mSelectedView = selectedView;
    }
}