/*
* Copyright 2013 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*     http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/


package com.example.android.batchstepsensor.cardstream;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.app.Activity;
import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.TextView;

import com.example.android.batchstepsensor.R;

import java.util.ArrayList;

/**
 * A Card contains a description and has a visual state. Optionally a card also contains a title,
 * progress indicator and zero or more actions. It is constructed through the {@link Builder}.
 */
public class Card {

    public static final int ACTION_POSITIVE = 1;
    public static final int ACTION_NEGATIVE = 2;
    public static final int ACTION_NEUTRAL = 3;

    public static final int PROGRESS_TYPE_NO_PROGRESS = 0;
    public static final int PROGRESS_TYPE_NORMAL = 1;
    public static final int PROGRESS_TYPE_INDETERMINATE = 2;
    public static final int PROGRESS_TYPE_LABEL = 3;

    private OnCardClickListener mClickListener;


    // The card model contains a reference to its desired layout (for extensibility), title,
    // description, zero to many action buttons, and zero or 1 progress indicators.
    private int mLayoutId = R.layout.card;

    /**
     * Tag that uniquely identifies this card.
     */
    private String mTag = null;

    private String mTitle = null;
    private String mDescription = null;

    private View mCardView = null;
    private View mOverlayView = null;
    private TextView mTitleView = null;
    private TextView mDescView = null;
    private View mActionAreaView = null;

    private Animator mOngoingAnimator = null;

    /**
     * Visual state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or
     * {@link #CARD_STATE_INACTIVE}.
     */
    private int mCardState = CARD_STATE_NORMAL;
    public static final int CARD_STATE_NORMAL = 1;
    public static final int CARD_STATE_FOCUSED = 2;
    public static final int CARD_STATE_INACTIVE = 3;

    /**
     * Represent actions that can be taken from the card.  Stylistically the developer can
     * designate the action as positive, negative (ok/cancel, for instance), or neutral.
     * This "type" can be used as a UI hint.
     * @see com.example.android.sensors.batchstepsensor.Card.CardAction
     */
    private ArrayList<CardAction> mCardActions = new ArrayList<CardAction>();

    /**
     * Some cards will have a sense of "progress" which should be associated with, but separated
     * from its "parent" card.  To push for simplicity in samples, Cards are designed to have
     * a maximum of one progress indicator per Card.
     */
    private CardProgress mCardProgress = null;

    public Card() {
    }

    public String getTag() {
        return mTag;
    }

    public View getView() {
        return mCardView;
    }


    public Card setDescription(String desc) {
        if (mDescView != null) {
            mDescription = desc;
            mDescView.setText(desc);
        }
        return this;
    }

    public Card setTitle(String title) {
        if (mTitleView != null) {
            mTitle = title;
            mTitleView.setText(title);
        }
        return this;
    }


    /**
     * Return the UI state, either {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED}
     * or {@link #CARD_STATE_INACTIVE}.
     */
    public int getState() {
        return mCardState;
    }

    /**
     * Set the UI state. The parameter describes the state and must be either
     * {@link #CARD_STATE_NORMAL}, {@link #CARD_STATE_FOCUSED} or {@link #CARD_STATE_INACTIVE}.
     * Note: This method must be called from the UI Thread.
     * @param state
     * @return The card itself, allows for chaining of calls
     */
    public Card setState(int state) {
        mCardState = state;
        if (null != mOverlayView) {
            if (null != mOngoingAnimator) {
                mOngoingAnimator.end();
                mOngoingAnimator = null;
            }
            switch (state) {
                case CARD_STATE_NORMAL: {
                    mOverlayView.setVisibility(View.GONE);
                    mOverlayView.setAlpha(1.f);
                    break;
                }
                case CARD_STATE_FOCUSED: {
                    mOverlayView.setVisibility(View.VISIBLE);
                    mOverlayView.setBackgroundResource(R.drawable.card_overlay_focused);
                    ObjectAnimator animator = ObjectAnimator.ofFloat(mOverlayView, "alpha", 0.f);
                    animator.setRepeatMode(ObjectAnimator.REVERSE);
                    animator.setRepeatCount(ObjectAnimator.INFINITE);
                    animator.setDuration(1000);
                    animator.start();
                    mOngoingAnimator = animator;
                    break;
                }
                case CARD_STATE_INACTIVE: {
                    mOverlayView.setVisibility(View.VISIBLE);
                    mOverlayView.setAlpha(1.f);
                    mOverlayView.setBackgroundColor(Color.argb(0xaa, 0xcc, 0xcc, 0xcc));
                    break;
                }
            }
        }
        return this;
    }

