package com.engebretson;

import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;

import java.util.*;
import java.util.concurrent.TimeUnit;

/**
 * Benchmark demonstrating performance impact of polymorphic call sites on HashMap.<init>(Map).
 * 
 * This test shows that manual inlining of HashMap construction can significantly outperform
 * the built-in HashMap(Map) constructor when the constructor call site becomes polymorphic.
 * 
 * The setup poisons call sites with HashMap, TreeMap, and LinkedHashMap to create
 * polymorphic call sites before benchmarking begins.
 */
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
@State(Scope.Benchmark)
@Warmup(iterations = 3, time = 1, timeUnit = TimeUnit.SECONDS)
@Measurement(iterations = 5, time = 3, timeUnit = TimeUnit.SECONDS)
@Fork(value = 1, jvmArgs = {"-XX:+UseParallelGC", "-Xmx3g"})
public class HashMapConstructorBenchmark {

    private static final int POISON_ITERATIONS = 10000;
    private static final double CAPACITY_FACTOR = 1.35; // Account for 0.75 load factor

    @Param({"0", "5", "75", "1000"})
    private int mapSize;

    private HashMap<String, Integer> inputHashMap;
    private TreeMap<String, Integer> inputTreeMap;
    private LinkedHashMap<String, Integer> inputLinkedHashMap;

    @Setup(Level.Trial)
    public void setup() {
        // Create test data with identical contents
        inputHashMap = new HashMap<>();
        inputTreeMap = new TreeMap<>();
        inputLinkedHashMap = new LinkedHashMap<>();

        for (int i = 0; i < mapSize; i++) {
            String key = "key" + i;
            Integer value = i;
            inputHashMap.put(key, value);
            inputTreeMap.put(key, value);
            inputLinkedHashMap.put(key, value);
        }

        // Poison HashMap.<init>(Map) call site with polymorphic calls
        // This ensures the constructor becomes a polymorphic call site before benchmarking
        for (int i = 0; i < POISON_ITERATIONS; i++) {
            Map<String, Integer> source = (i % 3 == 0) ? inputHashMap : (i % 3 == 1) ? inputTreeMap : inputLinkedHashMap;
            HashMap<String, Integer> temp = new HashMap<>(source);
            if (temp.size() != mapSize) throw new RuntimeException();
        }
        // Poison the calls made from the manualInlining benchmark method, without poisoning the benchmark method itself
       for (int i = 0; i < POISON_ITERATIONS; i++) {
            Map<String, Integer> source = (i % 3 == 0) ? inputHashMap : (i % 3 == 1) ? inputTreeMap : inputLinkedHashMap;
            HashMap<String, Integer> temp = new HashMap<>(source.size());
            for (Map.Entry<String, Integer> entry : source.entrySet()) {
                temp.put(entry.getKey(), entry.getValue());
            }
            if (temp.size() != mapSize) throw new RuntimeException();
        }
    }

    /**
     * Benchmark using HashMap's built-in constructor that takes a Map parameter.
     * This approach suffers from polymorphic call site overhead.
     */
    @Benchmark
    public HashMap<String, Integer> hashMapConstructor_HashMap(Blackhole bh) {
        HashMap<String, Integer> result = new HashMap<>(inputHashMap);
        bh.consume(result);
        return result;
    }

    /**
     * Benchmark using manual inlining of the HashMap construction logic.
     * This approach creates monomorphic call sites that can be fully optimized.
     * Uses capacity factor to avoid resize overhead during population.
     */
    @Benchmark
    public HashMap<String, Integer> manualInlining_HashMap(Blackhole bh) {
        HashMap<String, Integer> result = new HashMap<>((int) (inputHashMap.size() * CAPACITY_FACTOR));
        for (Map.Entry<String, Integer> entry : inputHashMap.entrySet()) {
            result.put(entry.getKey(), entry.getValue());
        }
        bh.consume(result);
        return result;
    }
}
