package com.eugeneek.smilebar;

import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.PointF;
import android.graphics.drawable.Drawable;
import android.support.v4.content.res.ResourcesCompat;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;

public class SmileBar extends View {

    private static final int NO_RATING = 0;
    private static final int MAX_RATE = 5;
    private boolean isSliding;
    private float slidePosition;
    private PointF[] points;
    private float itemWidth;
    private Drawable[] ratingSmiles;
    private Drawable defaultSmile;
    private OnRatingSliderChangeListener listener;
    private int currentRating = NO_RATING;
    private int smileWidth, smileHeight;
    private int horizontalSpacing;
    private boolean isEnabled;
    private int rating = NO_RATING;

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

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

    public SmileBar(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        init(attrs);
    }

    private void init() {
        init(null);
    }

    private void init(AttributeSet attrs) {
        isSliding = false;

        if (attrs != null) {
            TypedArray ta = getContext().obtainStyledAttributes(attrs, R.styleable.SmileBar, 0, 0);
            try {
                smileWidth = ta.getDimensionPixelSize(R.styleable.SmileBar_smileWidth, 0);
                smileHeight = ta.getDimensionPixelSize(R.styleable.SmileBar_smileHeight, 0);
                horizontalSpacing = ta.getDimensionPixelSize(R.styleable.SmileBar_horizontalSpacing, 0);
                isEnabled = ta.getBoolean(R.styleable.SmileBar_enabled, true);
                rating = ta.getInt(R.styleable.SmileBar_rating, NO_RATING);
                int resDefault = ta.getResourceId(R.styleable.SmileBar_smileDefault, R.drawable.smile_rate_none);
                int res1 = ta.getResourceId(R.styleable.SmileBar_smileRate1, R.drawable.smile_rate_1);
                int res2 = ta.getResourceId(R.styleable.SmileBar_smileRate2, R.drawable.smile_rate_2);
                int res3 = ta.getResourceId(R.styleable.SmileBar_smileRate3, R.drawable.smile_rate_3);
                int res4 = ta.getResourceId(R.styleable.SmileBar_smileRate4, R.drawable.smile_rate_4);
                int res5 = ta.getResourceId(R.styleable.SmileBar_smileRate5, R.drawable.smile_rate_5);
                defaultSmile = ResourcesCompat.getDrawable(getResources(), resDefault, null);
                ratingSmiles = new Drawable[] {
                        ResourcesCompat.getDrawable(getResources(), res1, null),
                        ResourcesCompat.getDrawable(getResources(), res2, null),
                        ResourcesCompat.getDrawable(getResources(), res3, null),
                        ResourcesCompat.getDrawable(getResources(), res4, null),
                        ResourcesCompat.getDrawable(getResources(), res5, null)
                };

                if (smileWidth == 0)
                    smileWidth = defaultSmile.getIntrinsicWidth();

                if (smileHeight == 0)
                    smileHeight = defaultSmile.getIntrinsicHeight();
            } finally {
                ta.recycle();
            }
        }

        points = new PointF[MAX_RATE];
        for (int i = 0; i < MAX_RATE; i++) {
            points[i] = new PointF();
        }
        if (rating != NO_RATING)
            setRating(rating);
    }

    @Override
    public void setEnabled(boolean enabled) {
        isEnabled = enabled;
        super.setEnabled(enabled);
    }

    @Override
    public boolean isEnabled() {
        return isEnabled;
    }

    /**
     * Set a listener that will be invoked whenever the users interacts with the SmileBar.
     *
     * @param listener
     *            Listener to set.
     */
    public void setOnRatingSliderChangeListener(OnRatingSliderChangeListener listener) {
        this.listener = listener;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (!isEnabled()) {
            // Disable all input if the slider is disabled
            return false;
        }
        int action = event.getAction();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
            case MotionEvent.ACTION_MOVE: {
                isSliding = true;
                slidePosition = getRelativePosition(event.getX());
                rating = (int) Math.ceil(slidePosition);
                if (listener != null && rating != currentRating) {
                    currentRating = rating;
                    listener.onPendingRating(rating);
                }
                break;
            }
            case MotionEvent.ACTION_UP:
                currentRating = NO_RATING;
                if (listener != null)
                    listener.onFinalRating((int) Math.ceil(slidePosition));
                rating = (int) Math.ceil(slidePosition);
                break;
            case MotionEvent.ACTION_CANCEL:
                currentRating = NO_RATING;
                if (listener != null)
                    listener.onCancelRating();
                isSliding = false;
                break;
            default:
                break;
        }

        invalidate();
        return true;
    }

    private float getRelativePosition(float x) {
        float position = x / itemWidth;
        position = Math.max(position, 0);
        return Math.min(position, MAX_RATE);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        itemWidth = w / (float) MAX_RATE;
        updatePositions();
    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {

        int width = smileWidth * MAX_RATE + horizontalSpacing * (MAX_RATE - 1) +
                getPaddingLeft() + getPaddingRight();
        int height = smileHeight + getPaddingTop() + getPaddingBottom();
        setMeasuredDimension(width, height);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        super.onDraw(canvas);

        for (int i = 0; i < MAX_RATE; i++) {
            PointF pos = points[i];
            canvas.save();
            canvas.translate(pos.x, pos.y);
            drawSmile(canvas, i);
            canvas.restore();
        }
    }

    private void drawSmile(Canvas canvas, int position) {

        // Draw the rated smile
        if (isSliding && position <= slidePosition) {
            Drawable[] smiles = ratingSmiles;

            int rating = (int) Math.ceil(slidePosition);
            int smileIndex = rating - 1;
            if (rating > 0)
                drawSmile(canvas, smiles[smileIndex]);
            else
                drawSmile(canvas, defaultSmile);
        }
        else {
            // Draw the default smile
            drawSmile(canvas, defaultSmile);
        }
    }

    private void drawSmile(Canvas canvas, Drawable smile) {
        canvas.save();
        canvas.translate(-smileWidth / 2, -smileHeight / 2);
        smile.setBounds(0, 0, smileWidth, smileHeight);
        smile.draw(canvas);
        canvas.restore();
    }

    private void updatePositions() {
        float left = 0;
        for (int i = 0; i < MAX_RATE; i++) {
            float posY = getHeight() / 2;
            float posX = left + smileWidth / 2;
            left += smileWidth;
            if (i > 0) {
                posX += horizontalSpacing;
                left += horizontalSpacing;
            } else {
                posX += getPaddingLeft();
                left += getPaddingLeft();
            }

            points[i].set(posX, posY);

        }
    }

    public void setRating(int rating) {
        if (rating < 0 || rating > MAX_RATE)
            throw new IndexOutOfBoundsException("Rating must be between 0 and " + MAX_RATE);

        this.rating = rating;
        slidePosition = (float) (rating - 0.1);
        isSliding = true;
        invalidate();
        if (listener != null)
            listener.onFinalRating(rating);
    }

    public int getRating() {
        return rating;
    }

    /**
     * A callback that notifies clients when the user starts rating, changes the rating
     * value and when the rating has ended.
     */
    public interface OnRatingSliderChangeListener {

        /**
         * Notification that the user has moved over to a different rating value.
         * The rating value is only temporary and might change again before the
         * rating is finalized.
         *
         * @param rating
         *            the pending rating. A value between 0 and 5.
         */
        void onPendingRating(int rating);

        /**
         * Notification that the user has selected a final rating.
         *
         * @param rating
         *            the final rating selected. A value between 0 and 5.
         */
        void onFinalRating(int rating);

        /**
         * Notification that the user has canceled the rating.
         */
        void onCancelRating();
    }
}