    /**
     * Set the type of progress indicator.
     * The progress type can only be changed if the Card was initially build with a progress
     * indicator.
     * See {@link Builder#setProgressType(int)}.
     * Must be a value of either {@link #PROGRESS_TYPE_NORMAL},
     * {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL} or
     * {@link #PROGRESS_TYPE_NO_PROGRESS}.
     * @param progressType
     * @return The card itself, allows for chaining of calls
     */
    public Card setProgressType(int progressType) {
        if (mCardProgress == null) {
            mCardProgress = new CardProgress();
        }
        mCardProgress.setProgressType(progressType);
        return this;
    }

    /**
     * Return the progress indicator type. A value of either {@link #PROGRESS_TYPE_NORMAL},
     * {@link #PROGRESS_TYPE_INDETERMINATE}, {@link #PROGRESS_TYPE_LABEL}. Otherwise if no progress
     * indicator is enabled, {@link #PROGRESS_TYPE_NO_PROGRESS} is returned.
     * @return
     */
    public int getProgressType() {
        if (mCardProgress == null) {
            return PROGRESS_TYPE_NO_PROGRESS;
        }
        return mCardProgress.progressType;
    }

    /**
     * Set the progress to the specified value. Only applicable if the card has a
     * {@link #PROGRESS_TYPE_NORMAL} progress type.
     * @param progress
     * @return
     * @see #setMaxProgress(int)
     */
    public Card setProgress(int progress) {
        if (mCardProgress != null) {
            mCardProgress.setProgress(progress);
        }
        return this;
    }

    /**
     * Set the range of the progress to 0...max. Only applicable if the card has a
     * {@link #PROGRESS_TYPE_NORMAL} progress type.
     * @return
     */
    public Card setMaxProgress(int max){
        if (mCardProgress != null) {
            mCardProgress.setMax(max);
        }
        return this;
    }

    /**
     * Set the label text for the progress if the card has a progress type of
     * {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
     * {@link #PROGRESS_TYPE_LABEL}
     * @param text
     * @return
     */
    public Card setProgressLabel(String text) {
        if (mCardProgress != null) {
            mCardProgress.setProgressLabel(text);
        }
        return this;
    }

    /**
     * Toggle the visibility of the progress section of the card. Only applicable if
     * the card has a progress type of
     * {@link #PROGRESS_TYPE_NORMAL}, {@link #PROGRESS_TYPE_INDETERMINATE} or
     * {@link #PROGRESS_TYPE_LABEL}.
     * @param isVisible
     * @return
     */
    public Card setProgressVisibility(boolean isVisible) {
        if (mCardProgress.progressView == null) {
            return this; // Card does not have progress
        }
        mCardProgress.progressView.setVisibility(isVisible ? View.VISIBLE : View.GONE);

        return this;
    }

    /**
     * Adds an action to this card during build time.
     *
     * @param label
     * @param id
     * @param type
     */
    private void addAction(String label, int id, int type) {
        CardAction cardAction = new CardAction();
        cardAction.label = label;
        cardAction.id = id;
        cardAction.type = type;
        mCardActions.add(cardAction);
    }

    /**
     * Toggles the visibility of a card action.
     * @param actionId
     * @param isVisible
     * @return
     */
    public Card setActionVisibility(int actionId, boolean isVisible) {
        int visibilityFlag = isVisible ? View.VISIBLE : View.GONE;
        for (CardAction action : mCardActions) {
            if (action.id == actionId && action.actionView != null) {
                action.actionView.setVisibility(visibilityFlag);
            }
        }
        return this;
    }

    /**
     * Toggles visibility of the action area of this Card through an animation.
     * @param isVisible
     * @return
     */
    public Card setActionAreaVisibility(boolean isVisible) {
        if (mActionAreaView == null) {
            return this; // Card does not have an action area
        }

        if (isVisible) {
            // Show the action area
            mActionAreaView.setVisibility(View.VISIBLE);
            mActionAreaView.setPivotY(0.f);
            mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
            mActionAreaView.setAlpha(0.5f);
            mActionAreaView.setRotationX(-90.f);
            mActionAreaView.animate().rotationX(0.f).alpha(1.f).setDuration(400);
        } else {
            // Hide the action area
            mActionAreaView.setPivotY(0.f);
            mActionAreaView.setPivotX(mCardView.getWidth() / 2.f);
            mActionAreaView.animate().rotationX(-90.f).alpha(0.f).setDuration(400).setListener(
                    new AnimatorListenerAdapter() {
                        @Override
                        public void onAnimationEnd(Animator animation) {
                            mActionAreaView.setVisibility(View.GONE);
                        }
                    });
        }
        return this;
    }


