/*
 * Copyright (C) 2010 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 android.graphics;

import com.android.ide.common.rendering.api.LayoutLog;
import com.android.layoutlib.bridge.Bridge;
import com.android.layoutlib.bridge.impl.DelegateManager;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;

import android.graphics.Paint.FontMetrics;
import android.graphics.Paint.FontMetricsInt;

import java.awt.BasicStroke;
import java.awt.Font;
import java.awt.Shape;
import java.awt.Stroke;
import java.awt.Toolkit;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Delegate implementing the native methods of android.graphics.Paint
 *
 * Through the layoutlib_create tool, the original native methods of Paint have been replaced
 * by calls to methods of the same name in this delegate class.
 *
 * This class behaves like the original native implementation, but in Java, keeping previously
 * native data into its own objects and mapping them to int that are sent back and forth between
 * it and the original Paint class.
 *
 * @see DelegateManager
 *
 */
public class Paint_Delegate {

    /**
     * Class associating a {@link Font} and it's {@link java.awt.FontMetrics}.
     */
    /*package*/ static final class FontInfo {
        Font mFont;
        java.awt.FontMetrics mMetrics;
    }

    // ---- delegate manager ----
    private static final DelegateManager<Paint_Delegate> sManager =
            new DelegateManager<Paint_Delegate>(Paint_Delegate.class);

    // ---- delegate helper data ----
    private List<FontInfo> mFonts;
    private final FontRenderContext mFontContext = new FontRenderContext(
            new AffineTransform(), true, true);

    // ---- delegate data ----
    private int mFlags;
    private int mColor;
    private int mStyle;
    private int mCap;
    private int mJoin;
    private int mTextAlign;
    private Typeface_Delegate mTypeface;
    private float mStrokeWidth;
    private float mStrokeMiter;
    private float mTextSize;
    private float mTextScaleX;
    private float mTextSkewX;

    private Xfermode_Delegate mXfermode;
    private ColorFilter_Delegate mColorFilter;
    private Shader_Delegate mShader;
    private PathEffect_Delegate mPathEffect;
    private MaskFilter_Delegate mMaskFilter;
    private Rasterizer_Delegate mRasterizer;

    // ---- Public Helper methods ----

    public static Paint_Delegate getDelegate(int native_paint) {
        return sManager.getDelegate(native_paint);
    }

    /**
     * Returns the list of {@link Font} objects. The first item is the main font, the rest
     * are fall backs for characters not present in the main font.
     */
    public List<FontInfo> getFonts() {
        return mFonts;
    }

    public boolean isAntiAliased() {
        return (mFlags & Paint.ANTI_ALIAS_FLAG) != 0;
    }

    public boolean isFilterBitmap() {
        return (mFlags & Paint.FILTER_BITMAP_FLAG) != 0;
    }

    public int getStyle() {
        return mStyle;
    }

    public int getColor() {
        return mColor;
    }

    public int getAlpha() {
        return mColor >>> 24;
    }

    public void setAlpha(int alpha) {
        mColor = (alpha << 24) | (mColor & 0x00FFFFFF);
    }

    public int getTextAlign() {
        return mTextAlign;
    }

    public float getStrokeWidth() {
        return mStrokeWidth;
    }

    /**
     * returns the value of stroke miter needed by the java api.
     */
    public float getJavaStrokeMiter() {
        float miter = mStrokeMiter * mStrokeWidth;
        if (miter < 1.f) {
            miter = 1.f;
        }
        return miter;
    }

    public int getJavaCap() {
        switch (Paint.sCapArray[mCap]) {
            case BUTT:
                return BasicStroke.CAP_BUTT;
            case ROUND:
                return BasicStroke.CAP_ROUND;
            default:
            case SQUARE:
                return BasicStroke.CAP_SQUARE;
        }
    }

    public int getJavaJoin() {
        switch (Paint.sJoinArray[mJoin]) {
            default:
            case MITER:
                return BasicStroke.JOIN_MITER;
            case ROUND:
                return BasicStroke.JOIN_ROUND;
            case BEVEL:
                return BasicStroke.JOIN_BEVEL;
        }
    }

