/*
 * Copyright (c) 2011, 2018, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package gc.hashcode;

import java.util.ArrayList;
import java.util.Random;

/**
 * Helper class for the hash code tests.
 */
public final class HCHelper {

    /**
     * Evacuation list 0 constant.
     */
    public static final int EVAC_LIST_0 = 0;
    /**
     * Evacuation list 1 constant.
     */
    public static final int EVAC_LIST_1 = 1;
    /**
     * Evacuation list 2 constant.
     */
    public static final int EVAC_LIST_2 = 2;
    /**
     * Evacuation list 3 constant.
     */
    public static final int EVAC_LIST_3 = 3;
    /**
     * Evacuation list 4 constant.
     */
    public static final int EVAC_LIST_4 = 4;
    /**
     * Evacuation list 5 constant.
     */
    public static final int EVAC_LIST_5 = 5;
    /**
     * Evacuation list 0 percentage constant.
     */
    public static final double EVAC_SIZE_0 = 0.50;
    /**
     * Evacuation list 1 percentage constant.
     */
    public static final double EVAC_SIZE_1 = 0.14;
    /**
     * Evacuation list 2 percentage constant.
     */
    public static final double EVAC_SIZE_2 = 0.12;
    /**
     * Evacuation list 3 percentage constant.
     */
    public static final double EVAC_SIZE_3 = 0.10;
    /**
     * Evacuation list 4 percentage constant.
     */
    public static final double EVAC_SIZE_4 = 0.07;
    /**
     * Evacuation list 5 percentage constant.
     */
    public static final double EVAC_SIZE_5 = 0.05;

    /**
     * Helper class that allocates memory and also tracks the original
     * as well as current hash code.
     */
    final class AllocObject {
        private byte[] allocatedArray;
        private int hashValue;

        /**
         * Create a new allocator object that allocates size bytes.
         *
         * @param size Number of bytes to allocate.
         */
        AllocObject(int size) {
            allocatedArray = new byte[size];
            hashValue = allocatedArray.hashCode();
        }

        /**
         * Get the stored hash code value.
         *
         * @return Stored hash code.
         */
        int getStoredHashValue() {
            return hashValue;
        }

        /**
         * Get the current hash code value.
         *
         * @return Current hash code.
         */
        int getCurrentHashValue() {
            return allocatedArray.hashCode();
        }

        /**
         * Get the size of the allocated object.
         *
         * @return Size of allocated object.
         */
        int getAllocatedSize() {
            return allocatedArray.length;
        }
    }

    /**
     * Helper class that holds all the allocation lists.
     */
    final class AllocInfo {
        private long allocatedSize;
        private long numOfAllocedObjs;
        private ArrayList safeList;
        private ArrayList allocList;
        private ArrayList evacList0;
        private ArrayList evacList1;
        private ArrayList evacList2;
        private ArrayList evacList3;
        private ArrayList evacList4;
        private ArrayList evacList5;

        /**
         * Create the helper object.
         */
        AllocInfo() {
            allocatedSize = 0;
            numOfAllocedObjs = 0;
            safeList = new ArrayList();
            allocList = new ArrayList();
            evacList0 = new ArrayList();
            evacList1 = new ArrayList();
            evacList2 = new ArrayList();
            evacList3 = new ArrayList();
            evacList4 = new ArrayList();
            evacList5 = new ArrayList();
        }

        /**
         * Get the amount of memory allocated in total.
         *
         * @return Total allocated size.
         */
        public long getAllocatedSize() {
            return allocatedSize;
        }

        /**
         * Set the amount of memory allocated in total.
         *
         * @param allocatedSize Total allocated size.
         */
        public void setAllocatedSize(long allocatedSize) {
            this.allocatedSize = allocatedSize;
        }

        /**
         * Get total number of objects allocated.
         *
         * @return Number of objects allocated.
         */
        public long getNumOfAllocedObjs() {
            return numOfAllocedObjs;
        }

        /**
         * Set total number of objects allocated.
         *
         * @param numOfAllocedObjs Number of objects allocated.
         */
        public void setNumOfAllocedObjs(long numOfAllocedObjs) {
            this.numOfAllocedObjs = numOfAllocedObjs;
        }