    /**
     * Creates a shallow clone of the card.  Shallow means all values are present, but no views.
     * This is useful for saving/restoring in the case of configuration changes, like screen
     * rotation.
     *
     * @return A shallow clone of the card instance
     */
    public Card createShallowClone() {
        Card cloneCard = new Card();

        // Outer card values
        cloneCard.mTitle = mTitle;
        cloneCard.mDescription = mDescription;
        cloneCard.mTag = mTag;
        cloneCard.mLayoutId = mLayoutId;
        cloneCard.mCardState = mCardState;

        // Progress
        if (mCardProgress != null) {
            cloneCard.mCardProgress = mCardProgress.createShallowClone();
        }

        // Actions
        for (CardAction action : mCardActions) {
            cloneCard.mCardActions.add(action.createShallowClone());
        }

        return cloneCard;
    }


    /**
     * Prepare the card to be stored for configuration change.
     */
    public void prepareForConfigurationChange() {
        // Null out views.
        mCardView = null;
        for (CardAction action : mCardActions) {
            action.actionView = null;
        }
        mCardProgress.progressView = null;
    }

    /**
     * Creates a new {@link #Card}.
     */
    public static class Builder {
        private Card mCard;

        /**
         * Instantiate the builder with data from a shallow clone.
         * @param listener
         * @param card
         * @see Card#createShallowClone()
         */
        protected Builder(OnCardClickListener listener, Card card) {
            mCard = card;
            mCard.mClickListener = listener;
        }

        /**
         * Instantiate the builder with the tag of the card.
         * @param listener
         * @param tag
         */
        public Builder(OnCardClickListener listener, String tag) {
            mCard = new Card();
            mCard.mTag = tag;
            mCard.mClickListener = listener;
        }

        public Builder setTitle(String title) {
            mCard.mTitle = title;
            return this;
        }

        public Builder setDescription(String desc) {
            mCard.mDescription = desc;
            return this;
        }

        /**
         * Add an action.
         * The type describes how this action will be displayed. Accepted values are
         * {@link #ACTION_NEUTRAL}, {@link #ACTION_POSITIVE} or {@link #ACTION_NEGATIVE}.
         *
         * @param label The text to display for this action
         * @param id Identifier for this action, supplied in the click listener
         * @param type UI style of action
         * @return
         */
        public Builder addAction(String label, int id, int type) {
            mCard.addAction(label, id, type);
            return this;
        }

        /**
         * Override the default layout.
         * The referenced layout file has to contain the same identifiers as defined in the default
         * layout configuration.
         * @param layout
         * @return
         * @see R.layout.card
         */
        public Builder setLayout(int layout) {
            mCard.mLayoutId = layout;
            return this;
        }

        /**
         * Set the type of progress bar to display.
         * Accepted values are:
         * <ul>
         *     <li>{@link #PROGRESS_TYPE_NO_PROGRESS} disables the progress indicator</li>
         *     <li>{@link #PROGRESS_TYPE_NORMAL} 
         *     displays a standard, linear progress indicator.</li>
         *     <li>{@link #PROGRESS_TYPE_INDETERMINATE} displays an indeterminate (infite) progress
         *     indicator.</li>
         *     <li>{@link #PROGRESS_TYPE_LABEL} only displays a label text in the progress area
         *     of the card.</li>
         * </ul>
         *
         * @param progressType
         * @return
         */
        public Builder setProgressType(int progressType) {
            mCard.setProgressType(progressType);
            return this;
        }

        public Builder setProgressLabel(String label) {
            // ensure the progress layout has been initialized, use 'no progress' by default
            if (mCard.mCardProgress == null) {
                mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
            }
            mCard.mCardProgress.label = label;
            return this;
        }

