/*
 * Copyright (C) 2017 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 com.example.android.renderscript_healingbrush;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Rect;
import android.renderscript.Allocation;
import android.renderscript.Element;
import android.renderscript.RenderScript;
import android.renderscript.Script;
import android.renderscript.Type;
import android.util.Log;

public class Healing {
    private static final String TAG = "Healing";
    Rect mRoiBounds; // bounding box of the ROI
    float[] mPointsXY; // polygon point in original image coordnates
    int mCutOffsetX; // image coords of the cut  (mPointsXY - mPasteOffX + mCutOffsetX)
    int mCutOffsetY; // image coords of the cut (mPointsXY - mPasteOffY + mCutOffsetY)
    Bitmap mUndoBitmap;

    public Healing(Rect roiBounds, float[] pointsXY, int cutOffsetX, int cutOffsetY) {
        mRoiBounds = roiBounds;
        mPointsXY = pointsXY;
        mCutOffsetX = cutOffsetX;
        mCutOffsetY = cutOffsetY;
    }

    private static Bitmap buildMask(Rect rec, float[] xy) {
        Bitmap bitmap = Bitmap.createBitmap(rec.width(), rec.height(), Bitmap.Config.ALPHA_8);

        Canvas c = new Canvas(bitmap);
        Paint paint = new Paint();
        paint.setStyle(Paint.Style.FILL);
        paint.setColor(Color.BLACK);
        Path path = new Path();
        for (int i = 0; i < xy.length; i += 2) {
            if (i == 0) {
                path.moveTo(xy[i] - rec.left, xy[i + 1] - rec.top);
            } else {
                path.lineTo(xy[i] - rec.left, xy[i + 1] - rec.top);
            }
        }
        path.close();
        c.drawPath(path, paint);
        return bitmap;
    }

    /**
     * This function only assumes mPointsXY, mPasteOffX, mPasteOffY
     *
     * @param healing
     * @param rs
     * @param image
     */
    public void heal(ScriptC_healing healing, RenderScript rs, Bitmap image, Bitmap output) {
        long time = System.nanoTime();

        Type.Builder floatImage = new Type.Builder(rs, Element.F32_3(rs));
        floatImage.setX(mRoiBounds.width());
        floatImage.setY(mRoiBounds.height());


        Bitmap mask_bitmap = buildMask(mRoiBounds, mPointsXY);
        Bitmap dest_bitmap = createMutableBitmap(image, mRoiBounds.left, mRoiBounds.top,
                mRoiBounds.width(), mRoiBounds.height());
        Allocation dest_alloc = Allocation.createFromBitmap(rs, dest_bitmap);
        Bitmap src_bitmap = createMutableBitmap(image, mCutOffsetX, mCutOffsetY,
                mRoiBounds.width(), mRoiBounds.height());
        Allocation src_alloc = Allocation.createFromBitmap(rs, src_bitmap);
        Allocation mask_alloc = Allocation.createFromBitmap(rs, mask_bitmap);

        healing.invoke_heal(mask_alloc, src_alloc, dest_alloc);

        dest_alloc.copyTo(dest_bitmap);

        dest_bitmap.setHasAlpha(true);

        // build the undo
        mUndoBitmap = Bitmap.createBitmap(mRoiBounds.width(), mRoiBounds.height(),
                Bitmap.Config.ARGB_8888);
        Canvas undoCanvas = new Canvas(mUndoBitmap);
        Rect undoRect = new Rect(0, 0, mRoiBounds.width(), mRoiBounds.height());
        undoCanvas.drawBitmap(output, mRoiBounds, undoRect, null);

        Canvas c = new Canvas(output);
        c.drawBitmap(image, 0, 0, null);
        c.drawBitmap(dest_bitmap, mRoiBounds.left, mRoiBounds.top, null);
        Log.v(TAG, " time ss to smart paste = " + (System.nanoTime() - time) / 1E6f + "ms");
        heal_orig(healing, rs, image, output);

    }


    /**
     * This function only assumes mPointsXY, mPasteOffX, mPasteOffY
     *
     * @param healing
     * @param rs
     * @param image
     */
    public void heal_orig(ScriptC_healing healing, RenderScript rs, Bitmap image, Bitmap output) {
        long time = System.nanoTime();
        Type.Builder floatImage = new Type.Builder(rs, Element.F32_3(rs));
        floatImage.setX(mRoiBounds.width());
        floatImage.setY(mRoiBounds.height());

        Bitmap maskBitmap = buildMask(mRoiBounds, mPointsXY);

        Allocation dest1 = Allocation.createTyped(rs, floatImage.create());
        Allocation dest2 = Allocation.createTyped(rs, floatImage.create());
        healing.set_dest1(dest1);
        healing.set_dest2(dest2);

        Bitmap destBitmap = createMutableBitmap(image, mRoiBounds.left, mRoiBounds.top,
                mRoiBounds.width(), mRoiBounds.height());
        Allocation dest_uc4 = Allocation.createFromBitmap(rs, destBitmap);
        healing.forEach_convert_to_f(dest_uc4, dest1);

        Bitmap src = createMutableBitmap(image, mCutOffsetX, mCutOffsetY,
                mRoiBounds.width(), mRoiBounds.height());
        Allocation src_f3 = Allocation.createTyped(rs, floatImage.create());
        Allocation src_uc4 = Allocation.createFromBitmap(rs, src);
        healing.forEach_convert_to_f(src_uc4, src_f3);
        healing.set_src(src_f3);

        Allocation mask = Allocation.createFromBitmap(rs, maskBitmap);
        healing.set_mask(mask);

        Allocation laplace_f3 = Allocation.createTyped(rs, floatImage.create());
        healing.set_laplace(laplace_f3);

        Script.LaunchOptions options = new Script.LaunchOptions();
        options.setX(1, mRoiBounds.width() - 1);
        options.setY(1, mRoiBounds.height() - 1);
        healing.forEach_laplacian(laplace_f3, options);
        healing.forEach_copyMasked(mask, dest1);

        int steps = (int) Math.hypot(mRoiBounds.width(), mRoiBounds.height()); // match RS Single source
        Log.v(TAG, "Healing_orig  :steps = " + steps);
        for (int i = 0; i < steps; i++) {
            healing.forEach_solve1(mask, dest2);
            healing.forEach_solve2(mask, dest1);
        }

        healing.forEach_convert_to_uc(dest1, dest_uc4);
        rs.finish();

        healing.forEach_alphaMask(dest_uc4, dest_uc4);
        rs.finish();

        dest_uc4.copyTo(destBitmap);
        rs.finish();
        destBitmap.setHasAlpha(true);
        rs.finish();
        // build the undo
        mUndoBitmap = Bitmap.createBitmap(mRoiBounds.width(), mRoiBounds.height(),
                Bitmap.Config.ARGB_8888);
        Canvas undoCanvas = new Canvas(mUndoBitmap);
        Rect undoRect = new Rect(0, 0, mRoiBounds.width(), mRoiBounds.height());
        undoCanvas.drawBitmap(output, mRoiBounds, undoRect, null);

        Canvas c = new Canvas(output);
        c.drawBitmap(image, 0, 0, null);
        c.drawBitmap(destBitmap, mRoiBounds.left, mRoiBounds.top, null);
        Log.v(TAG, " time to smart paste = " + (System.nanoTime() - time) / 1E6f + "ms");
    }

    Bitmap createMutableBitmap(Bitmap image, int x, int y, int width, int height) {
        Bitmap ret = Bitmap.createBitmap(image, x, y, width, height);
        return ret.copy(Bitmap.Config.ARGB_8888, true);
    }

    private static int calcMaskArea(Allocation mask) {
        int w = mask.getType().getX();
        int h = mask.getType().getY();
        byte[] data = new byte[w * h];
        mask.copyTo(data);
        int count = 0;
        int val = data[0];
        for (int i = 0; i < data.length; i++) {
            if (data[i] != val) {
                count++;
            }
        }
        return count;
    }

    Bitmap getmUndoBitmap() {
        return mUndoBitmap;
    }
}