/**
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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
 *
 *      http://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 org.apache.camel.impl;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import org.apache.camel.ContextTestSupport;
import org.apache.camel.ThreadPoolRejectedPolicy;
import org.apache.camel.spi.ThreadPoolProfile;
import org.apache.camel.util.concurrent.SizedScheduledExecutorService;

/**
 * @version 
 */
public class DefaultExecutorServiceManagerTest extends ContextTestSupport {
    
    public void testResolveThreadNameDefaultPattern() throws Exception {
        String foo = context.getExecutorServiceManager().resolveThreadName("foo");
        String bar = context.getExecutorServiceManager().resolveThreadName("bar");

        assertNotSame(foo, bar);
        assertTrue(foo.startsWith("Camel (" + context.getName() + ") thread "));
        assertTrue(foo.endsWith("foo"));
        assertTrue(bar.startsWith("Camel (" + context.getName() + ") thread "));
        assertTrue(bar.endsWith("bar"));
    }

    public void testGetThreadNameCustomPattern() throws Exception {
        context.getExecutorServiceManager().setThreadNamePattern("##counter# - #name#");
        assertEquals("##counter# - #name#", context.getExecutorServiceManager().getThreadNamePattern());
        String foo = context.getExecutorServiceManager().resolveThreadName("foo");
        String bar = context.getExecutorServiceManager().resolveThreadName("bar");

        assertNotSame(foo, bar);
        assertTrue(foo.startsWith("#"));
        assertTrue(foo.endsWith(" - foo"));
        assertTrue(bar.startsWith("#"));
        assertTrue(bar.endsWith(" - bar"));
    }

    public void testGetThreadNameCustomPatternCamelId() throws Exception {
        context.getExecutorServiceManager().setThreadNamePattern("##camelId# - ##counter# - #name#");
        String foo = context.getExecutorServiceManager().resolveThreadName("foo");
        String bar = context.getExecutorServiceManager().resolveThreadName("bar");

        assertNotSame(foo, bar);
        assertTrue(foo.startsWith("#" + context.getName() + " - #"));
        assertTrue(foo.endsWith(" - foo"));
        assertTrue(bar.startsWith("#" + context.getName() + " - #"));
        assertTrue(bar.endsWith(" - bar"));
    }

    public void testGetThreadNameCustomPatternWithDollar() throws Exception {
        context.getExecutorServiceManager().setThreadNamePattern("Hello - #name#");
        String foo = context.getExecutorServiceManager().resolveThreadName("foo$bar");

        assertEquals("Hello - foo$bar", foo);
    }

    public void testGetThreadNameCustomPatternLongName() throws Exception {
        context.getExecutorServiceManager().setThreadNamePattern("##counter# - #longName#");
        String foo = context.getExecutorServiceManager().resolveThreadName("foo?beer=Carlsberg");
        String bar = context.getExecutorServiceManager().resolveThreadName("bar");

        assertNotSame(foo, bar);
        assertTrue(foo.startsWith("#"));
        assertTrue(foo.endsWith(" - foo?beer=Carlsberg"));
        assertTrue(bar.startsWith("#"));
        assertTrue(bar.endsWith(" - bar"));
    }

    public void testGetThreadNameCustomPatternWithParameters() throws Exception {
        context.getExecutorServiceManager().setThreadNamePattern("##counter# - #name#");
        String foo = context.getExecutorServiceManager().resolveThreadName("foo?beer=Carlsberg");
        String bar = context.getExecutorServiceManager().resolveThreadName("bar");

        assertNotSame(foo, bar);
        assertTrue(foo.startsWith("#"));
        assertTrue(foo.endsWith(" - foo"));
        assertTrue(bar.startsWith("#"));
        assertTrue(bar.endsWith(" - bar"));
    }

    public void testGetThreadNameCustomPatternNoCounter() throws Exception {
        context.getExecutorServiceManager().setThreadNamePattern("Cool #name#");
        String foo = context.getExecutorServiceManager().resolveThreadName("foo");
        String bar = context.getExecutorServiceManager().resolveThreadName("bar");

        assertNotSame(foo, bar);
        assertEquals("Cool foo", foo);
        assertEquals("Cool bar", bar);
    }

    public void testGetThreadNameCustomPatternInvalid() throws Exception {
        context.getExecutorServiceManager().setThreadNamePattern("Cool #xxx#");
        try {
            context.getExecutorServiceManager().resolveThreadName("foo");
            fail("Should thrown an exception");
        } catch (IllegalArgumentException e) {
            assertEquals("Pattern is invalid: Cool #xxx#", e.getMessage());
        }

        // reset it so we can shutdown properly
        context.getExecutorServiceManager().setThreadNamePattern("Camel Thread #counter# - #name#");
    }