    public Stroke getJavaStroke() {
        if (mPathEffect != null) {
            if (mPathEffect.isSupported()) {
                Stroke stroke = mPathEffect.getStroke(this);
                assert stroke != null;
                if (stroke != null) {
                    return stroke;
                }
            } else {
                Bridge.getLog().fidelityWarning(LayoutLog.TAG_PATHEFFECT,
                        mPathEffect.getSupportMessage(),
                        null, null /*data*/);
            }
        }

        // if no custom stroke as been set, set the default one.
        return new BasicStroke(
                    getStrokeWidth(),
                    getJavaCap(),
                    getJavaJoin(),
                    getJavaStrokeMiter());
    }

    /**
     * Returns the {@link Xfermode} delegate or null if none have been set
     *
     * @return the delegate or null.
     */
    public Xfermode_Delegate getXfermode() {
        return mXfermode;
    }

    /**
     * Returns the {@link ColorFilter} delegate or null if none have been set
     *
     * @return the delegate or null.
     */
    public ColorFilter_Delegate getColorFilter() {
        return mColorFilter;
    }

    /**
     * Returns the {@link Shader} delegate or null if none have been set
     *
     * @return the delegate or null.
     */
    public Shader_Delegate getShader() {
        return mShader;
    }

    /**
     * Returns the {@link MaskFilter} delegate or null if none have been set
     *
     * @return the delegate or null.
     */
    public MaskFilter_Delegate getMaskFilter() {
        return mMaskFilter;
    }

    /**
     * Returns the {@link Rasterizer} delegate or null if none have been set
     *
     * @return the delegate or null.
     */
    public Rasterizer_Delegate getRasterizer() {
        return mRasterizer;
    }

    // ---- native methods ----

    @LayoutlibDelegate
    /*package*/ static int getFlags(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        return delegate.mFlags;
    }

