/*
 * 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.brooklyn.util.time;

import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;

import java.util.Arrays;
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.time.CountdownTimer;
import org.apache.brooklyn.util.time.Duration;
import org.apache.brooklyn.util.time.Time;
import org.testng.annotations.Test;

import com.google.common.base.Stopwatch;

@Test
public class CountdownTimerTest {

    // Test failed on jenkins when using 1 second, sleeping for 500ms; 
    // hence relaxing time constraints so is less time-sensitive at the expense of being a slower test.
    @Test(groups="Integration")
    public void testSimpleExpiry() {
        final int TOTAL_TIME_MS = 5*1000;
        final int OVERHEAD_MS = 2000;
        final int EARLY_RETURN_GRACE_MS = 30;
        final int FIRST_SLEEP_TIME_MS = 2500;
        final int SECOND_SLEEP_TIME_MS = TOTAL_TIME_MS - FIRST_SLEEP_TIME_MS + EARLY_RETURN_GRACE_MS*2;
        
        final Duration SIMPLE_DURATION = Duration.millis(TOTAL_TIME_MS);
        
        CountdownTimer timer = SIMPLE_DURATION.countdownTimer();
        assertFalse(timer.isExpired());
        assertTrue(timer.isNotExpired());
        assertTrue(timer.isLive());
        assertTrue(timer.isNotPaused());
        assertTrue(timer.getDurationElapsed().toMilliseconds() <= OVERHEAD_MS, "elapsed="+timer.getDurationElapsed().toMilliseconds());
        assertTrue(timer.getDurationRemaining().toMilliseconds() >= TOTAL_TIME_MS - OVERHEAD_MS, "remaining="+timer.getDurationElapsed().toMilliseconds());
        
        Time.sleep(Duration.millis(FIRST_SLEEP_TIME_MS));
        assertFalse(timer.isExpired());
        assertOrdered(FIRST_SLEEP_TIME_MS - EARLY_RETURN_GRACE_MS, timer.getDurationElapsed().toMilliseconds(), FIRST_SLEEP_TIME_MS + OVERHEAD_MS);
        assertOrdered(TOTAL_TIME_MS - FIRST_SLEEP_TIME_MS - OVERHEAD_MS, timer.getDurationRemaining().toMilliseconds(), TOTAL_TIME_MS - FIRST_SLEEP_TIME_MS + EARLY_RETURN_GRACE_MS);
        
        Time.sleep(Duration.millis(SECOND_SLEEP_TIME_MS));
        assertTrue(timer.isExpired());
        assertFalse(timer.isNotExpired());
        assertFalse(timer.isLive());
        assertTrue(timer.isNotPaused());

    }
    
    public void testNotify() throws InterruptedException {
        CountdownTimer timer = Duration.FIVE_SECONDS.countdownTimer();
        final Object mutex = new Object();
        final Semaphore gun = new Semaphore(0);
        Stopwatch watch = Stopwatch.createStarted();
        new Thread(new Runnable() {
            @Override
            public void run() {
                try { gun.acquire(); } catch (Exception e) { throw Exceptions.propagate(e); }
                synchronized (mutex) {
                    mutex.notifyAll();
                }
            }
        }).start();
        synchronized (mutex) {
            gun.release();
            assertTrue(timer.waitOnForExpiry(mutex));
        }
        assertTrue(watch.elapsed(TimeUnit.MILLISECONDS) < 3000, "took too long: "+watch);
    }
    
    private void assertOrdered(long... vals) {
        String errmsg = "vals="+Arrays.toString(vals);
        long prevVal = Long.MIN_VALUE;
        for (long val : vals) {
            assertTrue(val >= prevVal, errmsg);
            prevVal = val;
        }
    }
    
}
