/*
 * Copyright (C) 2006 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.drawable;

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Region;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Parcel;
import android.test.AndroidTestCase;
import android.test.suitebuilder.annotation.SmallTest;
import android.util.Log;

import com.android.frameworks.coretests.R;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Arrays;

public class IconTest extends AndroidTestCase {
    public static final String TAG = IconTest.class.getSimpleName();
    public static void L(String s, Object... parts) {
        Log.d(TAG, (parts.length == 0) ? s : String.format(s, parts));
    }

    @SmallTest
    public void testWithBitmap() throws Exception {
        final Bitmap bm1 = Bitmap.createBitmap(100, 200, Bitmap.Config.ARGB_8888);
        final Bitmap bm2 = Bitmap.createBitmap(100, 200, Bitmap.Config.RGB_565);
        final Bitmap bm3 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                .getBitmap();

        final Canvas can1 = new Canvas(bm1);
        can1.drawColor(0xFFFF0000);
        final Canvas can2 = new Canvas(bm2);
        can2.drawColor(0xFF00FF00);

        final Icon im1 = Icon.createWithBitmap(bm1);
        final Icon im2 = Icon.createWithBitmap(bm2);
        final Icon im3 = Icon.createWithBitmap(bm3);

        final Drawable draw1 = im1.loadDrawable(mContext);
        final Drawable draw2 = im2.loadDrawable(mContext);
        final Drawable draw3 = im3.loadDrawable(mContext);

        final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
                draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        final Bitmap test2 = Bitmap.createBitmap(draw2.getIntrinsicWidth(),
                draw2.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        final Bitmap test3 = Bitmap.createBitmap(draw3.getIntrinsicWidth(),
                draw3.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);

        draw1.setBounds(0, 0, draw1.getIntrinsicWidth(), draw1.getIntrinsicHeight());
        draw1.draw(new Canvas(test1));

        draw2.setBounds(0, 0, draw2.getIntrinsicWidth(), draw2.getIntrinsicHeight());
        draw2.draw(new Canvas(test2));

        draw3.setBounds(0, 0, draw3.getIntrinsicWidth(), draw3.getIntrinsicHeight());
        draw3.draw(new Canvas(test3));

        final File dir = getContext().getExternalFilesDir(null);
        L("writing temp bitmaps to %s...", dir);

        bm1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap1-original.png")));
        test1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap1-test.png")));
        if (!equalBitmaps(bm1, test1)) {
            findBitmapDifferences(bm1, test1);
            fail("bitmap1 differs, check " + dir);
        }

        bm2.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap2-original.png")));
        test2.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap2-test.png")));
        if (!equalBitmaps(bm2, test2)) {
            findBitmapDifferences(bm2, test2);
            fail("bitmap2 differs, check " + dir);
        }

        bm3.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap3-original.png")));
        test3.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "bitmap3-test.png")));
        if (!equalBitmaps(bm3, test3)) {
            findBitmapDifferences(bm3, test3);
            fail("bitmap3 differs, check " + dir);
        }
    }

    @SmallTest
    public void testWithAdaptiveBitmap() throws Exception {
        final Bitmap bm1 = Bitmap.createBitmap(150, 150, Bitmap.Config.ARGB_8888);

        final Canvas can1 = new Canvas(bm1);
        can1.drawColor(0xFFFF0000);

        final Icon im1 = Icon.createWithAdaptiveBitmap(bm1);

        final AdaptiveIconDrawable draw1 = (AdaptiveIconDrawable) im1.loadDrawable(mContext);

        final Bitmap test1 = Bitmap.createBitmap(
            (int)(draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
            (int)(draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
            Bitmap.Config.ARGB_8888);

        draw1.setBounds(0, 0,
            (int) (draw1.getIntrinsicWidth() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())),
            (int) (draw1.getIntrinsicHeight() * (1 + 2 * AdaptiveIconDrawable.getExtraInsetFraction())));
        draw1.draw(new Canvas(test1));

        final File dir = getContext().getExternalFilesDir(null);
        L("writing temp bitmaps to %s...", dir);

        bm1.compress(Bitmap.CompressFormat.PNG, 100,
            new FileOutputStream(new File(dir, "adaptive-bitmap1-original.png")));
        test1.compress(Bitmap.CompressFormat.PNG, 100,
            new FileOutputStream(new File(dir, "adaptive-bitmap1-test.png")));
        if (!equalBitmaps(bm1, test1, draw1.getSafeZone())) {
            findBitmapDifferences(bm1, test1);
            fail("adaptive bitmap1 differs, check " + dir);
        }
    }

    @SmallTest
    public void testWithBitmapResource() throws Exception {
        final Bitmap res1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                .getBitmap();

        final Icon im1 = Icon.createWithResource(getContext(), R.drawable.landscape);
        final Drawable draw1 = im1.loadDrawable(mContext);
        final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
                draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
        draw1.draw(new Canvas(test1));

        final File dir = getContext().getExternalFilesDir(null);
        res1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "res1-original.png")));
        test1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "res1-test.png")));
        if (!equalBitmaps(res1, test1)) {
            findBitmapDifferences(res1, test1);
            fail("res1 differs, check " + dir);
        }
    }

    @SmallTest
    public void testWithFile() throws Exception {
        final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                .getBitmap();
        final File dir = getContext().getExternalFilesDir(null);
        final File file1 = new File(dir, "file1-original.png");
        bit1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(file1));

        final Icon im1 = Icon.createWithFilePath(file1.toString());
        final Drawable draw1 = im1.loadDrawable(mContext);
        final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
                draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
        draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
        draw1.draw(new Canvas(test1));

        test1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(new File(dir, "file1-test.png")));
        if (!equalBitmaps(bit1, test1)) {
            findBitmapDifferences(bit1, test1);
            fail("testWithFile: file1 differs, check " + dir);
        }
    }

    @SmallTest
    public void testAsync() throws Exception {
        final Bitmap bit1 = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                .getBitmap();
        final File dir = getContext().getExternalFilesDir(null);
        final File file1 = new File(dir, "async-original.png");
        bit1.compress(Bitmap.CompressFormat.PNG, 100,
                new FileOutputStream(file1));

        final Icon im1 = Icon.createWithFilePath(file1.toString());
        final HandlerThread thd = new HandlerThread("testAsync");
        thd.start();
        final Handler h = new Handler(thd.getLooper());
        L(TAG, "asyncTest: dispatching load to thread: " + thd);
        im1.loadDrawableAsync(mContext, new Icon.OnDrawableLoadedListener() {
            @Override
            public void onDrawableLoaded(Drawable draw1) {
                L(TAG, "asyncTest: thread: loading drawable");
                L(TAG, "asyncTest: thread: loaded: %dx%d", draw1.getIntrinsicWidth(),
                    draw1.getIntrinsicHeight());
                final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
                        draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
                draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
                draw1.draw(new Canvas(test1));

                try {
                    test1.compress(Bitmap.CompressFormat.PNG, 100,
                            new FileOutputStream(new File(dir, "async-test.png")));
                } catch (java.io.FileNotFoundException ex) {
                    fail("couldn't create test file: " + ex);
                }
                if (!equalBitmaps(bit1, test1)) {
                    findBitmapDifferences(bit1, test1);
                    fail("testAsync: file1 differs, check " + dir);
                }
            }
        }, h);
        L(TAG, "asyncTest: awaiting result");
        Thread.sleep(500); // ;_;
        assertTrue("async-test.png does not exist!", new File(dir, "async-test.png").exists());
        L(TAG, "asyncTest: done");
    }

    @SmallTest
    public void testParcel() throws Exception {
        final Bitmap originalbits = ((BitmapDrawable) getContext().getDrawable(R.drawable.landscape))
                .getBitmap();

        final ByteArrayOutputStream ostream = new ByteArrayOutputStream(
                originalbits.getWidth() * originalbits.getHeight() * 2); // guess 50% compression
        originalbits.compress(Bitmap.CompressFormat.PNG, 100, ostream);
        final byte[] pngdata = ostream.toByteArray();

        L("starting testParcel; bitmap: %d bytes, PNG: %d bytes",
                originalbits.getByteCount(),
                pngdata.length);

        final File dir = getContext().getExternalFilesDir(null);
        final File originalfile = new File(dir, "parcel-original.png");
        new FileOutputStream(originalfile).write(pngdata);

        ArrayList<Icon> imgs = new ArrayList<>();
        final Icon file1 = Icon.createWithFilePath(originalfile.getAbsolutePath());
        imgs.add(file1);
        final Icon bit1 = Icon.createWithBitmap(originalbits);
        imgs.add(bit1);
        final Icon data1 = Icon.createWithData(pngdata, 0, pngdata.length);
        imgs.add(data1);
        final Icon res1 = Icon.createWithResource(getContext(), R.drawable.landscape);
        imgs.add(res1);

        ArrayList<Icon> test = new ArrayList<>();
        final Parcel parcel = Parcel.obtain();
        int pos = 0;
        parcel.writeInt(imgs.size());
        for (Icon img : imgs) {
            img.writeToParcel(parcel, 0);
            L("used %d bytes parceling: %s", parcel.dataPosition() - pos, img);
            pos = parcel.dataPosition();
        }

        parcel.setDataPosition(0); // rewind
        final int N = parcel.readInt();
        for (int i=0; i<N; i++) {
            Icon img = Icon.CREATOR.createFromParcel(parcel);
            L("test %d: read from parcel: %s", i, img);
            final File testfile = new File(dir,
                    String.format("parcel-test%02d.png", i));

            final Drawable draw1 = img.loadDrawable(mContext);
            if (draw1 == null) {
                fail("null drawable from img: " + img);
            }
            final Bitmap test1 = Bitmap.createBitmap(draw1.getIntrinsicWidth(),
                    draw1.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
            draw1.setBounds(0, 0, test1.getWidth(), test1.getHeight());
            draw1.draw(new Canvas(test1));

            try {
                test1.compress(Bitmap.CompressFormat.PNG, 100,
                        new FileOutputStream(testfile));
            } catch (java.io.FileNotFoundException ex) {
                fail("couldn't create test file " + testfile + ": " + ex);
            }
            if (!equalBitmaps(originalbits, test1)) {
                findBitmapDifferences(originalbits, test1);
                fail(testfile + " differs from original: " + originalfile);
            }

        }
    }


    // ======== utils ========

    static final char[] GRADIENT = " .:;+=xX$#".toCharArray();
    static float[] hsv = new float[3];
    static char colorToChar(int color) {
        int sum = ((color >> 16) & 0xff)
                + ((color >> 8)  & 0xff)
                + ((color)       & 0xff);
        return GRADIENT[sum * (GRADIENT.length-1) / (3*0xff)];
    }
    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 void printBits(Bitmap a) {
        final int w = a.getWidth();
        final int h = a.getHeight();
        int[] aPix = new int[w * h];
        printBits(aPix, w, h);
    }
    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++) {
                    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);
                }
            }
        }
        L(sb.toString());
    }
}
