/*
 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 */
import java.util.*;

import oracle.jrockit.jfr.parser.FLREvent;
import oracle.jrockit.jfr.parser.FLRStruct;

import jrockit.jfr.TestRecording;
import static jrockit.Asserts.*;

/*
 * @test TestOneObjectCountEventPerClass
 * @key jfr
 * @library ../common
 * @run main/othervm -XX:+UnlockCommercialFeatures -XX:+FlightRecorder -XX:+UnlockExperimentalVMOptions -XX:-UseFastUnorderedTimeStamps -XX:+UseParallelGC -XX:+UseParallelOldGC TestOneObjectCountEventPerClass
 */
public class TestOneObjectCountEventPerClass {
    private static String objectCountEventPath = "vm/gc/detailed/object_count_after_gc";
    private static final String gcEventPath = "vm/gc/collector/garbage_collection";

    public static void main(String[] args) throws Exception {
        TestRecording r = new TestRecording();
        try {
            enableEvent(r, objectCountEventPath);
            enableEvent(r, gcEventPath);
            r.start();

            System.gc();

            r.stop();

            List<FLREvent> allEvents = getRecordedEvents(r);
            List<FLREvent> parOldEvents = selectGCEventsWithName(allEvents, "ParallelOld");
            assertFalse(parOldEvents.isEmpty(), "Expected at least one GC event from ParallelOld");
            Long gcId = getGCId(parOldEvents.get(0));

            List<FLREvent> objectCountEvents = selectEventsWithPath(allEvents, objectCountEventPath);
            List<FLREvent> objectCountEventsFromGC = selectEventsWithGCId(objectCountEvents, gcId);
            assertFalse(objectCountEventsFromGC.isEmpty(), "Expected at least one object count after gc event");

            Map<String, Integer> eventsOfClass = new HashMap<>();
            for (FLREvent e : objectCountEventsFromGC) {
                String className = getClassName(e);
                int count = getCountOrZero(eventsOfClass, className);
                eventsOfClass.put(className, count + 1);
            }

            for (String className : eventsOfClass.keySet()) {
                int numEvents = eventsOfClass.get(className).intValue();
                if (numEvents != 1) {
                    System.out.println("Expected 1 " + objectCountEventPath + " event for class " +
                                        className + " but got " + numEvents + " events");
                }
            }
        } catch (Throwable t) {
            r.copyTo("TestOneObjectCountEventPerClass.jfr");
            throw t;
        } finally {
            r.close();
        }
    }

    private static void enableEvent(TestRecording r, String path) throws Exception {
        r.createJVMSetting(path, true, false, 0, 0);
    }

    private static List<FLREvent> getRecordedEvents(TestRecording r) throws Exception {
        return r.parser().findJVMEvents(".*");
    }

    private static List<FLREvent> selectEventsWithPath(List<FLREvent> events, String path)
        throws Exception {
        List<FLREvent> selection = new ArrayList<FLREvent>();
        for (FLREvent e : events) {
            if (e.getPath().equals(path)) {
                selection.add(e);
            }
        }
        return selection;
    }

    private static String getClassName(FLREvent e) {
        FLRStruct klass = (FLRStruct) e.getResolvedValue("class");
        FLRStruct name = (FLRStruct) klass.getResolvedValue("name");
        return name.toString();
    }

    private static int getCountOrZero(Map<String, Integer> map, String key) {
        Integer v = map.get(key);
        if (v == null) {
            return 0;
        }
        return v.intValue();
    }

    private static List<FLREvent> selectGCEventsWithName(List<FLREvent> events, String name)
        throws Exception {
        List<FLREvent> gcEvents = selectEventsWithPath(events, gcEventPath);

        List<FLREvent> selected = new ArrayList<>();
        for (FLREvent e : gcEvents) {
            FLRStruct resolved = (FLRStruct) e.getResolvedValue("name");
            String gcName = (String) resolved.getValue("name");
            if (gcName.equals(name)) {
                selected.add(e);
            }
        }
        return selected;
    }

    private static Long getGCId(FLREvent e) throws Exception {
        return (Long) e.getValue("gcId");
    }

    private static List<FLREvent> selectEventsWithGCId(List<FLREvent> events, Long gcId)
        throws Exception {
        List<FLREvent> selection = new ArrayList<>();
        for (FLREvent e : events) {
            if (gcId.equals(getGCId(e))) {
                selection.add(e);
            }
        }
        return selection;
    }
}
