package android.graphics.drawable;

import static org.junit.Assert.assertTrue;

import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Outline;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.Path.Direction;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.Region;
import android.test.AndroidTestCase;
import android.util.Log;
import android.util.PathParser;
import java.io.File;
import java.io.FileOutputStream;
import java.util.Arrays;
import org.junit.Test;

public class AdaptiveIconDrawableTest extends AndroidTestCase {

    public static final String TAG = AdaptiveIconDrawableTest.class.getSimpleName();
    public static void L(String s, Object... parts) {
        Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
    }
    private Drawable mBackgroundDrawable;
    private Drawable mForegroundDrawable;
    private AdaptiveIconDrawable mIconDrawable;
    private File mDir;

    /**
     * When setBound isn't called before draw method is called.
     * Nothing is drawn.
     */
    @Test
    public void testDraw_withoutBounds() throws Exception {
        mBackgroundDrawable = new ColorDrawable(Color.BLUE);
        mForegroundDrawable = new ColorDrawable(Color.RED);
        mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
        mDir = getContext().getExternalFilesDir(null);
        L("writing temp bitmaps to %s...", mDir);

        final Bitmap bm_test = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);
        final Bitmap bm_org = bm_test.copy(Config.ARGB_8888, false);
        final Canvas can1 = new Canvas(bm_test);