    @LayoutlibDelegate
    /*package*/ static void setFlags(Paint thisPaint, int flags) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.mFlags = flags;
    }

    @LayoutlibDelegate
    /*package*/ static void setFilterBitmap(Paint thisPaint, boolean filter) {
        setFlag(thisPaint, Paint.FILTER_BITMAP_FLAG, filter);
    }

    @LayoutlibDelegate
    /*package*/ static void setAntiAlias(Paint thisPaint, boolean aa) {
        setFlag(thisPaint, Paint.ANTI_ALIAS_FLAG, aa);
    }

    @LayoutlibDelegate
    /*package*/ static void setSubpixelText(Paint thisPaint, boolean subpixelText) {
        setFlag(thisPaint, Paint.SUBPIXEL_TEXT_FLAG, subpixelText);
    }

    @LayoutlibDelegate
    /*package*/ static void setUnderlineText(Paint thisPaint, boolean underlineText) {
        setFlag(thisPaint, Paint.UNDERLINE_TEXT_FLAG, underlineText);
    }

    @LayoutlibDelegate
    /*package*/ static void setStrikeThruText(Paint thisPaint, boolean strikeThruText) {
        setFlag(thisPaint, Paint.STRIKE_THRU_TEXT_FLAG, strikeThruText);
    }

    @LayoutlibDelegate
    /*package*/ static void setFakeBoldText(Paint thisPaint, boolean fakeBoldText) {
        setFlag(thisPaint, Paint.FAKE_BOLD_TEXT_FLAG, fakeBoldText);
    }

    @LayoutlibDelegate
    /*package*/ static void setDither(Paint thisPaint, boolean dither) {
        setFlag(thisPaint, Paint.DITHER_FLAG, dither);
    }

    @LayoutlibDelegate
    /*package*/ static void setLinearText(Paint thisPaint, boolean linearText) {
        setFlag(thisPaint, Paint.LINEAR_TEXT_FLAG, linearText);
    }

    @LayoutlibDelegate
    /*package*/ static int getColor(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        return delegate.mColor;
    }

    @LayoutlibDelegate
    /*package*/ static void setColor(Paint thisPaint, int color) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.mColor = color;
    }

    @LayoutlibDelegate
    /*package*/ static int getAlpha(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        return delegate.getAlpha();
    }

    @LayoutlibDelegate
    /*package*/ static void setAlpha(Paint thisPaint, int a) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.setAlpha(a);
    }

    @LayoutlibDelegate
    /*package*/ static float getStrokeWidth(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 1.f;
        }

        return delegate.mStrokeWidth;
    }

    @LayoutlibDelegate
    /*package*/ static void setStrokeWidth(Paint thisPaint, float width) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.mStrokeWidth = width;
    }

    @LayoutlibDelegate
    /*package*/ static float getStrokeMiter(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 1.f;
        }

        return delegate.mStrokeMiter;
    }

    @LayoutlibDelegate
    /*package*/ static void setStrokeMiter(Paint thisPaint, float miter) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.mStrokeMiter = miter;
    }

    @LayoutlibDelegate
    /*package*/ static void setShadowLayer(Paint thisPaint, float radius, float dx, float dy,
            int color) {
        // FIXME
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "Paint.setShadowLayer is not supported.", null, null /*data*/);
    }

    @LayoutlibDelegate
    /*package*/ static float getTextSize(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 1.f;
        }

        return delegate.mTextSize;
    }

    @LayoutlibDelegate
    /*package*/ static void setTextSize(Paint thisPaint, float textSize) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.mTextSize = textSize;
        delegate.updateFontObject();
    }

    @LayoutlibDelegate
    /*package*/ static float getTextScaleX(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 1.f;
        }

        return delegate.mTextScaleX;
    }

    @LayoutlibDelegate
    /*package*/ static void setTextScaleX(Paint thisPaint, float scaleX) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.mTextScaleX = scaleX;
        delegate.updateFontObject();
    }

    @LayoutlibDelegate
    /*package*/ static float getTextSkewX(Paint thisPaint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 1.f;
        }

        return delegate.mTextSkewX;
    }

    @LayoutlibDelegate
    /*package*/ static void setTextSkewX(Paint thisPaint, float skewX) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        delegate.mTextSkewX = skewX;
        delegate.updateFontObject();
    }

    @LayoutlibDelegate
    /*package*/ static float ascent(Paint thisPaint) {
        // get the delegate
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        if (delegate.mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
            // Android expects negative ascent so we invert the value from Java.
            return - javaMetrics.getAscent();
        }

        return 0;
    }

    @LayoutlibDelegate
    /*package*/ static float descent(Paint thisPaint) {
        // get the delegate
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        if (delegate.mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
            return javaMetrics.getDescent();
        }

        return 0;

    }

    @LayoutlibDelegate
    /*package*/ static float getFontMetrics(Paint thisPaint, FontMetrics metrics) {
        // get the delegate
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        return delegate.getFontMetrics(metrics);
    }

    @LayoutlibDelegate
    /*package*/ static int getFontMetricsInt(Paint thisPaint, FontMetricsInt fmi) {
        // get the delegate
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        if (delegate.mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = delegate.mFonts.get(0).mMetrics;
            if (fmi != null) {
                // Android expects negative ascent so we invert the value from Java.
                fmi.top = - javaMetrics.getMaxAscent();
                fmi.ascent = - javaMetrics.getAscent();
                fmi.descent = javaMetrics.getDescent();
                fmi.bottom = javaMetrics.getMaxDescent();
                fmi.leading = javaMetrics.getLeading();
            }

            return javaMetrics.getHeight();
        }

        return 0;
    }

    @LayoutlibDelegate
    /*package*/ static float native_measureText(Paint thisPaint, char[] text, int index,
            int count) {
        // get the delegate
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        return delegate.measureText(text, index, count);
    }

    @LayoutlibDelegate
    /*package*/ static float native_measureText(Paint thisPaint, String text, int start, int end) {
        return native_measureText(thisPaint, text.toCharArray(), start, end - start);
    }

    @LayoutlibDelegate
    /*package*/ static float native_measureText(Paint thisPaint, String text) {
        return native_measureText(thisPaint, text.toCharArray(), 0, text.length());
    }

    @LayoutlibDelegate
    /*package*/ static int native_breakText(Paint thisPaint, char[] text, int index, int count,
            float maxWidth, float[] measuredWidth) {

        // get the delegate
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return 0;
        }

        int inc = count > 0 ? 1 : -1;

        int measureIndex = 0;
        float measureAcc = 0;
        for (int i = index; i != index + count; i += inc, measureIndex++) {
            int start, end;
            if (i < index) {
                start = i;
                end = index;
            } else {
                start = index;
                end = i;
            }

            // measure from start to end
            float res = delegate.measureText(text, start, end - start + 1);

            if (measuredWidth != null) {
                measuredWidth[measureIndex] = res;
            }

            measureAcc += res;
            if (res > maxWidth) {
                // we should not return this char index, but since it's 0-based
                // and we need to return a count, we simply return measureIndex;
                return measureIndex;
            }

        }

        return measureIndex;
    }

    @LayoutlibDelegate
    /*package*/ static int native_breakText(Paint thisPaint, String text, boolean measureForwards,
            float maxWidth, float[] measuredWidth) {
        return native_breakText(thisPaint, text.toCharArray(), 0, text.length(), maxWidth,
                measuredWidth);
    }

    @LayoutlibDelegate
    /*package*/ static int native_init() {
        Paint_Delegate newDelegate = new Paint_Delegate();
        return sManager.addNewDelegate(newDelegate);
    }

    @LayoutlibDelegate
    /*package*/ static int native_initWithPaint(int paint) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(paint);
        if (delegate == null) {
            return 0;
        }

        Paint_Delegate newDelegate = new Paint_Delegate(delegate);
        return sManager.addNewDelegate(newDelegate);
    }

    @LayoutlibDelegate
    /*package*/ static void native_reset(int native_object) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return;
        }

        delegate.reset();
    }

    @LayoutlibDelegate
    /*package*/ static void native_set(int native_dst, int native_src) {
        // get the delegate from the native int.
        Paint_Delegate delegate_dst = sManager.getDelegate(native_dst);
        if (delegate_dst == null) {
            return;
        }

        // get the delegate from the native int.
        Paint_Delegate delegate_src = sManager.getDelegate(native_src);
        if (delegate_src == null) {
            return;
        }

        delegate_dst.set(delegate_src);
    }

    @LayoutlibDelegate
    /*package*/ static int native_getStyle(int native_object) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return 0;
        }

        return delegate.mStyle;
    }

    @LayoutlibDelegate
    /*package*/ static void native_setStyle(int native_object, int style) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return;
        }

        delegate.mStyle = style;
    }

    @LayoutlibDelegate
    /*package*/ static int native_getStrokeCap(int native_object) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return 0;
        }

        return delegate.mCap;
    }

    @LayoutlibDelegate
    /*package*/ static void native_setStrokeCap(int native_object, int cap) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return;
        }

        delegate.mCap = cap;
    }

    @LayoutlibDelegate
    /*package*/ static int native_getStrokeJoin(int native_object) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return 0;
        }

        return delegate.mJoin;
    }

    @LayoutlibDelegate
    /*package*/ static void native_setStrokeJoin(int native_object, int join) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return;
        }

        delegate.mJoin = join;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_getFillPath(int native_object, int src, int dst) {
        Paint_Delegate paint = sManager.getDelegate(native_object);
        if (paint == null) {
            return false;
        }

        Path_Delegate srcPath = Path_Delegate.getDelegate(src);
        if (srcPath == null) {
            return true;
        }

        Path_Delegate dstPath = Path_Delegate.getDelegate(dst);
        if (dstPath == null) {
            return true;
        }

        Stroke stroke = paint.getJavaStroke();
        Shape strokeShape = stroke.createStrokedShape(srcPath.getJavaShape());

        dstPath.setJavaShape(strokeShape);

        // FIXME figure out the return value?
        return true;
    }

    @LayoutlibDelegate
    /*package*/ static int native_setShader(int native_object, int shader) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return shader;
        }

        delegate.mShader = Shader_Delegate.getDelegate(shader);

        return shader;
    }

    @LayoutlibDelegate
    /*package*/ static int native_setColorFilter(int native_object, int filter) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return filter;
        }

        delegate.mColorFilter = ColorFilter_Delegate.getDelegate(filter);;

        // since none of those are supported, display a fidelity warning right away
        if (delegate.mColorFilter != null && delegate.mColorFilter.isSupported() == false) {
            Bridge.getLog().fidelityWarning(LayoutLog.TAG_COLORFILTER,
                    delegate.mColorFilter.getSupportMessage(), null, null /*data*/);
        }

        return filter;
    }

    @LayoutlibDelegate
    /*package*/ static int native_setXfermode(int native_object, int xfermode) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return xfermode;
        }

        delegate.mXfermode = Xfermode_Delegate.getDelegate(xfermode);

        return xfermode;
    }

    @LayoutlibDelegate
    /*package*/ static int native_setPathEffect(int native_object, int effect) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return effect;
        }

        delegate.mPathEffect = PathEffect_Delegate.getDelegate(effect);

        return effect;
    }

    @LayoutlibDelegate
    /*package*/ static int native_setMaskFilter(int native_object, int maskfilter) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return maskfilter;
        }

        delegate.mMaskFilter = MaskFilter_Delegate.getDelegate(maskfilter);

        // since none of those are supported, display a fidelity warning right away
        if (delegate.mMaskFilter != null && delegate.mMaskFilter.isSupported() == false) {
            Bridge.getLog().fidelityWarning(LayoutLog.TAG_MASKFILTER,
                    delegate.mMaskFilter.getSupportMessage(), null, null /*data*/);
        }

        return maskfilter;
    }

    @LayoutlibDelegate
    /*package*/ static int native_setTypeface(int native_object, int typeface) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return 0;
        }

        delegate.mTypeface = Typeface_Delegate.getDelegate(typeface);
        delegate.updateFontObject();
        return typeface;
    }

    @LayoutlibDelegate
    /*package*/ static int native_setRasterizer(int native_object, int rasterizer) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return rasterizer;
        }

        delegate.mRasterizer = Rasterizer_Delegate.getDelegate(rasterizer);

        // since none of those are supported, display a fidelity warning right away
        if (delegate.mRasterizer != null && delegate.mRasterizer.isSupported() == false) {
            Bridge.getLog().fidelityWarning(LayoutLog.TAG_RASTERIZER,
                    delegate.mRasterizer.getSupportMessage(), null, null /*data*/);
        }

        return rasterizer;
    }

    @LayoutlibDelegate
    /*package*/ static int native_getTextAlign(int native_object) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return 0;
        }

        return delegate.mTextAlign;
    }

    @LayoutlibDelegate
    /*package*/ static void native_setTextAlign(int native_object, int align) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return;
        }

        delegate.mTextAlign = align;
    }

    @LayoutlibDelegate
    /*package*/ static float native_getFontMetrics(int native_paint, FontMetrics metrics) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_paint);
        if (delegate == null) {
            return 0.f;
        }

        return delegate.getFontMetrics(metrics);
    }

    @LayoutlibDelegate
    /*package*/ static int native_getTextWidths(int native_object, char[] text, int index,
            int count, float[] widths) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(native_object);
        if (delegate == null) {
            return 0;
        }

        if (delegate.mFonts.size() > 0) {
            // FIXME: handle multi-char characters (see measureText)
            float totalAdvance = 0;
            for (int i = 0; i < count; i++) {
                char c = text[i + index];
                boolean found = false;
                for (FontInfo info : delegate.mFonts) {
                    if (info.mFont.canDisplay(c)) {
                        float adv = info.mMetrics.charWidth(c);
                        totalAdvance += adv;
                        if (widths != null) {
                            widths[i] = adv;
                        }

                        found = true;
                        break;
                    }
                }

                if (found == false) {
                    // no advance for this char.
                    if (widths != null) {
                        widths[i] = 0.f;
                    }
                }
            }

            return (int) totalAdvance;
        }

        return 0;
    }

    @LayoutlibDelegate
    /*package*/ static int native_getTextWidths(int native_object, String text, int start,
            int end, float[] widths) {
        return native_getTextWidths(native_object, text.toCharArray(), start, end - start, widths);
    }

    @LayoutlibDelegate
    /*package*/ static void native_getTextPath(int native_object,
                char[] text, int index, int count, float x, float y, int path) {
        // FIXME
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "Paint.getTextPath is not supported.", null, null /*data*/);
    }

    @LayoutlibDelegate
    /*package*/ static void native_getTextPath(int native_object,
            String text, int start, int end, float x, float y, int path) {
        // FIXME
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "Paint.getTextPath is not supported.", null, null /*data*/);
    }

    @LayoutlibDelegate
    /*package*/ static void nativeGetStringBounds(int nativePaint, String text, int start,
            int end, Rect bounds) {
        nativeGetCharArrayBounds(nativePaint, text.toCharArray(), start, end - start, bounds);
    }

    @LayoutlibDelegate
    /*package*/ static void nativeGetCharArrayBounds(int nativePaint, char[] text, int index,
            int count, Rect bounds) {

        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(nativePaint);
        if (delegate == null) {
            return;
        }

        // FIXME should test if the main font can display all those characters.
        // See MeasureText
        if (delegate.mFonts.size() > 0) {
            FontInfo mainInfo = delegate.mFonts.get(0);

            Rectangle2D rect = mainInfo.mFont.getStringBounds(text, index, index + count,
                    delegate.mFontContext);
            bounds.set(0, 0, (int) rect.getWidth(), (int) rect.getHeight());
        }
    }

    @LayoutlibDelegate
    /*package*/ static void finalizer(int nativePaint) {
        sManager.removeJavaReferenceFor(nativePaint);
    }

    // ---- Private delegate/helper methods ----

    /*package*/ Paint_Delegate() {
        reset();
    }

    private Paint_Delegate(Paint_Delegate paint) {
        set(paint);
    }

    private void set(Paint_Delegate paint) {
        mFlags = paint.mFlags;
        mColor = paint.mColor;
        mStyle = paint.mStyle;
        mCap = paint.mCap;
        mJoin = paint.mJoin;
        mTextAlign = paint.mTextAlign;
        mTypeface = paint.mTypeface;
        mStrokeWidth = paint.mStrokeWidth;
        mStrokeMiter = paint.mStrokeMiter;
        mTextSize = paint.mTextSize;
        mTextScaleX = paint.mTextScaleX;
        mTextSkewX = paint.mTextSkewX;
        mXfermode = paint.mXfermode;
        mColorFilter = paint.mColorFilter;
        mShader = paint.mShader;
        mPathEffect = paint.mPathEffect;
        mMaskFilter = paint.mMaskFilter;
        mRasterizer = paint.mRasterizer;
        updateFontObject();
    }

    private void reset() {
        mFlags = Paint.DEFAULT_PAINT_FLAGS;
        mColor = 0xFF000000;
        mStyle = Paint.Style.FILL.nativeInt;
        mCap = Paint.Cap.BUTT.nativeInt;
        mJoin = Paint.Join.MITER.nativeInt;
        mTextAlign = 0;
        mTypeface = Typeface_Delegate.getDelegate(Typeface.sDefaults[0].native_instance);
        mStrokeWidth = 1.f;
        mStrokeMiter = 4.f;
        mTextSize = 20.f;
        mTextScaleX = 1.f;
        mTextSkewX = 0.f;
        mXfermode = null;
        mColorFilter = null;
        mShader = null;
        mPathEffect = null;
        mMaskFilter = null;
        mRasterizer = null;
        updateFontObject();
    }

    /**
     * Update the {@link Font} object from the typeface, text size and scaling
     */
    @SuppressWarnings("deprecation")
    private void updateFontObject() {
        if (mTypeface != null) {
            // Get the fonts from the TypeFace object.
            List<Font> fonts = mTypeface.getFonts();

            // create new font objects as well as FontMetrics, based on the current text size
            // and skew info.
            ArrayList<FontInfo> infoList = new ArrayList<FontInfo>(fonts.size());
            for (Font font : fonts) {
                FontInfo info = new FontInfo();
                info.mFont = font.deriveFont(mTextSize);
                if (mTextScaleX != 1.0 || mTextSkewX != 0) {
                    // TODO: support skew
                    info.mFont = info.mFont.deriveFont(new AffineTransform(
                            mTextScaleX, mTextSkewX, 0, 0, 1, 0));
                }
                info.mMetrics = Toolkit.getDefaultToolkit().getFontMetrics(info.mFont);

                infoList.add(info);
            }

            mFonts = Collections.unmodifiableList(infoList);
        }
    }

    /*package*/ float measureText(char[] text, int index, int count) {

        // WARNING: the logic in this method is similar to Canvas_Delegate.native_drawText
        // Any change to this method should be reflected there as well

        if (mFonts.size() > 0) {
            FontInfo mainFont = mFonts.get(0);
            int i = index;
            int lastIndex = index + count;
            float total = 0f;
            while (i < lastIndex) {
                // always start with the main font.
                int upTo = mainFont.mFont.canDisplayUpTo(text, i, lastIndex);
                if (upTo == -1) {
                    // shortcut to exit
                    return total + mainFont.mMetrics.charsWidth(text, i, lastIndex - i);
                } else if (upTo > 0) {
                    total += mainFont.mMetrics.charsWidth(text, i, upTo - i);
                    i = upTo;
                    // don't call continue at this point. Since it is certain the main font
                    // cannot display the font a index upTo (now ==i), we move on to the
                    // fallback fonts directly.
                }

                // no char supported, attempt to read the next char(s) with the
                // fallback font. In this case we only test the first character
                // and then go back to test with the main font.
                // Special test for 2-char characters.
                boolean foundFont = false;
                for (int f = 1 ; f < mFonts.size() ; f++) {
                    FontInfo fontInfo = mFonts.get(f);

                    // need to check that the font can display the character. We test
                    // differently if the char is a high surrogate.
                    int charCount = Character.isHighSurrogate(text[i]) ? 2 : 1;
                    upTo = fontInfo.mFont.canDisplayUpTo(text, i, i + charCount);
                    if (upTo == -1) {
                        total += fontInfo.mMetrics.charsWidth(text, i, charCount);
                        i += charCount;
                        foundFont = true;
                        break;

                    }
                }

                // in case no font can display the char, measure it with the main font.
                if (foundFont == false) {
                    int size = Character.isHighSurrogate(text[i]) ? 2 : 1;
                    total += mainFont.mMetrics.charsWidth(text, i, size);
                    i += size;
                }
            }

            return total;
        }

        return 0;
    }

    private float getFontMetrics(FontMetrics metrics) {
        if (mFonts.size() > 0) {
            java.awt.FontMetrics javaMetrics = mFonts.get(0).mMetrics;
            if (metrics != null) {
                // Android expects negative ascent so we invert the value from Java.
                metrics.top = - javaMetrics.getMaxAscent();
                metrics.ascent = - javaMetrics.getAscent();
                metrics.descent = javaMetrics.getDescent();
                metrics.bottom = javaMetrics.getMaxDescent();
                metrics.leading = javaMetrics.getLeading();
            }

            return javaMetrics.getHeight();
        }

        return 0;
    }



    private static void setFlag(Paint thisPaint, int flagMask, boolean flagValue) {
        // get the delegate from the native int.
        Paint_Delegate delegate = sManager.getDelegate(thisPaint.mNativePaint);
        if (delegate == null) {
            return;
        }

        if (flagValue) {
            delegate.mFlags |= flagMask;
        } else {
            delegate.mFlags &= ~flagMask;
        }
    }
}
