//
//  ========================================================================
//  Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  All rights reserved. This program and the accompanying materials
//  are made available under the terms of the Eclipse Public License v1.0
//  and Apache License v2.0 which accompanies this distribution.
//
//      The Eclipse Public License is available at
//      http://www.eclipse.org/legal/epl-v10.html
//
//      The Apache License v2.0 is available at
//      http://www.opensource.org/licenses/apache2.0.php
//
//  You may elect to redistribute this code under either of these licenses.
//  ========================================================================
//

package org.eclipse.jetty.server;


import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;

import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.TimerScheduler;
import org.junit.After;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;

import static org.hamcrest.Matchers.equalTo;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertThat;

public class LowResourcesMonitorTest
{
    QueuedThreadPool _threadPool;
    Server _server;
    ServerConnector _connector;
    LowResourceMonitor _lowResourcesMonitor;
    
    @Before
    public void before() throws Exception
    {
        _threadPool = new QueuedThreadPool();
        _threadPool.setMaxThreads(50);

        _server = new Server(_threadPool);
        _server.manage(_threadPool);

        _server.addBean(new TimerScheduler());
        
        _connector = new ServerConnector(_server);
        _connector.setPort(0);
        _connector.setIdleTimeout(35000);
        _server.addConnector(_connector);

        _server.setHandler(new DumpHandler());

        _lowResourcesMonitor=new LowResourceMonitor(_server);
        _lowResourcesMonitor.setLowResourcesIdleTimeout(200);
        _lowResourcesMonitor.setMaxConnections(20);
        _lowResourcesMonitor.setPeriod(900);
        _server.addBean(_lowResourcesMonitor);

        _server.start();
    }
    
    @After
    public void after() throws Exception
    {
        _server.stop();
    }
    
    
    @Test
    public void testLowOnThreads() throws Exception
    {
        Thread.sleep(1200);
        _threadPool.setMaxThreads(_threadPool.getThreads()-_threadPool.getIdleThreads()+10);
        Thread.sleep(1200);
        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());
        
        final CountDownLatch latch = new CountDownLatch(1);
        
        for (int i=0;i<100;i++)
        {
            _threadPool.execute(new Runnable()
            {
                @Override
                public void run()
                {
                    try
                    {
                        latch.await();
                    }
                    catch (InterruptedException e)
                    {
                        e.printStackTrace();
                    }
                }
            });
        }
        
        Thread.sleep(1200);
        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
        
        latch.countDown();
        Thread.sleep(1200);
        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());      
    }
    

    @Ignore ("not reliable")
    @Test
    public void testLowOnMemory() throws Exception
    {
        _lowResourcesMonitor.setMaxMemory(Runtime.getRuntime().totalMemory()-Runtime.getRuntime().freeMemory()+(100*1024*1024));
        Thread.sleep(1200);
        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());

        byte[] data = new byte[100*1024*1024];
        Arrays.fill(data,(byte)1);
        int hash = Arrays.hashCode(data);
        assertThat(hash,not(equalTo(0)));

        Thread.sleep(1200);
        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
        data=null;
        System.gc();
        System.gc();

        Thread.sleep(1200);
        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());   
    }
    

    @Test
    public void testMaxConnectionsAndMaxIdleTime() throws Exception
    {
        _lowResourcesMonitor.setMaxMemory(0);
        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());

        Socket[] socket = new Socket[_lowResourcesMonitor.getMaxConnections()+1];
        for (int i=0;i<socket.length;i++)
            socket[i]=new Socket("localhost",_connector.getLocalPort());
        
        Thread.sleep(1200);
        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());

        Socket newSocket = new Socket("localhost",_connector.getLocalPort());
        
        // wait for low idle time to close sockets, but not new Socket
        Thread.sleep(1200);
        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());   

        for (int i=0;i<socket.length;i++)
            Assert.assertEquals(-1,socket[i].getInputStream().read());
        
        newSocket.getOutputStream().write("GET / HTTP/1.0\r\n\r\n".getBytes(StandardCharsets.UTF_8));
        Assert.assertEquals('H',newSocket.getInputStream().read());
        
    }
    
    @Test
    public void testMaxLowResourceTime() throws Exception
    {
        _lowResourcesMonitor.setMaxLowResourcesTime(2000);
        Assert.assertFalse(_lowResourcesMonitor.isLowOnResources());

        Socket socket0 = new Socket("localhost",_connector.getLocalPort());
        _lowResourcesMonitor.setMaxMemory(1);

        Thread.sleep(1200);
        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());

        Socket socket1 = new Socket("localhost",_connector.getLocalPort());

        Thread.sleep(1200);
        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
        Assert.assertEquals(-1,socket0.getInputStream().read());
        socket1.getOutputStream().write("G".getBytes(StandardCharsets.UTF_8));

        Thread.sleep(1200);
        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
        socket1.getOutputStream().write("E".getBytes(StandardCharsets.UTF_8));

        Thread.sleep(1200);
        Assert.assertTrue(_lowResourcesMonitor.isLowOnResources());
        Assert.assertEquals(-1,socket1.getInputStream().read());

    }
}
