package com.atlassian.stash.internal.concurrent;

import com.google.common.collect.ImmutableMap;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import org.springframework.context.ApplicationContext;
import org.springframework.context.event.ContextRefreshedEvent;

import javax.annotation.Nonnull;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.TimeUnit;

import static org.junit.Assert.assertArrayEquals;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

@RunWith(MockitoJUnitRunner.class)
public class StateTransferringExecutorTest {

    @Mock
    private StatefulService statefulService;

    private final List<String> s = Collections.synchronizedList(new LinkedList<>());
    private ExecutorService executor;
    private StateTransferringExecutor stateTransferringExecutor;

    @Before
    public void setUp() throws Exception {
        executor = newExecutor();
        when(statefulService.getState()).thenReturn(STATE);
        stateTransferringExecutor = new StateTransferringExecutor(executor);
        ContextRefreshedEvent event = mock(ContextRefreshedEvent.class);
        ApplicationContext applicationContext = mock(ApplicationContext.class);
        when(applicationContext.getBeansOfType(StatefulService.class))
                .thenReturn(ImmutableMap.of("service", statefulService));
        when(event.getApplicationContext()).thenReturn(applicationContext);

        stateTransferringExecutor.onApplicationEvent(event);
    }

    @After
    public void tearDown() throws Exception {
        if (!executor.isShutdown()) {
            executor.shutdownNow();
        }
        executor = null;
    }

    @Test
    public void testExecute() throws Exception {
        stateTransferringExecutor.execute(NOOP);
        executor.shutdown();
        executor.awaitTermination(30L, TimeUnit.SECONDS);

        assertArrayEquals(new String[]{"apply", "run", "remove"}, s.toArray());
    }

    private final Runnable NOOP = () -> s.add("run");

    private final TransferableState STATE = new TransferableState() {
        @Override
        public void apply() {
            s.add("apply");
        }

        @Override
        public void remove() {
            s.add("remove");
        }
    };

    private static ExecutorService newExecutor() {
        return Executors.newFixedThreadPool(1, new ThreadFactory() {
            @Override
            public Thread newThread(@Nonnull  Runnable r) {
                return new Thread(r, this.getClass().getSimpleName());
            }
        });
    }

}