        /**
         * Increase the number of objects allocated.
         */
        public void incNumOfAllocedObjs() {
            numOfAllocedObjs++;
        }

        /**
         * Decrease the number of objects allocated.
         */
        public void decNumOfAllocedObjs() {
            numOfAllocedObjs--;
        }

        /**
         * Get the safe list.
         *
         * @return ArrayList that contains the safe list.
         */
        public ArrayList getSafeList() {
            return safeList;
        }

        /**
         * Get the alloc list.
         *
         * @return ArrayList that contains the alloc list.
         */
        public ArrayList getAllocList() {
            return allocList;
        }

        /**
         * Get evacuation list 0.
         *
         * @return ArrayList that contains evacuation list 0.
         */
        public ArrayList getEvacList0() {
            return evacList0;
        }

        /**
         * Get evacuation list 1.
         *
         * @return ArrayList that contains evacuation list 1.
         */
        public ArrayList getEvacList1() {
            return evacList1;
        }

        /**
         * Get evacuation list 2.
         *
         * @return ArrayList that contains evacuation list 2.
         */
        public ArrayList getEvacList2() {
            return evacList2;
        }

        /**
         * Get evacuation list 3.
         *
         * @return ArrayList that contains evacuation list 3.
         */
        public ArrayList getEvacList3() {
            return evacList3;
        }

        /**
         * Get evacuation list 4.
         *
         * @return ArrayList that contains evacuation list 4.
         */
        public ArrayList getEvacList4() {
            return evacList4;
        }

        /**
         * Get evacuation list 5.
         *
         * @return ArrayList that contains evacuation list 5.
         */
        public ArrayList getEvacList5() {
            return evacList5;
        }
    }


    private int minSize;
    private int maxSize;
    private double percentToFill;
    private int allocTrigSize;
    private AllocInfo ai;
    private Random rnd;

    private long sizeLimit0;
    private long sizeLimit1;
    private long sizeLimit2;
    private long sizeLimit3;
    private long sizeLimit4;
    private long sizeLimit5;

    /**
     * Create the helper class.
     *
     * @param minSize Minimum size of objects to allocate.
     * @param maxSize Maximum size of objects to allocate.
     * @param seed Random seed to use.
     * @param percentToFill Percentage of the heap to fill.
     * @param allocTrigSize Object size to use when triggering a GC.
     */
    public HCHelper(int minSize, int maxSize, long seed,
                    double percentToFill, int allocTrigSize) {
        this.minSize = minSize;
        this.maxSize = maxSize;
        this.percentToFill = percentToFill;
        this.allocTrigSize = allocTrigSize;
        ai = new AllocInfo();
        rnd = new Random(seed);

        sizeLimit0 = 0;
        sizeLimit1 = 0;
        sizeLimit2 = 0;
        sizeLimit3 = 0;
        sizeLimit4 = 0;
        sizeLimit5 = 0;
    }

    /**
     * Setup all the evacuation lists and fill them with objects.
     */
    public void setupLists() {
        Runtime r = Runtime.getRuntime();
        long maxMem = r.maxMemory();
        long safeMaxMem = (long) (maxMem * percentToFill);
        sizeLimit0 = (long) (safeMaxMem * EVAC_SIZE_0);
        sizeLimit1 = (long) (safeMaxMem * EVAC_SIZE_1);
        sizeLimit2 = (long) (safeMaxMem * EVAC_SIZE_2);
        sizeLimit3 = (long) (safeMaxMem * EVAC_SIZE_3);
        sizeLimit4 = (long) (safeMaxMem * EVAC_SIZE_4);
        sizeLimit5 = (long) (safeMaxMem * EVAC_SIZE_5);

        // Fill the memory with objects
        System.gc();
        allocObjects(ai.getEvacList0(), sizeLimit0);
        System.gc();
        allocObjects(ai.getEvacList1(), sizeLimit1);
        System.gc();
        allocObjects(ai.getEvacList2(), sizeLimit2);
        System.gc();
        allocObjects(ai.getEvacList3(), sizeLimit3);
        System.gc();
        allocObjects(ai.getEvacList4(), sizeLimit4);
        System.gc();
        allocObjects(ai.getEvacList5(), sizeLimit5);
        System.gc();
    }

