/*
 * Copyright (C) 2009 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.
 */

import java.io.File;
import java.lang.ref.WeakReference;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.lang.reflect.InvocationTargetException;

public class Main {
    private static final int TEST_LENGTH = 100;

    private static boolean makeArray(int i) {
        return i % 10 == 0;
    }

    private static void fillArray(Object global[], Object local[], int i) {
        // Very stupid linking.
        local[0] = global;
        for (int j = 1; j < local.length; j++) {
            local[j] = global[j];
        }
    }

    private static Object allocInDifferentLoader() throws Exception {
        final String DEX_FILE = System.getenv("DEX_LOCATION") + "/130-hprof-ex.jar";
        Class<?> pathClassLoader = Class.forName("dalvik.system.PathClassLoader");
        if (pathClassLoader == null) {
            throw new AssertionError("Couldn't find path class loader class");
        }
        Constructor<?> constructor =
            pathClassLoader.getDeclaredConstructor(String.class, ClassLoader.class);
        ClassLoader loader = (ClassLoader)constructor.newInstance(
                DEX_FILE, ClassLoader.getSystemClassLoader());
        Class<?> allocator = loader.loadClass("Allocator");
        return allocator.getDeclaredMethod("allocObject", null).invoke(null);
    }

    private static void createDumpAndConv() throws RuntimeException {
        File dumpFile = null;
        File convFile = null;

        try {
            // Now dump the heap.
            dumpFile = createDump();

            // Run hprof-conv on it.
            convFile = getConvFile();

            File hprof_conv = getHprofConf();
            try {
                ProcessBuilder pb = new ProcessBuilder(
                        hprof_conv.getAbsoluteFile().toString(),
                        dumpFile.getAbsoluteFile().toString(),
                        convFile.getAbsoluteFile().toString());
                pb.redirectErrorStream(true);
                Process process = pb.start();
                int ret = process.waitFor();
                if (ret != 0) {
                    throw new RuntimeException("Exited abnormally with " + ret);
                }
            } catch (Exception exc) {
                throw new RuntimeException(exc);
            }
        } finally {
            // Delete the files.
            if (dumpFile != null) {
                dumpFile.delete();
            }
            if (convFile != null) {
                convFile.delete();
            }
        }
    }

    public static void main(String[] args) throws Exception {
        testBasicDump();
        testAllocationTrackingAndClassUnloading();
        testGcAndDump();
    }

    private static void testBasicDump() throws Exception {
        // Create some data.
        Object data[] = new Object[TEST_LENGTH];
        for (int i = 0; i < data.length; i++) {
            if (makeArray(i)) {
                data[i] = new Object[TEST_LENGTH];
            } else {
                data[i] = String.valueOf(i);
            }
        }
        for (int i = 0; i < data.length; i++) {
            if (makeArray(i)) {
                Object data2[] = (Object[]) data[i];
                fillArray(data, data2, i);
            }
        }
        System.out.println("Generated data.");
        createDumpAndConv();
    }

    private static void testAllocationTrackingAndClassUnloading() throws Exception {
        Class<?> klass = Class.forName("org.apache.harmony.dalvik.ddmc.DdmVmInternal");
        if (klass == null) {
            throw new AssertionError("Couldn't find path class loader class");
        }
        Method enableMethod = klass.getDeclaredMethod("enableRecentAllocations",
                Boolean.TYPE);
        if (enableMethod == null) {
            throw new AssertionError("Couldn't find path class loader class");
        }
        enableMethod.invoke(null, true);
        Object o = allocInDifferentLoader();
        // Run GC to cause class unloading.
        Runtime.getRuntime().gc();
        createDumpAndConv();
        // TODO: Somehow check contents of hprof file.
        enableMethod.invoke(null, false);
    }

    private static void testGcAndDump() throws Exception {
        Allocator allocator = new Allocator();
        Dumper dumper = new Dumper(allocator);
        allocator.start();
        dumper.start();
        try {
            allocator.join();
            dumper.join();
        } catch (InterruptedException e) {
            System.err.println("join interrupted");
        }
    }

    private static class Allocator extends Thread {
        private static int ARRAY_SIZE = 1024;
        public volatile boolean running = true;
        public void run() {
            Object[] array = new Object[ARRAY_SIZE];
            int i = 0;
            while (running) {
                array[i] = new byte[1024];
                if (i % ARRAY_SIZE == 0) {
                    Main.sleep(100L);
                }
                i = (i + 1) % ARRAY_SIZE;
            }
        }
    }

    private static class Dumper extends Thread {
        Dumper(Allocator allocator) {
            this.allocator = allocator;
        }
        Allocator allocator;
        public void run() {
            for (int i = 0; i < 5; ++i) {
                Main.sleep(1000L);
                createDumpAndConv();
            }
            allocator.running = false;
        }
    }

    public static void sleep(long ms) {
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            System.err.println("sleep interrupted");
        }
    }

    private static File getHprofConf() {
        // Use the java.library.path. It points to the lib directory.
        File libDir = new File(System.getProperty("java.library.path").split(":")[0]);
        return new File(new File(libDir.getParentFile(), "bin"), "hprof-conv");
    }

    private static File createDump() {
        java.lang.reflect.Method dumpHprofDataMethod = getDumpHprofDataMethod();
        if (dumpHprofDataMethod != null) {
            File f = getDumpFile();
            try {
                dumpHprofDataMethod.invoke(null, f.getAbsoluteFile().toString());
                return f;
            } catch (Exception exc) {
                exc.printStackTrace(System.out);
            }
        } else {
            System.out.println("Could not find dump method!");
        }
        return null;
    }

    /**
     * Finds VMDebug.dumpHprofData() through reflection.  In the reference
     * implementation this will not be available.
     *
     * @return the reflection object, or null if the method can't be found
     */
    private static Method getDumpHprofDataMethod() {
        ClassLoader myLoader = Main.class.getClassLoader();
        Class<?> vmdClass;
        try {
            vmdClass = myLoader.loadClass("dalvik.system.VMDebug");
        } catch (ClassNotFoundException cnfe) {
            return null;
        }

        Method meth;
        try {
            meth = vmdClass.getMethod("dumpHprofData", String.class);
        } catch (NoSuchMethodException nsme) {
            System.err.println("Found VMDebug but not dumpHprofData method");
            return null;
        }

        return meth;
    }

    private static File getDumpFile() {
        try {
            return File.createTempFile("test-130-hprof", "dump");
        } catch (Exception exc) {
            return null;
        }
    }

    private static File getConvFile() {
        try {
            return File.createTempFile("test-130-hprof", "conv");
        } catch (Exception exc) {
            return null;
        }
    }
}