/*
 * Copyright (C) 2016 Álinson Santos Xavier <isoron@gmail.com>
 *
 * This file is part of Loop Habit Tracker.
 *
 * Loop Habit Tracker is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by the
 * Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * Loop Habit Tracker is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program. If not, see <http://www.gnu.org/licenses/>.
 */

package org.isoron.uhabits;

import android.graphics.*;
import android.os.*;
import android.support.annotation.*;
import android.view.*;
import android.widget.*;

import org.isoron.uhabits.utils.*;
import org.isoron.uhabits.widgets.*;

import java.io.*;

import static android.view.View.MeasureSpec.*;
import static junit.framework.Assert.*;

public class BaseViewTest extends BaseAndroidTest
{
    protected static final double DEFAULT_SIMILARITY_CUTOFF = 0.09;

    public static final int HISTOGRAM_BIN_SIZE = 8;

    private double similarityCutoff;

    @Override
    public void setUp()
    {
        super.setUp();
        similarityCutoff = DEFAULT_SIMILARITY_CUTOFF;
    }

    protected void assertRenders(View view, String expectedImagePath)
        throws IOException
    {
        StringBuilder errorMessage = new StringBuilder();
        expectedImagePath = getVersionedViewAssetPath(expectedImagePath);

        if (view.isLayoutRequested()) measureView(view, view.getMeasuredWidth(),
            view.getMeasuredHeight());

        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap actual = view.getDrawingCache();
        Bitmap expected = getBitmapFromAssets(expectedImagePath);

        int width = actual.getWidth();
        int height = actual.getHeight();
        Bitmap scaledExpected =
            Bitmap.createScaledBitmap(expected, width, height, true);

        double distance;
        boolean similarEnough = true;

        if ((distance = compareHistograms(getHistogram(actual),
            getHistogram(scaledExpected))) > similarityCutoff)
        {
            similarEnough = false;
            errorMessage.append(String.format(
                "Rendered image has wrong histogram (distance=%f). ",
                distance));
        }

        if (!similarEnough)
        {
            saveBitmap(expectedImagePath, ".expected", scaledExpected);
            String path = saveBitmap(expectedImagePath, "", actual);
            errorMessage.append(
                String.format("Actual rendered image saved to %s", path));
            fail(errorMessage.toString());
        }

        expected.recycle();
        scaledExpected.recycle();
    }

    @NonNull
    protected FrameLayout convertToView(BaseWidget widget,
                                        int width,
                                        int height)
    {
        widget.setDimensions(
            new WidgetDimensions(width, height, width, height));
        FrameLayout view = new FrameLayout(targetContext);
        RemoteViews remoteViews = widget.getPortraitRemoteViews();
        view.addView(remoteViews.apply(targetContext, view));
        measureView(view, width, height);
        return view;
    }

    protected int dpToPixels(int dp)
    {
        return (int) InterfaceUtils.dpToPixels(targetContext, dp);
    }

    protected void measureView(View view, int width, int height)
    {
        int specWidth = makeMeasureSpec(width, View.MeasureSpec.EXACTLY);
        int specHeight = makeMeasureSpec(height, View.MeasureSpec.EXACTLY);

        view.measure(specWidth, specHeight);
        view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
    }

    protected void setSimilarityCutoff(double similarityCutoff)
    {
        this.similarityCutoff = similarityCutoff;
    }

    protected void skipAnimation(View view)
    {
        ViewPropertyAnimator animator = view.animate();
        animator.setDuration(0);
        animator.start();
    }

    protected void tap(GestureDetector.OnGestureListener view, int x, int y)
        throws InterruptedException
    {
        long now = SystemClock.uptimeMillis();
        MotionEvent e =
            MotionEvent.obtain(now, now, MotionEvent.ACTION_UP, dpToPixels(x),
                dpToPixels(y), 0);
        view.onSingleTapUp(e);
        e.recycle();
    }

    private double compareHistograms(int[][] actualHistogram,
                                     int[][] expectedHistogram)
    {
        long diff = 0;
        long total = 0;

        for (int i = 0; i < 256 / HISTOGRAM_BIN_SIZE; i++)
        {
            diff += Math.abs(actualHistogram[0][i] - expectedHistogram[0][i]);
            diff += Math.abs(actualHistogram[1][i] - expectedHistogram[1][i]);
            diff += Math.abs(actualHistogram[2][i] - expectedHistogram[2][i]);
            diff += Math.abs(actualHistogram[3][i] - expectedHistogram[3][i]);

            total += actualHistogram[0][i];
            total += actualHistogram[1][i];
            total += actualHistogram[2][i];
            total += actualHistogram[3][i];
        }

        return (double) diff / total / 2;
    }

    private Bitmap getBitmapFromAssets(String path) throws IOException
    {
        InputStream stream = testContext.getAssets().open(path);
        return BitmapFactory.decodeStream(stream);
    }

    private int[][] getHistogram(Bitmap bitmap)
    {
        int histogram[][] = new int[4][256 / HISTOGRAM_BIN_SIZE];

        for (int x = 0; x < bitmap.getWidth(); x++)
        {
            for (int y = 0; y < bitmap.getHeight(); y++)
            {
                int color = bitmap.getPixel(x, y);
                int[] argb = new int[]{
                    (color >> 24) & 0xff, //alpha
                    (color >> 16) & 0xff, //red
                    (color >> 8) & 0xff, //green
                    (color) & 0xff  //blue
                };

                histogram[0][argb[0] / HISTOGRAM_BIN_SIZE]++;
                histogram[1][argb[1] / HISTOGRAM_BIN_SIZE]++;
                histogram[2][argb[2] / HISTOGRAM_BIN_SIZE]++;
                histogram[3][argb[3] / HISTOGRAM_BIN_SIZE]++;
            }
        }

        return histogram;
    }

    private String getVersionedViewAssetPath(String path)
    {
        String result = null;

        if (android.os.Build.VERSION.SDK_INT >= 21)
        {
            try
            {
                String vpath = "views-v21/" + path;
                testContext.getAssets().open(vpath);
                result = vpath;
            }
            catch (IOException e)
            {
                // ignored
            }
        }

        if (result == null) result = "views/" + path;

        return result;
    }

    private String saveBitmap(String filename, String suffix, Bitmap bitmap)
        throws IOException
    {
        File dir = FileUtils.getSDCardDir("test-screenshots");
        if (dir == null) dir = FileUtils.getFilesDir("test-screenshots");
        if (dir == null) throw new RuntimeException(
            "Could not find suitable dir for screenshots");

        filename = filename.replaceAll("\\.png$", suffix + ".png");
        String absolutePath =
            String.format("%s/%s", dir.getAbsolutePath(), filename);

        File parent = new File(absolutePath).getParentFile();
        if (!parent.exists() && !parent.mkdirs()) throw new RuntimeException(
            String.format("Could not create dir: %s",
                parent.getAbsolutePath()));

        FileOutputStream out = new FileOutputStream(absolutePath);
        bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);

        return absolutePath;
    }
}
