/*
 * Copyright (C) 2014 Grantland Chew
 * Copyright (C) 2015 The CyanogenMod 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.android.launcher3;

import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.TextPaint;
import android.text.TextUtils;
import android.text.method.TransformationMethod;
import android.text.style.ForegroundColorSpan;
import android.util.AttributeSet;
import android.widget.TextView;

import java.util.ArrayList;

/**
 * A single-line TextView that resizes it's letter spacing to fit the width of the view
 *
 * @author Grantland Chew <grantlandchew@gmail.com>
 * @author Linus Lee <llee@cyngn.com>
 */
public class AutoExpandTextView extends TextView {
    // How precise we want to be when reaching the target textWidth size
    private static final float PRECISION = 0.01f;

    // Attributes
    private float mPrecision;
    private TextPaint mPaint;
    private float[] mPositions;

    public static class HighlightedText {
        public String mText;
        public boolean mHighlight;

        public HighlightedText(String text, boolean highlight) {
            mText = text;
            mHighlight = highlight;
        }
    }

    public AutoExpandTextView(Context context) {
        super(context);
        init(context, null, 0);
    }

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

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

    private void init(Context context, AttributeSet attrs, int defStyle) {
        float precision = PRECISION;

        if (attrs != null) {
            TypedArray ta = context.obtainStyledAttributes(
                    attrs,
                    R.styleable.AutofitTextView,
                    defStyle,
                    0);
            precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision);
        }

        mPaint = new TextPaint();
        setPrecision(precision);
    }

    /**
     * @return the amount of precision used to calculate the correct text size to fit within it's
     * bounds.
     */
    public float getPrecision() {
        return mPrecision;
    }

    /**
     * Set the amount of precision used to calculate the correct text size to fit within it's
     * bounds. Lower precision is more precise and takes more time.
     *
     * @param precision The amount of precision.
     */
    public void setPrecision(float precision) {
        if (precision != mPrecision) {
            mPrecision = precision;
            refitText();
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setLines(int lines) {
        super.setLines(1);
        refitText();
    }

    /**
     * Only allow max lines of 1
     */
    @Override
    public void setMaxLines(int maxLines) {
        super.setMaxLines(1);
        refitText();
    }

    /**
     * Re size the font so the specified text fits in the text box assuming the text box is the
     * specified width.
     */
    private void refitText() {
        CharSequence text = getText();

        if (TextUtils.isEmpty(text)) {
            return;
        }

        TransformationMethod method = getTransformationMethod();
        if (method != null) {
            text = method.getTransformation(text, this);
        }
        int targetWidth = getWidth() - getPaddingLeft() - getPaddingRight();
        if (targetWidth > 0) {
            float high = 100;
            float low = 0;

            mPaint.set(getPaint());
            mPaint.setTextSize(getTextSize());
            float letterSpacing = getLetterSpacing(text, mPaint, targetWidth, low, high,
                    mPrecision);
            mPaint.setLetterSpacing(letterSpacing);
            calculateSections(text);

            super.setLetterSpacing(letterSpacing);
        }
    }

    public float getPositionOfSection(int position) {
        if (mPositions == null || position >= mPositions.length) {
            return 0;
        }
        return mPositions[position];
    }

    /**
     * This calculates the different horizontal positions of each character
     */
    private void calculateSections(CharSequence text) {
        mPositions = new float[text.length()];
        for (int i = 0; i < text.length(); i++) {
            if (i == 0) {
                mPositions[0] = mPaint.measureText(text, 0, 1) / 2;
            } else {
                // try to be lazy and just add the width of the newly added char
                mPositions[i] = mPaint.measureText(text, i, i + 1) + mPositions[i - 1];
            }
        }
    }

    /**
     * Sets the list of sections in the text view.  This will take the first character of each
     * and space it out in the text view using letter spacing
     */
    public void setSections(ArrayList<HighlightedText> sections) {
        mPositions = null;
        if (sections == null || sections.size() == 0) {
            setText("");
            return;
        }

        Resources r = getContext().getResources();
        int highlightColor = r.getColor(R.color.app_scrubber_highlight_color);
        int grayColor = r.getColor(R.color.app_scrubber_gray_color);

        SpannableStringBuilder builder = new SpannableStringBuilder();
        for (HighlightedText highlightText : sections) {
            if (!TextUtils.isEmpty(highlightText.mText)) {
                SpannableString spannable =
                        new SpannableString(highlightText.mText.substring(0, 1));
                spannable.setSpan(
                        new ForegroundColorSpan(highlightText.mHighlight ? highlightColor :
                                grayColor), 0, spannable.length(), 0);
                builder.append(spannable);
            }
        }

        setText(builder);
    }

    private static float getLetterSpacing(CharSequence text, TextPaint paint, float targetWidth,
                                          float low, float high, float precision) {
        float mid = (low + high) / 2.0f;
        paint.setLetterSpacing(mid);

        float measuredWidth = paint.measureText(text, 0, text.length());

        if (high - low < precision) {
            if (measuredWidth < targetWidth) {
                return mid;
            } else {
                return low;
            }
        } else if (measuredWidth > targetWidth) {
            return getLetterSpacing(text, paint, targetWidth, low, mid, precision);
        } else if (measuredWidth < targetWidth) {
            return getLetterSpacing(text, paint, targetWidth, mid, high, precision);
        } else {
            return mid;
        }
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
                                 final int lengthBefore, final int lengthAfter) {
        super.onTextChanged(text, start, lengthBefore, lengthAfter);
        refitText();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        super.onSizeChanged(w, h, oldw, oldh);
        if (w != oldw) {
            refitText();
        }
    }
}