        public Builder setProgressMaxValue(int maxValue) {
            // ensure the progress layout has been initialized, use 'no progress' by default
            if (mCard.mCardProgress == null) {
                mCard.setProgressType(PROGRESS_TYPE_NO_PROGRESS);
            }
            mCard.mCardProgress.maxValue = maxValue;
            return this;
        }

        public Builder setStatus(int status) {
            mCard.setState(status);
            return this;
        }

        public Card build(Activity activity) {
            LayoutInflater inflater = activity.getLayoutInflater();
            // Inflating the card.
            ViewGroup cardView = (ViewGroup) inflater.inflate(mCard.mLayoutId,
                    (ViewGroup) activity.findViewById(R.id.card_stream), false);

            // Check that the layout contains a TextView with the card_title id
            View viewTitle = cardView.findViewById(R.id.card_title);
            if (mCard.mTitle != null && viewTitle != null) {
                mCard.mTitleView = (TextView) viewTitle;
                mCard.mTitleView.setText(mCard.mTitle);
            } else if (viewTitle != null) {
                viewTitle.setVisibility(View.GONE);
            }

            // Check that the layout contains a TextView with the card_content id
            View viewDesc = cardView.findViewById(R.id.card_content);
            if (mCard.mDescription != null && viewDesc != null) {
                mCard.mDescView = (TextView) viewDesc;
                mCard.mDescView.setText(mCard.mDescription);
            } else if (viewDesc != null) {
                cardView.findViewById(R.id.card_content).setVisibility(View.GONE);
            }


            ViewGroup actionArea = (ViewGroup) cardView.findViewById(R.id.card_actionarea);

            // Inflate Progress
            initializeProgressView(inflater, actionArea);

            // Inflate all action views.
            initializeActionViews(inflater, cardView, actionArea);

            mCard.mCardView = cardView;
            mCard.mOverlayView = cardView.findViewById(R.id.card_overlay);

            return mCard;
        }

        /**
         * Initialize data from the given card.
         * @param card
         * @return
         * @see Card#createShallowClone()
         */
        public Builder cloneFromCard(Card card) {
            mCard = card.createShallowClone();
            return this;
        }

        /**
         * Build the action views by inflating the appropriate layouts and setting the text and 
         * values.
         * @param inflater
         * @param cardView
         * @param actionArea
         */
        private void initializeActionViews(LayoutInflater inflater, ViewGroup cardView,
                                           ViewGroup actionArea) {
            if (!mCard.mCardActions.isEmpty()) {
                // Set action area to visible only when actions are visible
                actionArea.setVisibility(View.VISIBLE);
                mCard.mActionAreaView = actionArea;
            }

            // Inflate all card actions
            for (final CardAction action : mCard.mCardActions) {

                int useActionLayout = 0;
                switch (action.type) {
                    case Card.ACTION_POSITIVE:
                        useActionLayout = R.layout.card_button_positive;
                        break;
                    case Card.ACTION_NEGATIVE:
                        useActionLayout = R.layout.card_button_negative;
                        break;
                    case Card.ACTION_NEUTRAL:
                    default:
                        useActionLayout = R.layout.card_button_neutral;
                        break;
                }

                action.actionView = inflater.inflate(useActionLayout, actionArea, false);
                Button actionButton = (Button) action.actionView.findViewById(R.id.card_button);

                actionButton.setText(action.label);
                actionButton.setOnClickListener(new View.OnClickListener() {
                    @Override
                    public void onClick(View v) {
                        mCard.mClickListener.onCardClick(action.id, mCard.mTag);
                    }
                });
                actionArea.addView(action.actionView);
            }
        }

        /**
         * Build the progress view into the given ViewGroup.
         *
         * @param inflater
         * @param actionArea
         */
        private void initializeProgressView(LayoutInflater inflater, ViewGroup actionArea) {

            // Only inflate progress layout if a progress type other than NO_PROGRESS was set.
            if (mCard.mCardProgress != null) {
                //Setup progress card.
                View progressView = inflater.inflate(R.layout.card_progress, actionArea, false);
                ProgressBar progressBar = 
                        (ProgressBar) progressView.findViewById(R.id.card_progress);
                ((TextView) progressView.findViewById(R.id.card_progress_text))
                        .setText(mCard.mCardProgress.label);
                progressBar.setMax(mCard.mCardProgress.maxValue);
                progressBar.setProgress(0);
                mCard.mCardProgress.progressView = progressView;
                mCard.mCardProgress.setProgressType(mCard.getProgressType());
                actionArea.addView(progressView);
            }
        }
    }

