/*
 * Copyright 2008-2010 LinkedIn, Inc
 * 
 * Licensed 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 voldemort.server.protocol.admin;

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;

import junit.framework.TestCase;
import voldemort.common.service.SchedulerService;
import voldemort.utils.SystemTime;

/**
 * Test {@link voldemort.server.protocol.admin.AsyncOperationService}
 */
public class AsyncOperationTest extends TestCase {

    public void testAsyncOperationService() throws Exception {
        SchedulerService schedulerService = new SchedulerService(2, SystemTime.INSTANCE);
        AsyncOperationService asyncOperationService = new AsyncOperationService(schedulerService,
                                                                                10);

        final AtomicBoolean op0Complete = new AtomicBoolean(false);
        final AtomicBoolean op1Complete = new AtomicBoolean(false);
        final CountDownLatch op0Latch = new CountDownLatch(1);
        final CountDownLatch op1Latch = new CountDownLatch(1);

        int op0 = asyncOperationService.getUniqueRequestId();
        asyncOperationService.submitOperation(op0, new AsyncOperation(op0, "op0") {

            @Override
            public void operate() throws Exception {
                Thread.sleep(500);
                op0Latch.countDown();
                op0Complete.set(true);
            }

            @Override
            public void stop() {

            }
        });

        int op1 = asyncOperationService.getUniqueRequestId();
        asyncOperationService.submitOperation(op1, new AsyncOperation(op1, "op1") {

            @Override
            public void operate() throws Exception {
                op1Complete.set(true);
                op1Latch.countDown();
            }

            @Override
            public void stop() {

            }
        });
        op1Latch.await();
        List<Integer> opList = asyncOperationService.getAsyncOperationList(false);
        assertFalse("doesn't list completed operations", opList.contains(1));
        assertTrue("lists a pending operation", opList.contains(0));
        opList = asyncOperationService.getAsyncOperationList(true);
        assertTrue("lists all operations", opList.containsAll(Arrays.asList(0, 1)));

        op0Latch.await();
        assertTrue("operation 0 finished", op0Complete.get());
        assertTrue("operation 1 finished", op1Complete.get());
    }

    public void testAsyncOperationCache() throws Exception {
        Map<Integer, AsyncOperation> operations = Collections.synchronizedMap(new AsyncOperationCache(2));
        final CountDownLatch countDownLatch = new CountDownLatch(1);
        AsyncOperation completeLater = new AsyncOperation(0, "test") {

            @Override
            public void stop() {}

            @Override
            public void operate() {
                try {
                    Thread.sleep(500);
                    countDownLatch.countDown();
                } catch(InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        };

        ExecutorService executorService = Executors.newFixedThreadPool(5);

        AsyncOperation completeNow = new AsyncOperation(1, "test") {

            @Override
            public void stop() {}

            @Override
            public void operate() {}
        };

        AsyncOperation completeSoon = new AsyncOperation(2, "test") {

            @Override
            public void stop() {}

            @Override
            public void operate() {
                try {
                    Thread.sleep(250);
                } catch(InterruptedException e) {
                    Thread.currentThread().interrupt();
                }
            }
        };

        operations.put(0, completeLater);
        operations.put(1, completeNow);
        operations.put(2, completeSoon);

        executorService.submit(completeLater);
        executorService.submit(completeNow);
        executorService.submit(completeSoon);

        assertTrue("Handles overflow okay", operations.containsKey(0) && operations.containsKey(1)
                                            && operations.containsKey(2));

        countDownLatch.await();

        for(int i = 3; i < 32; i++) {
            AsyncOperation asyncOperation = new AsyncOperation(i, "test") {

                @Override
                public void stop() {}

                @Override
                public void operate() {}
            };
            operations.put(i, asyncOperation);
        }

        // Commented out as it's brittle and leads to frequent test failures
        // assertFalse("Actually does LRU heuristics",
        // operations.containsKey(0));
    }
}