    private void allocObjects(ArrayList al, long totalSizeLimit) {
        long allocedSize = 0;
        int multiplier = maxSize - minSize;

        while (allocedSize < totalSizeLimit) {
            int allocSize = minSize + (int) (rnd.nextDouble() * multiplier);
            if (allocSize >= totalSizeLimit - allocedSize) {
                allocSize = (int) (totalSizeLimit - allocedSize);
            }

            al.add(new AllocObject(allocSize));
            allocedSize += allocSize;
        }
    }

    /**
     * Free all objects in a specific evacuation list.
     *
     * @param listNr The evacuation list to clear. Must be between 0 and 5.
     */
    public void clearList(int listNr) {
        if (listNr < EVAC_LIST_0 || listNr > EVAC_LIST_5) {
            throw new IllegalArgumentException("List to removed bust be "
                    + "between EVAC_LIST_0 and EVAC_LIST_5");
        }

        switch (listNr) {
            case EVAC_LIST_0:
                ai.getEvacList0().clear();
                break;
            case EVAC_LIST_1:
                ai.getEvacList1().clear();
                break;
            case EVAC_LIST_2:
                ai.getEvacList2().clear();
                break;
            case EVAC_LIST_3:
                ai.getEvacList3().clear();
                break;
            case EVAC_LIST_4:
                ai.getEvacList4().clear();
                break;
            case EVAC_LIST_5:
                ai.getEvacList5().clear();
                break;
            default: // Should never occur, since we test the listNr param
                break;
        }
    }

    /**
     * Verify the hash codes for a list of AllocObject:s.
     *
     * @param objList ArrayList containing AllocObject:s
     * @return true if all hash codes are OK, otherwise false
     */
    boolean verifyHashCodes(ArrayList objList) {
        // Check the hash values
        for (int i = 0; i < objList.size(); i++) {
            AllocObject tmp = (AllocObject) objList.get(i);
            if (tmp.getStoredHashValue() != tmp.getCurrentHashValue()) {
                // At least one of the hash values mismatch, so the test failed
                return false;
            }
        }

        return true;
    }


    /**
     * Verify the hash codes for all objects in all the lists.
     *
     * @return Success if all hash codes matches the original hash codes.
     */
    public boolean verifyHashCodes() {
        return verifyHashCodes(ai.getAllocList())
                && verifyHashCodes(ai.getSafeList())
                && verifyHashCodes(ai.getEvacList0())
                && verifyHashCodes(ai.getEvacList1())
                && verifyHashCodes(ai.getEvacList2())
                && verifyHashCodes(ai.getEvacList3())
                && verifyHashCodes(ai.getEvacList4())
                && verifyHashCodes(ai.getEvacList5());
    }

    /**
     * Free all allocated objects from all the lists.
     */
    public void cleanupLists() {
        ai.getAllocList().clear();
        ai.getSafeList().clear();

        ai.getEvacList0().clear();
        ai.getEvacList1().clear();
        ai.getEvacList2().clear();
        ai.getEvacList3().clear();
        ai.getEvacList4().clear();
        ai.getEvacList5().clear();
    }

    /**
     * Get the size of evacuation list 0.
     *
     * @return Size of evacuation list 0.
     */
    public long getEvac0Size() {
        return sizeLimit0;
    }

    /**
     * Get the size of evacuation list 1.
     *
     * @return Size of evacuation list 1.
     */
    public long getEvac1Size() {
        return sizeLimit1;
    }

    /**
     * Get the size of evacuation list 2.
     *
     * @return Size of evacuation list 2.
     */
    public long getEvac2Size() {
        return sizeLimit2;
    }

    /**
     * Get the size of evacuation list 3.
     *
     * @return Size of evacuation list 3.
     */
    public long getEvac3Size() {
        return sizeLimit3;
    }

    /**
     * Get the size of evacuation list 4.
     *
     * @return Size of evacuation list 4.
     */
    public long getEvac4Size() {
        return sizeLimit4;
    }

    /**
     * Get the size of evacuation list 5.
     *
     * @return Size of evacuation list 5.
     */
    public long getEvac5Size() {
        return sizeLimit5;
    }
}