/*
 * Copyright 2015 The Netty Project
 *
 * The Netty Project licenses this file to you 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:
 *
 * https://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 io.netty.microbenchmark.common;

import io.netty.microbench.util.AbstractMicrobenchmark;
import io.netty.util.collection.IntObjectHashMap;
import org.agrona.collections.Int2ObjectHashMap;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Level;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.infra.Blackhole;

import java.util.HashSet;
import java.util.Random;
import java.util.Set;

public class IntObjectHashMapBenchmark extends AbstractMicrobenchmark {
    private static final Long VALUE = Long.MAX_VALUE;

    public enum MapType {
        AGRONA,
        NETTY
    }

    public enum KeyDistribution {
        HTTP2,
        RANDOM
    }

    @Param({ "10", "100", "1000", "10000", "100000" })
    public int size;

    @Param
    public MapType mapType;

    @Param
    public KeyDistribution keyDistribution;

    private Environment environment;

    @Setup(Level.Trial)
    public void setup() {
        switch(mapType) {
            case AGRONA: {
                environment = new AgronaEnvironment();
                break;
            }
            case NETTY: {
                environment = new NettyEnvironment();
                break;
            }
            default: {
                throw new IllegalStateException("Invalid mapType: " + mapType);
            }
        }
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void put(Blackhole bh) {
        environment.put(bh);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void lookup(Blackhole bh) {
        environment.lookup(bh);
    }

    @Benchmark
    @BenchmarkMode(Mode.Throughput)
    public void remove(Blackhole bh) {
        environment.remove(bh);
    }

    private abstract class Environment {
        final int[] keys;
        Environment() {
            keys = new int[size];
            switch(keyDistribution) {
                case HTTP2:
                    for (int index = 0, key = 3; index < size; ++index, key += 2) {
                        keys[index] = key;
                    }
                    break;
                case RANDOM: {
                    // Create a 'size' # of random integers.
                    Random r = new Random();
                    Set<Integer> keySet = new HashSet<Integer>();
                    while (keySet.size() < size) {
                        keySet.add(r.nextInt());
                    }

                    int index = 0;
                    for (Integer key : keySet) {
                        keys[index++] = key;
                    }
                    break;
                }
                default: {
                    throw new IllegalStateException("Unknown keyDistribution: " + keyDistribution);
                }
            }
        }
        abstract void put(Blackhole bh);
        abstract void lookup(Blackhole bh);
        abstract void remove(Blackhole bh);
    }

    private class AgronaEnvironment extends Environment {
        private final Int2ObjectHashMap<Long> map = new Int2ObjectHashMap<Long>();

        AgronaEnvironment() {
            for (int key : keys) {
                map.put(key, VALUE);
            }
        }

        @Override
        void put(Blackhole bh) {
            Int2ObjectHashMap<Long> map = new Int2ObjectHashMap<Long>();
            for (int key : keys) {
                bh.consume(map.put(key, VALUE));
            }
        }

        @Override
        void lookup(Blackhole bh) {
            for (int key : keys) {
                bh.consume(map.get(key));
            }
        }

        @Override
        void remove(Blackhole bh) {
            Int2ObjectHashMap<Long> copy = new Int2ObjectHashMap<Long>();
            copy.putAll(map);
            for (int key : keys) {
                bh.consume(copy.remove(key));
            }
        }
    }

    private class NettyEnvironment extends Environment {
        private final IntObjectHashMap<Long> map = new IntObjectHashMap<Long>();

        NettyEnvironment() {
            for (int key : keys) {
                map.put(key, VALUE);
            }
        }

        @Override
        void put(Blackhole bh) {
            IntObjectHashMap<Long> map = new IntObjectHashMap<Long>();
            for (int key : keys) {
                bh.consume(map.put(key, VALUE));
            }
        }

        @Override
        void lookup(Blackhole bh) {
            for (int key : keys) {
                bh.consume(map.get(key));
            }
        }

        @Override
        void remove(Blackhole bh) {
            IntObjectHashMap<Long> copy = new IntObjectHashMap<Long>();
            copy.putAll(map);
            for (int key : keys) {
                bh.consume(copy.remove(key));
            }
        }
    }
}
