/*
 * Copyright (C) 2015 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.layoutlib.bridge.util.CachedPathIteratorFactory;
import com.android.tools.layoutlib.annotations.LayoutlibDelegate;

import com.android.layoutlib.bridge.util.CachedPathIteratorFactory.CachedPathIterator;

import java.awt.geom.PathIterator;

/**
 * Delegate implementing the native methods of {@link android.graphics.PathMeasure}
 * <p/>
 * Through the layoutlib_create tool, the original native methods of PathMeasure have been
 * replaced by
 * calls to methods of the same name in this delegate class.
 * <p/>
 * 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 PathMeasure class.
 *
 * @see DelegateManager
 */
public final class PathMeasure_Delegate {

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

    // ---- delegate data ----
    private CachedPathIteratorFactory mOriginalPathIterator;

    private long mNativePath;


    private PathMeasure_Delegate(long native_path, boolean forceClosed) {
        mNativePath = native_path;
        if (native_path != 0) {
            if (forceClosed) {
                // Copy the path and call close
                native_path = Path_Delegate.nInit(native_path);
                Path_Delegate.nClose(native_path);
            }

            Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
            mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
                    .getPathIterator(null));
        }
    }

    @LayoutlibDelegate
    /*package*/ static long native_create(long native_path, boolean forceClosed) {
        return sManager.addNewDelegate(new PathMeasure_Delegate(native_path, forceClosed));
    }

    @LayoutlibDelegate
    /*package*/ static void native_destroy(long native_instance) {
        sManager.removeJavaReferenceFor(native_instance);
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_getPosTan(long native_instance, float distance, float pos[],
            float tan[]) {
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "PathMeasure.getPostTan is not supported.", null, null);
        return false;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_getMatrix(long native_instance, float distance, long
            native_matrix, int flags) {
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "PathMeasure.getMatrix is not supported.", null, null);
        return false;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_nextContour(long native_instance) {
        Bridge.getLog().fidelityWarning(LayoutLog.TAG_UNSUPPORTED,
                "PathMeasure.nextContour is not supported.", null, null);
        return false;
    }

    @LayoutlibDelegate
    /*package*/ static void native_setPath(long native_instance, long native_path, boolean
            forceClosed) {
        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        if (native_path != 0) {
            if (forceClosed) {
                // Copy the path and call close
                native_path = Path_Delegate.nInit(native_path);
                Path_Delegate.nClose(native_path);
            }

            Path_Delegate pathDelegate = Path_Delegate.getDelegate(native_path);
            pathMeasure.mOriginalPathIterator = new CachedPathIteratorFactory(pathDelegate.getJavaShape()
                    .getPathIterator(null));
        }

        pathMeasure.mNativePath = native_path;
    }

    @LayoutlibDelegate
    /*package*/ static float native_getLength(long native_instance) {
        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        if (pathMeasure.mOriginalPathIterator == null) {
            return 0;
        }

        return pathMeasure.mOriginalPathIterator.iterator().getTotalLength();
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_isClosed(long native_instance) {
        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        Path_Delegate path = Path_Delegate.getDelegate(pathMeasure.mNativePath);
        if (path == null) {
            return false;
        }

        int type = 0;
        float segment[] = new float[6];
        for (PathIterator pi = path.getJavaShape().getPathIterator(null); !pi.isDone(); pi.next()) {
            type = pi.currentSegment(segment);
        }

        // A path is a closed path if the last element is SEG_CLOSE
        return type == PathIterator.SEG_CLOSE;
    }

    @LayoutlibDelegate
    /*package*/ static boolean native_getSegment(long native_instance, float startD, float stopD,
            long native_dst_path, boolean startWithMoveTo) {
        if (startD < 0) {
            startD = 0;
        }

        if (startD >= stopD) {
            return false;
        }

        PathMeasure_Delegate pathMeasure = sManager.getDelegate(native_instance);
        assert pathMeasure != null;

        CachedPathIterator iterator = pathMeasure.mOriginalPathIterator.iterator();
        float accLength = startD;
        boolean isZeroLength = true; // Whether the output has zero length or not
        float[] points = new float[6];

        iterator.jumpToSegment(accLength);
        while (!iterator.isDone() && (stopD - accLength > 0.1f)) {
            int type = iterator.currentSegment(points, stopD - accLength);

            if (accLength - iterator.getCurrentSegmentLength() <= stopD) {
                if (startWithMoveTo) {
                    startWithMoveTo = false;

                    // If this segment is a MOVETO, then we just use that one. If not, then we issue
                    // a first moveto
                    if (type != PathIterator.SEG_MOVETO) {
                        float[] lastPoint = new float[2];
                        iterator.getCurrentSegmentEnd(lastPoint);
                        Path_Delegate.nMoveTo(native_dst_path, lastPoint[0], lastPoint[1]);
                    }
                }

                isZeroLength = isZeroLength && iterator.getCurrentSegmentLength() > 0;
                switch (type) {
                    case PathIterator.SEG_MOVETO:
                        Path_Delegate.nMoveTo(native_dst_path, points[0], points[1]);
                        break;
                    case PathIterator.SEG_LINETO:
                        Path_Delegate.nLineTo(native_dst_path, points[0], points[1]);
                        break;
                    case PathIterator.SEG_CLOSE:
                        Path_Delegate.nClose(native_dst_path);
                        break;
                    case PathIterator.SEG_CUBICTO:
                        Path_Delegate.nCubicTo(native_dst_path, points[0], points[1],
                                points[2], points[3],
                                points[4], points[5]);
                        break;
                    case PathIterator.SEG_QUADTO:
                        Path_Delegate.nQuadTo(native_dst_path, points[0], points[1],
                                points[2],
                                points[3]);
                        break;
                    default:
                        assert false;
                }
            }

            accLength += iterator.getCurrentSegmentLength();
            iterator.next();
        }

        return !isZeroLength;
    }
}
