package com.alexvasilkov.android.commons.ui;

import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.AttrRes;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.util.StateSet;
import android.util.TypedValue;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;

/**
 * Simplified tinting for drawables and views.
 */
@SuppressWarnings({ "unused", "WeakerAccess" }) // Public API
public class Tints {

    public static final Tint PRIMARY = fromAttr("colorPrimary");
    public static final Tint PRIMARY_DARK = fromAttr("colorPrimaryDark");
    public static final Tint ACCENT = fromAttr("colorAccent");

    public static final Tint NORMAL = fromAttr("colorControlNormal");
    public static final Tint ACTIVATED = fromAttr("colorControlActivated");
    public static final Tint DISABLED = withDisabledAlpha(fromAttr("colorControlNormal"));

    private static final int ALPHA_DISABLED = -1;

    private static final int[][] states = new int[][] {
            new int[] { -android.R.attr.state_enabled }, // disabled
            new int[] { android.R.attr.state_pressed }, // pressed
            new int[] { android.R.attr.state_focused }, // focused
            new int[] { android.R.attr.state_selected }, // selected
            new int[] { android.R.attr.state_activated }, // activated
            new int[] { android.R.attr.state_checked }, // checked
            StateSet.WILD_CARD // other
    };

    private static final TypedValue tmpValue = new TypedValue();
    private static final int[] tmpAttrs = new int[1];


    private Tints() {}


    public static Drawable tint(@NonNull Context context,
            @NonNull Drawable drawable, @NonNull Tint tint) {
        return tint(drawable, tint.getColor(context));
    }

    public static Drawable tint(@NonNull Context context,
            @DrawableRes int drawableId, @NonNull Tint tint) {
        return tint(ContextCompat.getDrawable(context, drawableId), tint.getColor(context));
    }

    private static Drawable tint(Drawable drawable, ColorStateList color) {
        Drawable wrapped = DrawableCompat.wrap(drawable);
        wrapped.mutate();
        DrawableCompat.setTintList(wrapped, color);
        return wrapped;
    }


    /**
     * Tints ImageView drawable or TextView compound drawables to given tint color.
     */
    public static void tint(@NonNull View view, @NonNull Tint tint) {
        final ColorStateList color = tint.getColor(view.getContext());

        if (view instanceof ImageView) {
            tint(((ImageView) view).getDrawable(), color);
        } else if (view instanceof TextView) {
            TextView text = (TextView) view;
            Drawable[] comp = text.getCompoundDrawables();
            for (int i = 0; i < comp.length; i++) {
                if (comp[i] != null) {
                    comp[i] = tint(comp[i], color);
                }
            }
            text.setCompoundDrawablesWithIntrinsicBounds(comp[0], comp[1], comp[2], comp[3]);
        } else {
            throw new IllegalArgumentException("Unsupported view type");
        }
    }


    private static Tint fromAttr(@NonNull final String attrName) {
        return new Tint() {
            @Override
            public ColorStateList getColor(Context context) {
                int attrId = context.getResources()
                        .getIdentifier(attrName, "attr", context.getPackageName());
                return getThemeAttrColor(context, attrId);
            }
        };
    }

    public static Tint fromAttr(@AttrRes final int attrId) {
        return new Tint() {
            @Override
            public ColorStateList getColor(Context context) {
                return getThemeAttrColor(context, attrId);
            }
        };
    }

    public static Tint fromColor(@ColorInt final int color) {
        return new Tint() {
            @Override
            public ColorStateList getColor(Context context) {
                return ColorStateList.valueOf(color);
            }
        };
    }

    public static Tint fromColorRes(@ColorRes final int colorId) {
        return new Tint() {
            @Override
            public ColorStateList getColor(Context context) {
                return ContextCompat.getColorStateList(context, colorId);
            }
        };
    }

    public static Tint fromColorList(@NonNull final ColorStateList color) {
        return new Tint() {
            @Override
            public ColorStateList getColor(Context context) {
                return color;
            }
        };
    }

    public static Tint fromDefaultStates() {
        return new Tint() {
            @Override
            public ColorStateList getColor(Context context) {
                final int normal = NORMAL.getDefaultColor(context);
                final int activated = ACTIVATED.getDefaultColor(context);
                final int disabled = DISABLED.getDefaultColor(context);
                final int[] colors = new int[] {
                        disabled, activated, activated, activated, activated, activated, normal
                };
                return new ColorStateList(states, colors);
            }
        };
    }

    public static Tint withAlpha(@NonNull final Tint tint, final int alpha) {
        return new Tint() {
            @Override
            public ColorStateList getColor(Context context) {
                int appliedAlpha = alpha == ALPHA_DISABLED ? getThemeDisabledAlpha(context) : alpha;
                ColorStateList stateColor = tint.getColor(context);

                if (stateColor.isStateful()) {
                    // New ColorStateList object will be created each time calling .withAlpha()
                    return stateColor.withAlpha(appliedAlpha);
                } else {
                    // Created ColorStateList object will be cached
                    int color = (stateColor.getDefaultColor() & 0xFFFFFF) | (appliedAlpha << 24);
                    return ColorStateList.valueOf(color);
                }
            }
        };
    }

    public static Tint withDisabledAlpha(@NonNull Tint tint) {
        return withAlpha(tint, ALPHA_DISABLED);
    }


    private static ColorStateList getThemeAttrColor(Context context, @AttrRes int attr) {
        tmpAttrs[0] = attr;
        TypedArray arr = context.obtainStyledAttributes(null, tmpAttrs);
        ColorStateList color = arr.getColorStateList(0);
        arr.recycle();
        return color;
    }

    private static int getThemeDisabledAlpha(Context context) {
        context.getTheme().resolveAttribute(android.R.attr.disabledAlpha, tmpValue, true);
        return Math.round(0xFF * tmpValue.getFloat());
    }


    public abstract static class Tint {
        /**
         * Returns ColorStateList for tinting.
         */
        public abstract ColorStateList getColor(Context context);

        /**
         * Returns default color from ColorStateList for tinting.
         * Useful if it is known that tinting color is stateless (representing single color).
         */
        public final int getDefaultColor(Context context) {
            return getColor(context).getDefaultColor();
        }
    }

}