/*
 * Copyright (c) 2015-present, Horcrux.
 * All rights reserved.
 *
 * This source code is licensed under the MIT-style license found in the
 * LICENSE file in the root directory of this source tree.
 */


package com.horcrux.svg;

import android.graphics.Matrix;
import android.graphics.Path;
import android.graphics.PathMeasure;
import android.graphics.RectF;
import android.graphics.Region;

import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;

import javax.annotation.Nonnull;

class RNSVGRenderableManager extends ReactContextBaseJavaModule {
    RNSVGRenderableManager(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    @Nonnull
    @Override
    public String getName() {
        return "RNSVGRenderableManager";
    }

    @SuppressWarnings("unused")
    @ReactMethod(isBlockingSynchronousMethod = true)
    public boolean isPointInFill(int tag, ReadableMap options) {
        RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
        if (svg == null) {
            return false;
        }

        float scale = svg.mScale;
        float x = (float) options.getDouble("x") * scale;
        float y = (float) options.getDouble("y") * scale;

        int i = svg.hitTest(new float[]{x, y});
        return i != -1;
    }

    @SuppressWarnings("unused")
    @ReactMethod(isBlockingSynchronousMethod = true)
    public boolean isPointInStroke(int tag, ReadableMap options) {
        RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
        if (svg == null) {
            return false;
        }

        svg.getPath(null, null);
        svg.initBounds();

        float scale = svg.mScale;
        int x = (int) (options.getDouble("x") * scale);
        int y = (int) (options.getDouble("y") * scale);

        Region strokeRegion = svg.mStrokeRegion;
        return strokeRegion != null && strokeRegion.contains(x, y);
    }

    @SuppressWarnings("unused")
    @ReactMethod(isBlockingSynchronousMethod = true)
    public float getTotalLength(int tag) {
        RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
        if (svg == null) {
            return 0;
        }

        Path path = svg.getPath(null, null);
        PathMeasure pm = new PathMeasure(path, false);
        return pm.getLength() / svg.mScale;
    }

    @SuppressWarnings("unused")
    @ReactMethod(isBlockingSynchronousMethod = true)
    public WritableMap getPointAtLength(int tag, ReadableMap options) {
        RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
        if (svg == null) {
            return null;
        }

        Path path = svg.getPath(null, null);
        PathMeasure pm = new PathMeasure(path, false);
        float length = (float) options.getDouble("length");
        float scale = svg.mScale;

        float[] pos = new float[2];
        float[] tan = new float[2];
        float distance = Math.max(0, Math.min(length, pm.getLength()));
        pm.getPosTan(distance, pos, tan);

        double angle = Math.atan2(tan[1], tan[0]);
        WritableMap result = Arguments.createMap();
        result.putDouble("x", pos[0] / scale);
        result.putDouble("y", pos[1] / scale);
        result.putDouble("angle", angle);
        return result;
    }

    @SuppressWarnings("unused")
    @ReactMethod(isBlockingSynchronousMethod = true)
    public WritableMap getBBox(int tag, ReadableMap options) {
        RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
        if (svg == null) {
            return null;
        }

        boolean fill = options.getBoolean("fill");
        boolean stroke = options.getBoolean("stroke");
        boolean markers = options.getBoolean("markers");
        boolean clipped = options.getBoolean("clipped");

        Path path = svg.getPath(null, null);
        float scale = svg.mScale;
        svg.initBounds();

        RectF bounds = new RectF();
        if (fill) {
            bounds.union(svg.mFillBounds);
        }
        if (stroke) {
            bounds.union(svg.mStrokeBounds);
        }
        if (markers) {
            bounds.union(svg.mMarkerBounds);
        }
        if (clipped) {
            RectF clipBounds = svg.mClipBounds;
            if (clipBounds != null) {
                bounds.intersect(svg.mClipBounds);
            }
        }

        WritableMap result = Arguments.createMap();
        result.putDouble("x", bounds.left / scale);
        result.putDouble("y", bounds.top / scale);
        result.putDouble("width", bounds.width() / scale);
        result.putDouble("height", bounds.height() / scale);
        return result;
    }

    @SuppressWarnings("unused")
    @ReactMethod(isBlockingSynchronousMethod = true)
    public WritableMap getCTM(int tag) {
        RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
        if (svg == null) {
            return null;
        }

        float scale = svg.mScale;
        Matrix ctm = new Matrix(svg.mCTM);
        Matrix invViewBoxMatrix = svg.getSvgView().mInvViewBoxMatrix;
        ctm.preConcat(invViewBoxMatrix);

        float[] values = new float[9];
        ctm.getValues(values);

        WritableMap result = Arguments.createMap();
        result.putDouble("a", values[Matrix.MSCALE_X]);
        result.putDouble("b", values[Matrix.MSKEW_Y]);
        result.putDouble("c", values[Matrix.MSKEW_X]);
        result.putDouble("d", values[Matrix.MSCALE_Y]);
        result.putDouble("e", values[Matrix.MTRANS_X] / scale);
        result.putDouble("f", values[Matrix.MTRANS_Y] / scale);
        return result;
    }

    @SuppressWarnings("unused")
    @ReactMethod(isBlockingSynchronousMethod = true)
    public WritableMap getScreenCTM(int tag) {
        RenderableView svg = RenderableViewManager.getRenderableViewByTag(tag);
        if (svg == null) {
            return null;
        }

        float[] values = new float[9];
        svg.mCTM.getValues(values);
        float scale = svg.mScale;

        WritableMap result = Arguments.createMap();
        result.putDouble("a", values[Matrix.MSCALE_X]);
        result.putDouble("b", values[Matrix.MSKEW_Y]);
        result.putDouble("c", values[Matrix.MSKEW_X]);
        result.putDouble("d", values[Matrix.MSCALE_Y]);
        result.putDouble("e", values[Matrix.MTRANS_X] / scale);
        result.putDouble("f", values[Matrix.MTRANS_Y] / scale);
        return result;
    }
}