    /**
     * Represents a clickable action, accessible from the bottom of the card.
     * Fields include the label, an ID to specify the action that was performed in the callback,
     * an action type (positive, negative, neutral), and the callback.
     */
    public class CardAction {

        public String label;
        public int id;
        public int type;
        public View actionView;

        public CardAction createShallowClone() {
            CardAction actionClone = new CardAction();
            actionClone.label = label;
            actionClone.id = id;
            actionClone.type = type;
            return actionClone;
            // Not the view.  Never the view (don't want to hold view references for
            // onConfigurationChange.
        }

    }

    /**
     * Describes the progress of a {@link Card}.
     * Three types of progress are supported:
     * <ul><li>{@link Card#PROGRESS_TYPE_NORMAL: Standard progress bar with label text</li>
     * <li>{@link Card#PROGRESS_TYPE_INDETERMINATE}: Indeterminate progress bar with label txt</li>
     * <li>{@link Card#PROGRESS_TYPE_LABEL}: Label only, no progresss bar</li>
     * </ul>
     */
    public class CardProgress {
        private int progressType = Card.PROGRESS_TYPE_NO_PROGRESS;
        private String label = "";
        private int currProgress = 0;
        private int maxValue = 100;

        public View progressView = null;
        private ProgressBar progressBar = null;
        private TextView progressLabel = null;

        public CardProgress createShallowClone() {
            CardProgress progressClone = new CardProgress();
            progressClone.label = label;
            progressClone.currProgress = currProgress;
            progressClone.maxValue = maxValue;
            progressClone.progressType = progressType;
            return progressClone;
        }

        /**
         * Set the progress. Only useful for the type {@link #PROGRESS_TYPE_NORMAL}.
         * @param progress
         * @see android.widget.ProgressBar#setProgress(int)
         */
        public void setProgress(int progress) {
            currProgress = progress;
            final ProgressBar bar = getProgressBar();
            if (bar != null) {
                bar.setProgress(currProgress);
                bar.invalidate();
            }
        }

        /**
         * Set the range of the progress to 0...max.
         * Only useful for the type {@link #PROGRESS_TYPE_NORMAL}.
         * @param max
         * @see android.widget.ProgressBar#setMax(int)
         */
        public void setMax(int max) {
            maxValue = max;
            final ProgressBar bar = getProgressBar();
            if (bar != null) {
                bar.setMax(maxValue);
            }
        }

        /**
         * Set the label text that appears near the progress indicator.
         * @param text
         */
        public void setProgressLabel(String text) {
            label = text;
            final TextView labelView = getProgressLabel();
            if (labelView != null) {
                labelView.setText(text);
            }
        }

        /**
         * Set how progress is displayed. The parameter must be one of three supported types:
         * <ul><li>{@link Card#PROGRESS_TYPE_NORMAL: Standard progress bar with label text</li>
         * <li>{@link Card#PROGRESS_TYPE_INDETERMINATE}: 
         * Indeterminate progress bar with label txt</li>
         * <li>{@link Card#PROGRESS_TYPE_LABEL}: Label only, no progresss bar</li>
         * @param type
         */
        public void setProgressType(int type) {
            progressType = type;
            if (progressView != null) {
                switch (type) {
                    case PROGRESS_TYPE_NO_PROGRESS: {
                        progressView.setVisibility(View.GONE);
                        break;
                    }
                    case PROGRESS_TYPE_NORMAL: {
                        progressView.setVisibility(View.VISIBLE);
                        getProgressBar().setIndeterminate(false);
                        break;
                    }
                    case PROGRESS_TYPE_INDETERMINATE: {
                        progressView.setVisibility(View.VISIBLE);
                        getProgressBar().setIndeterminate(true);
                        break;
                    }
                }
            }
        }

        private TextView getProgressLabel() {
            if (progressLabel != null) {
                return progressLabel;
            } else if (progressView != null) {
                progressLabel = (TextView) progressView.findViewById(R.id.card_progress_text);
                return progressLabel;
            } else {
                return null;
            }
        }

        private ProgressBar getProgressBar() {
            if (progressBar != null) {
                return progressBar;
            } else if (progressView != null) {
                progressBar = (ProgressBar) progressView.findViewById(R.id.card_progress);
                return progressBar;
            } else {
                return null;
            }
        }

    }
}