    public void testDefaultThreadPool() throws Exception {
        ExecutorService myPool = context.getExecutorServiceManager().newDefaultThreadPool(this, "myPool");
        assertEquals(false, myPool.isShutdown());

        // should use default settings
        ThreadPoolExecutor executor = (ThreadPoolExecutor) myPool;
        assertEquals(10, executor.getCorePoolSize());
        assertEquals(20, executor.getMaximumPoolSize());
        assertEquals(60, executor.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(1000, executor.getQueue().remainingCapacity());

        context.stop();
        assertEquals(true, myPool.isShutdown());
    }

    public void testDefaultUnboundedQueueThreadPool() throws Exception {
        ThreadPoolProfile custom = new ThreadPoolProfile("custom");
        custom.setPoolSize(10);
        custom.setMaxPoolSize(30);
        custom.setKeepAliveTime(50L);
        custom.setMaxQueueSize(Integer.MAX_VALUE);

        context.getExecutorServiceManager().setDefaultThreadPoolProfile(custom);
        assertEquals(true, custom.isDefaultProfile().booleanValue());

        ExecutorService myPool = context.getExecutorServiceManager().newDefaultThreadPool(this, "myPool");
        assertEquals(false, myPool.isShutdown());

        // should use default settings
        ThreadPoolExecutor executor = (ThreadPoolExecutor) myPool;
        assertEquals(10, executor.getCorePoolSize());
        assertEquals(30, executor.getMaximumPoolSize());
        assertEquals(50, executor.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(Integer.MAX_VALUE, executor.getQueue().remainingCapacity());

        context.stop();
        assertEquals(true, myPool.isShutdown());
    }

    public void testDefaultNoMaxQueueThreadPool() throws Exception {
        ThreadPoolProfile custom = new ThreadPoolProfile("custom");
        custom.setPoolSize(10);
        custom.setMaxPoolSize(30);
        custom.setKeepAliveTime(50L);
        custom.setMaxQueueSize(0);

        context.getExecutorServiceManager().setDefaultThreadPoolProfile(custom);
        assertEquals(true, custom.isDefaultProfile().booleanValue());

        ExecutorService myPool = context.getExecutorServiceManager().newDefaultThreadPool(this, "myPool");
        assertEquals(false, myPool.isShutdown());

        // should use default settings
        ThreadPoolExecutor executor = (ThreadPoolExecutor) myPool;
        assertEquals(10, executor.getCorePoolSize());
        assertEquals(30, executor.getMaximumPoolSize());
        assertEquals(50, executor.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(0, executor.getQueue().remainingCapacity());

        context.stop();
        assertEquals(true, myPool.isShutdown());
    }

    public void testCustomDefaultThreadPool() throws Exception {
        ThreadPoolProfile custom = new ThreadPoolProfile("custom");
        custom.setKeepAliveTime(20L);
        custom.setMaxPoolSize(40);
        custom.setPoolSize(5);
        custom.setMaxQueueSize(2000);

        context.getExecutorServiceManager().setDefaultThreadPoolProfile(custom);
        assertEquals(true, custom.isDefaultProfile().booleanValue());

        ExecutorService myPool = context.getExecutorServiceManager().newDefaultThreadPool(this, "myPool");
        assertEquals(false, myPool.isShutdown());

        // should use default settings
        ThreadPoolExecutor executor = (ThreadPoolExecutor) myPool;
        assertEquals(5, executor.getCorePoolSize());
        assertEquals(40, executor.getMaximumPoolSize());
        assertEquals(20, executor.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(2000, executor.getQueue().remainingCapacity());

        context.stop();
        assertEquals(true, myPool.isShutdown());
    }

    public void testGetThreadPoolProfile() throws Exception {
        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setKeepAliveTime(20L);
        foo.setMaxPoolSize(40);
        foo.setPoolSize(5);
        foo.setMaxQueueSize(2000);

        context.getExecutorServiceManager().registerThreadPoolProfile(foo);

        assertSame(foo, context.getExecutorServiceManager().getThreadPoolProfile("foo"));
    }

    public void testTwoGetThreadPoolProfile() throws Exception {
        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setKeepAliveTime(20L);
        foo.setMaxPoolSize(40);
        foo.setPoolSize(5);
        foo.setMaxQueueSize(2000);

        context.getExecutorServiceManager().registerThreadPoolProfile(foo);

        ThreadPoolProfile bar = new ThreadPoolProfile("bar");
        bar.setKeepAliveTime(40L);
        bar.setMaxPoolSize(5);
        bar.setPoolSize(1);
        bar.setMaxQueueSize(100);

        context.getExecutorServiceManager().registerThreadPoolProfile(bar);

        assertSame(foo, context.getExecutorServiceManager().getThreadPoolProfile("foo"));
        assertSame(bar, context.getExecutorServiceManager().getThreadPoolProfile("bar"));
        assertNotSame(foo, bar);

        assertFalse(context.getExecutorServiceManager().getThreadPoolProfile("foo").isDefaultProfile());
        assertFalse(context.getExecutorServiceManager().getThreadPoolProfile("bar").isDefaultProfile());
    }

    public void testGetThreadPoolProfileInheritDefaultValues() throws Exception {
        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));
        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setMaxPoolSize(40);
        context.getExecutorServiceManager().registerThreadPoolProfile(foo);
        assertSame(foo, context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ExecutorService executor = context.getExecutorServiceManager().newThreadPool(this, "MyPool", "foo");
        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, executor);
        assertEquals(40, tp.getMaximumPoolSize());
        // should inherit the default values
        assertEquals(10, tp.getCorePoolSize());
        assertEquals(60, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals("CallerRuns", tp.getRejectedExecutionHandler().toString());
    }

    public void testGetThreadPoolProfileInheritCustomDefaultValues() throws Exception {
        ThreadPoolProfile newDefault = new ThreadPoolProfile("newDefault");
        newDefault.setKeepAliveTime(30L);
        newDefault.setMaxPoolSize(50);
        newDefault.setPoolSize(5);
        newDefault.setMaxQueueSize(2000);
        newDefault.setRejectedPolicy(ThreadPoolRejectedPolicy.Abort);
        context.getExecutorServiceManager().setDefaultThreadPoolProfile(newDefault);

        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));
        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setMaxPoolSize(25);
        foo.setPoolSize(1);
        context.getExecutorServiceManager().registerThreadPoolProfile(foo);
        assertSame(foo, context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ExecutorService executor = context.getExecutorServiceManager().newThreadPool(this, "MyPool", "foo");

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, executor);
        assertEquals(25, tp.getMaximumPoolSize());
        // should inherit the default values
        assertEquals(1, tp.getCorePoolSize());
        assertEquals(30, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals("Abort", tp.getRejectedExecutionHandler().toString());
    }

    public void testGetThreadPoolProfileInheritCustomDefaultValues2() throws Exception {
        ThreadPoolProfile newDefault = new ThreadPoolProfile("newDefault");
        // just change the max pool as the default profile should then inherit the old default profile
        newDefault.setMaxPoolSize(50);
        context.getExecutorServiceManager().setDefaultThreadPoolProfile(newDefault);

        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));
        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setPoolSize(1);
        context.getExecutorServiceManager().registerThreadPoolProfile(foo);
        assertSame(foo, context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ExecutorService executor = context.getExecutorServiceManager().newThreadPool(this, "MyPool", "foo");

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, executor);
        assertEquals(1, tp.getCorePoolSize());
        // should inherit the default values
        assertEquals(50, tp.getMaximumPoolSize());
        assertEquals(60, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals("CallerRuns", tp.getRejectedExecutionHandler().toString());
    }

    public void testNewThreadPoolProfile() throws Exception {
        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setKeepAliveTime(20L);
        foo.setMaxPoolSize(40);
        foo.setPoolSize(5);
        foo.setMaxQueueSize(2000);

        ExecutorService pool = context.getExecutorServiceManager().newThreadPool(this, "Cool", foo);
        assertNotNull(pool);

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, pool);
        assertEquals(20, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(40, tp.getMaximumPoolSize());
        assertEquals(5, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewThreadPoolProfileById() throws Exception {
        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setKeepAliveTime(20L);
        foo.setMaxPoolSize(40);
        foo.setPoolSize(5);
        foo.setMaxQueueSize(2000);

        context.getExecutorServiceManager().registerThreadPoolProfile(foo);

        ExecutorService pool = context.getExecutorServiceManager().newThreadPool(this, "Cool", "foo");
        assertNotNull(pool);

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, pool);
        assertEquals(20, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(40, tp.getMaximumPoolSize());
        assertEquals(5, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewThreadPoolMinMax() throws Exception {
        ExecutorService pool = context.getExecutorServiceManager().newThreadPool(this, "Cool", 5, 10);
        assertNotNull(pool);

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, pool);
        assertEquals(60, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(10, tp.getMaximumPoolSize());
        assertEquals(5, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewFixedThreadPool() throws Exception {
        ExecutorService pool = context.getExecutorServiceManager().newFixedThreadPool(this, "Cool", 5);
        assertNotNull(pool);

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, pool);
        // a fixed dont use keep alive
        assertEquals("keepAliveTime", 0, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals("maximumPoolSize", 5, tp.getMaximumPoolSize());
        assertEquals(5, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewSingleThreadExecutor() throws Exception {
        ExecutorService pool = context.getExecutorServiceManager().newSingleThreadExecutor(this, "Cool");
        assertNotNull(pool);

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, pool);
        // a single dont use keep alive
        assertEquals("keepAliveTime", 0, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals("maximumPoolSize", 1, tp.getMaximumPoolSize());
        assertEquals(1, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewScheduledThreadPool() throws Exception {
        ExecutorService pool = context.getExecutorServiceManager().newScheduledThreadPool(this, "Cool", 5);
        assertNotNull(pool);

        SizedScheduledExecutorService tp = assertIsInstanceOf(SizedScheduledExecutorService.class, pool);
        // a scheduled dont use keep alive
        assertEquals(0, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(Integer.MAX_VALUE, tp.getMaximumPoolSize());
        assertEquals(5, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewSingleThreadScheduledExecutor() throws Exception {
        ExecutorService pool = context.getExecutorServiceManager().newSingleThreadScheduledExecutor(this, "Cool");
        assertNotNull(pool);

        SizedScheduledExecutorService tp = assertIsInstanceOf(SizedScheduledExecutorService.class, pool);
        // a scheduled dont use keep alive
        assertEquals(0, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(Integer.MAX_VALUE, tp.getMaximumPoolSize());
        assertEquals(1, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewCachedThreadPool() throws Exception {
        ExecutorService pool = context.getExecutorServiceManager().newCachedThreadPool(this, "Cool");
        assertNotNull(pool);

        ThreadPoolExecutor tp = assertIsInstanceOf(ThreadPoolExecutor.class, pool);
        assertEquals(60, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(Integer.MAX_VALUE, tp.getMaximumPoolSize());
        assertEquals(0, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewScheduledThreadPoolProfileById() throws Exception {
        assertNull(context.getExecutorServiceManager().getThreadPoolProfile("foo"));

        ThreadPoolProfile foo = new ThreadPoolProfile("foo");
        foo.setKeepAliveTime(20L);
        foo.setMaxPoolSize(40);
        foo.setPoolSize(5);
        foo.setMaxQueueSize(2000);

        context.getExecutorServiceManager().registerThreadPoolProfile(foo);

        ExecutorService pool = context.getExecutorServiceManager().newScheduledThreadPool(this, "Cool", "foo");
        assertNotNull(pool);

        SizedScheduledExecutorService tp = assertIsInstanceOf(SizedScheduledExecutorService.class, pool);
        // a scheduled dont use keep alive
        assertEquals(0, tp.getKeepAliveTime(TimeUnit.SECONDS));
        assertEquals(Integer.MAX_VALUE, tp.getMaximumPoolSize());
        assertEquals(5, tp.getCorePoolSize());
        assertFalse(tp.isShutdown());

        context.stop();

        assertTrue(tp.isShutdown());
    }

    public void testNewThread() throws Exception {
        Thread thread = context.getExecutorServiceManager().newThread("Cool", new Runnable() {
            @Override
            public void run() {
                // noop
            }
        });

        assertNotNull(thread);
        assertTrue(thread.isDaemon());
        assertTrue(thread.getName().contains("Cool"));
    }

    // this is a manual test, by looking at the logs
    public void xxxTestLongShutdownOfThreadPool() throws Exception {
        final CountDownLatch latch = new CountDownLatch(1);
        ExecutorService pool = context.getExecutorServiceManager().newSingleThreadExecutor(this, "Cool");

        pool.execute(new Runnable() {
            @Override
            public void run() {
                log.info("Starting thread");

                // this should take a long time to shutdown
                try {
                    latch.await(42, TimeUnit.SECONDS);
                } catch (InterruptedException e) {
                    // ignore
                }

                log.info("Existing thread");
            }
        });

        // sleep a bit before shutting down
        Thread.sleep(3000);

        context.getExecutorServiceManager().shutdown(pool);

        assertTrue(pool.isShutdown());
        assertTrue(pool.isTerminated());
    }

}
