// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

/* TEST: Usage
 * DESCRIPTION: Three usage scenarios that monitor the number of live handles and GC Collections
 */

using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;

// the class that holds the HandleCollectors
public class HandleCollectorTest
{
    private static HandleCollector s_hc = new HandleCollector("hc", 100);

    public HandleCollectorTest()
    {
        s_hc.Add();
    }

    public static int Count
    {
        get { return s_hc.Count; }
    }

    ~HandleCollectorTest()
    {
        s_hc.Remove();
    }

    public static void Reset()
    {
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();
        s_hc = new HandleCollector("hc", 100);
    }
}


public class Usage
{
    private int _numTests = 0;
    private int _numInstances = 100;
    private const int deltaPercent = 10;

    // ensures GC Collections occur when handle count exceeds maximum
    private bool Case1()
    {
        _numTests++;

        // clear GC
        GC.Collect();
        GC.WaitForPendingFinalizers();
        GC.Collect();

        HandleCollectorTest h;
        int original = GC.CollectionCount(0);

        // create objects and let them go out of scope
        for (int i = 0; i < _numInstances; i++)
            h = new HandleCollectorTest();

        h = null;
        GC.WaitForPendingFinalizers();

        // Collection should not have occurred
        if (GC.CollectionCount(0) != original)
        {
            Console.WriteLine("Early collection!");
            Console.WriteLine("Case 1 Failed!");
            return false;
        }

        new HandleCollectorTest();

        if ((GC.CollectionCount(0) - original) > 0)
        {
            Console.WriteLine("Case 1 Passed!");
            return true;
        }

        Console.WriteLine("Expected collection did not occur!");
        Console.WriteLine("Case 1 Failed!");
        return false;
    }

    // ensures GC Collection does not occur when handle count stays below maximum
    private bool Case2()
    {
        _numTests++;
        int handleCount = 0;

        for (int i = 0; i < _numInstances; i++)
        {
            new HandleCollectorTest();
            GC.WaitForPendingFinalizers();
            handleCount = HandleCollectorTest.Count;
            //Note that the GC should occur when handle count is 101 but it will happen at anytime after a creation and we stick to the previous
            //count to avoid error
        }

        Console.WriteLine("{0}, {1}", handleCount, _numInstances);

        if (handleCount == _numInstances)
        {
            Console.WriteLine("Case 2 Passed!");
            return true;
        }

        Console.WriteLine("Case 2 Failed!");
        return false;
    }


    // ensures GC Collections frequency decrease by threshold
    private bool Case3()
    {
        _numTests++;

        int gcCount = GC.CollectionCount(2);
        int handleCount = HandleCollectorTest.Count;
        int prevHandleCount = HandleCollectorTest.Count;

        List<HandleCollectorTest> list = new List<HandleCollectorTest>();

        for (int i = 0; i < deltaPercent; i++)
        {
            do
            {
                HandleCollectorTest h = new HandleCollectorTest();
                if ((HandleCollectorTest.Count % 2) == 0)
                    list.Add(h);
                GC.WaitForPendingFinalizers();
                if (GC.CollectionCount(2) != gcCount)
                {
                    gcCount = GC.CollectionCount(2);
                    break;
                }
                else
                    handleCount = HandleCollectorTest.Count;
            } while (true);

            // ensure threshold is increasing
            if (!CheckPercentageIncrease(handleCount, prevHandleCount))
            {
                // see github#4093 for the rationale for fail-fast in this test.
                Environment.FailFast(string.Empty);
                return false;
            }
            prevHandleCount = handleCount;
        }


        Console.WriteLine("Case 3 Passed!");
        return true;
    }


    // Checks that the threshold increases are within 0.2 error margine of deltaPercent
    private bool CheckPercentageIncrease(int current, int previous)
    {
        bool retValue = true;
        if (previous != 0)
        {
            double value = ((double)(current - previous)) / (double)previous;
            double expected = (double)deltaPercent / 100;
            double errorMargin = Math.Abs((double)(value - expected) / (double)expected);
            retValue = (errorMargin < 0.2);
        }

        return retValue;
    }


    public bool RunTest()
    {
        int numPassed = 0;

        if (Case1())
        {
            numPassed++;
        }

        HandleCollectorTest.Reset();

        if (Case2())
        {
            numPassed++;
        }

        HandleCollectorTest.Reset();

        if (Case3())
        {
            numPassed++;
        }

        return (numPassed == _numTests);
    }


    public static int Main()
    {
        if (GC.CollectionCount(0) > 20)
        {
            Console.WriteLine("GC Stress is enabled");
            Console.WriteLine("Abort Test");
            return 100;
        }

        Usage u = new Usage();

        if (u.RunTest())
        {
            Console.WriteLine();
            Console.WriteLine("Test Passed!");
            return 100;
        }

        Console.WriteLine();
        Console.WriteLine("Test Failed!");
        return 1;
    }
}