        // Even when setBounds is not called, should not crash
        mIconDrawable.draw(can1);
        // Draws nothing! Hence same as original.
        if (!equalBitmaps(bm_test, bm_org)) {
            findBitmapDifferences(bm_test, bm_org);
            fail("bm differs, check " + mDir);
        }
    }

    /**
     * When setBound is called, translate accordingly.
     */
    @Test
    public void testDraw_withBounds() throws Exception {
        int dpi = 4 ;
        int top = 18 * dpi;
        int left = 18 * dpi;
        int right = 90 * dpi;
        int bottom = 90 * dpi;
        int width = right - left;
        int height = bottom - top;

        mIconDrawable = (AdaptiveIconDrawable) getContext().getResources().getDrawable(android.R.drawable.sym_def_app_icon);
        mDir = getContext().getExternalFilesDir(null);
        L("writing temp bitmaps to %s...", mDir);
        final Bitmap bm_org = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        final Canvas can_org = new Canvas(bm_org);
        mIconDrawable.setBounds(0, 0, width, height);
        mIconDrawable.draw(can_org);

        // Tested bitmap is drawn from the adaptive icon drawable.
        final Bitmap bm_test = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
        final Canvas can_test = new Canvas(bm_test);

        mIconDrawable.setBounds(left, top, right, bottom);
        can_test.translate(-left, -top);
        mIconDrawable.draw(can_test);
        can_test.translate(left, top);


        bm_org.compress(Bitmap.CompressFormat.PNG, 100,
            new FileOutputStream(new File(mDir, "adaptive-bm-original.png")));
        bm_test.compress(Bitmap.CompressFormat.PNG, 100,
            new FileOutputStream(new File(mDir, "adaptive-bm-test.png")));
        Region region = new Region(new Rect(0, 0, width, height));

        Path circle = new Path();
        circle.addCircle(width / 2, height / 2,  (right - left)/2 -10 /* room for anti-alias */, Direction.CW);

        region.setPath(circle, region);
        if (!equalBitmaps(bm_test, bm_org, region)) {
            findBitmapDifferences(bm_test, bm_org);
            fail("bm differs, check " + mDir);
        }
    }

    /**
     * When setBound isn't called before getIconMask method is called.
     * default device config mask is returned.
     */
    @Test
    public void testGetIconMask_withoutBounds() throws Exception {
        mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
        Path pathFromDrawable = mIconDrawable.getIconMask();
        Path pathFromDeviceConfig = PathParser.createPathFromPathData(
            Resources.getSystem().getString(com.android.internal.R.string.config_icon_mask));

        RectF boundFromDrawable = new RectF();
        pathFromDrawable.computeBounds(boundFromDrawable, true);

        RectF boundFromDeviceConfig = new RectF();
        pathFromDeviceConfig.computeBounds(boundFromDeviceConfig, true);

        double delta = 0.01;
        assertEquals("left", boundFromDrawable.left, boundFromDeviceConfig.left, delta);
        assertEquals("top", boundFromDrawable.top, boundFromDeviceConfig.top, delta);
        assertEquals("right", boundFromDrawable.right, boundFromDeviceConfig.right, delta);
        assertEquals("bottom", boundFromDrawable.bottom, boundFromDeviceConfig.bottom, delta);

        assertTrue("path from device config is convex.", pathFromDeviceConfig.isConvex());
        assertTrue("path from drawable is convex.", pathFromDrawable.isConvex());
    }

    @Test
    public void testGetIconMaskAfterSetBounds() throws Exception {
        int dpi = 4;
        int top = 18 * dpi;
        int left = 18 * dpi;
        int right = 90 * dpi;
        int bottom = 90 * dpi;

        mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
        mIconDrawable.setBounds(left, top, right, bottom);
        RectF maskBounds = new RectF();

        mIconDrawable.getIconMask().computeBounds(maskBounds, true);

        double delta = 0.01;
        assertEquals("left", left, maskBounds.left, delta);
        assertEquals("top", top, maskBounds.top, delta);
        assertEquals("right", right, maskBounds.right, delta);
        assertEquals("bottom", bottom, maskBounds.bottom, delta);

        assertTrue(mIconDrawable.getIconMask().isConvex());
    }

    @Test
    public void testGetOutline_withBounds() throws Exception {
        int dpi = 4;
        int top = 18 * dpi;
        int left = 18 * dpi;
        int right = 90 * dpi;
        int bottom = 90 * dpi;

        mIconDrawable = new AdaptiveIconDrawable(mBackgroundDrawable, mForegroundDrawable);
        mIconDrawable.setBounds(left, top, right, bottom);
        Outline outline = new Outline();
        mIconDrawable.getOutline(outline);
        assertTrue("outline path should be convex", outline.mPath.isConvex());
    }

    //
    // Utils
    //

    boolean equalBitmaps(Bitmap a, Bitmap b) {
      return equalBitmaps(a, b, null);
    }

    boolean equalBitmaps(Bitmap a, Bitmap b, Region region) {
        if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) return false;

        final int w = a.getWidth();
        final int h = a.getHeight();
        int[] aPix = new int[w * h];
        int[] bPix = new int[w * h];

        if (region != null) {
            for (int i = 0; i < w; i++) {
                for (int j = 0; j < h; j++) {
                    int ra = (a.getPixel(i, j) >> 16) & 0xff;
                    int ga = (a.getPixel(i, j) >> 8) & 0xff;
                    int ba = a.getPixel(i, j) & 0xff;
                    int rb = (b.getPixel(i, j) >> 16) & 0xff;
                    int gb = (b.getPixel(i, j) >> 8) & 0xff;
                    int bb = b.getPixel(i, j) & 0xff;
                    if (region.contains(i, j) && a.getPixel(i, j) != b.getPixel(i, j) ) {
                        return false;
                    }
                }
            }
            return true;
        } else {
            a.getPixels(aPix, 0, w, 0, 0, w, h);
            b.getPixels(bPix, 0, w, 0, 0, w, h);
            return Arrays.equals(aPix, bPix);
        }
    }

    void findBitmapDifferences(Bitmap a, Bitmap b) {
        if (a.getWidth() != b.getWidth() || a.getHeight() != b.getHeight()) {
            L("different sizes: %dx%d vs %dx%d",
                a.getWidth(), a.getHeight(), b.getWidth(), b.getHeight());
            return;
        }

        final int w = a.getWidth();
        final int h = a.getHeight();
        int[] aPix = new int[w * h];
        int[] bPix = new int[w * h];

        a.getPixels(aPix, 0, w, 0, 0, w, h);
        b.getPixels(bPix, 0, w, 0, 0, w, h);

        L("bitmap a (%dx%d)", w, h);
        printBits(aPix, w, h);
        L("bitmap b (%dx%d)", w, h);
        printBits(bPix, w, h);

        StringBuffer sb = new StringBuffer("Different pixels: ");
        for (int i=0; i<w; i++) {
            for (int j=0; j<h; j++) {
                if (aPix[i+w*j] != bPix[i+w*j]) {
                    sb.append(" ").append(i).append(",").append(j).append("<")
                        .append(aPix[i+w*j]).append(",").append(bPix[i+w*j]).append(">");
                }
            }
        }
        L(sb.toString());
    }

    static void printBits(int[] a, int w, int h) {
        final StringBuilder sb = new StringBuilder();
        for (int i=0; i<w; i++) {
            for (int j=0; j<h; j++) {
                sb.append(colorToChar(a[i+w*j]));
            }
            sb.append('\n');
        }
        L(sb.toString());
    }

    static char colorToChar(int color) {
        int sum = ((color >> 16) & 0xff)
            + ((color >> 8)  & 0xff)
            + ((color)       & 0xff);
        return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)];
    }
    static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
}
