package brooklyn.test;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;

import org.slf4j.Logger;
import org.testng.Assert;

/**
 * Repeatedly polls a given URL, to check if it is always available.
 * 
 * @author Alex, Aled
 */
public class WebAppMonitor implements Runnable {
    final AtomicBoolean shouldBeActive = new AtomicBoolean(true);
    final AtomicBoolean isActive = new AtomicBoolean(false);
    final AtomicInteger successes = new AtomicInteger(0);
    final AtomicInteger failures = new AtomicInteger(0);
    final AtomicLong lastTime = new AtomicLong(-1);
    final AtomicReference<Object> lastStatus = new AtomicReference<Object>(null);
    final AtomicReference<Object> lastFailure = new AtomicReference<Object>(null);
    Logger log;
    Object problem = null; 
    String url;
    long delayMillis = 500;
    int expectedResponseCode = 200;
    
    public WebAppMonitor(String url) {
        this.url = url;
    }
    public WebAppMonitor() {
    }
    public WebAppMonitor logFailures(Logger log) {
        this.log = log;
        return this;
    }
    public WebAppMonitor delayMillis(long val) {
        this.delayMillis = val;
        return this;
    }
    public WebAppMonitor expectedResponseCode(int val) {
        this.expectedResponseCode = val;
        return this;
    }
    public WebAppMonitor url(String val) {
        this.url = val;
        return this;
    }
    
    public void run() {
        synchronized (isActive) {
            if (isActive.getAndSet(true))
                throw new IllegalStateException("already running");
        }
        while (shouldBeActive.get()) {
            long startTime = System.currentTimeMillis();
            try {
                if (preAttempt()) {
                    int code = HttpTestUtils.getHttpStatusCode(url);
                    lastTime.set(System.currentTimeMillis()-startTime);
                    lastStatus.set(code);
                    if (isResponseOkay(code)) {
                        successes.incrementAndGet();
                    } else {
                        lastFailure.set(code);
                        failures.incrementAndGet();
                        onFailure("return code "+code);
                    }
                }
            } catch (Exception e) {
                lastTime.set(System.currentTimeMillis()-startTime);
                lastStatus.set(e);
                lastFailure.set(e);
                failures.incrementAndGet();
                onFailure(e);
            }
            try {
                if (delayMillis > 0) {
                    Thread.sleep(delayMillis);
                }
            } catch (InterruptedException e) {
                onFailure(e);
                shouldBeActive.set(false);
            }
        }
        synchronized (isActive) {
            if (!isActive.getAndSet(false))
                throw new IllegalStateException("shouldn't be possible!");
            isActive.notifyAll();
        }
    }
    
    public boolean isResponseOkay(Object code) {
        return code!=null && new Integer(expectedResponseCode).equals(code);
    }
    
    public void setDelayMillis(long delayMillis) {
        this.delayMillis = delayMillis;
    }
    public long getDelayMillis() {
        return delayMillis;
    }
    public void terminate() throws InterruptedException {
        shouldBeActive.set(false);
        synchronized (isActive) {
            while (isActive.get()) isActive.wait();
        }
    }
    public int getFailures() {
        return failures.get();
    }
    public int getSuccesses() {
        return successes.get();
    }
    public void setUrl(String url) {
        this.url = url;
    }
    public String getUrl() {
        return url;
    }
    public Object getProblem() {
        return problem;
    }
    public int getAttempts() {
        return getFailures()+getSuccesses();
    }
    public boolean getLastWasFailed() {
        return isResponseOkay(getLastStatus());
    }
    public Object getLastStatus() {
        return lastStatus.get();
    }
    public long getLastTime() {
        return lastTime.get();
    }
    /** result code (int) or exception */
    public Object getLastFailure() {
        return lastFailure.get();
    }
    
    public void onFailure(Object problem) {
        if (log != null) {
            log.warn("Detected failure in monitor accessing "+getUrl()+": "+problem);
        }
        this.problem = problem;
    }
    
    /** return false to skip a run */
    public boolean preAttempt() {
        return true;
    }
    
    public WebAppMonitor assertNoFailures(String message) {
        return assertSuccessFraction(message, 1.0);
    }
    public WebAppMonitor assertAttemptsMade(int minAttempts, String message) {
        if (getAttempts()<minAttempts) {
            Assert.fail(message+" -- webapp access failures! " +
                    "(0 attempts made; probably blocked on server)");            
        }
        return this;
    }
    public WebAppMonitor assertSuccessFraction(String message, double percentage) {
        if ((getFailures() > (1-percentage) * getAttempts()+0.0001) || getAttempts()<=0) {
            Assert.fail(message+" -- webapp access failures! " +
            		"("+getFailures()+" failed of "+getAttempts()+" monitoring attempts) against "+getUrl()+"; " +
            		"last was "+getLastStatus()+" taking "+getLastTime()+"ms" +
            		(getLastFailure()!=null ? "; last failure was "+getLastFailure() : ""));
        }
        return this;
    }
    public WebAppMonitor resetCounts() {
        failures.set(0);
        successes.set(0);
        return this;
    }
    
}
@@ -17,16 +17,54 @@
 .test;%0A%0A
+import java.util.concurrent.TimeUnit;%0A
 import j
@@ -307,16 +307,98 @@
 ssert;%0A%0A
+import brooklyn.util.collections.MutableMap;%0Aimport brooklyn.util.time.Duration;%0A%0A
 /**%0A * R
@@ -5270,93 +5270,598 @@
 tor 
-assertSuccessFraction(String message, double percentage) %7B%0A        if ((getFailures()
+waitForAtLeastOneAttempt() %7B%0A        return waitForAtLeastOneAttempt(Duration.of(30, TimeUnit.SECONDS));%0A    %7D%0A    public WebAppMonitor waitForAtLeastOneAttempt(Duration timeout) %7B%0A        Asserts.succeedsEventually(MutableMap.of(%22timeout%22, timeout), new Runnable() %7B%0A            @Override public void run() %7B%0A                Assert.assertTrue(getAttempts() %3E= 1);%0A            %7D%7D);%0A        return this;%0A    %7D%0A    public WebAppMonitor assertSuccessFraction(String message, double percentage) %7B%0A        int failures = getFailures();%0A        int attempts = getAttempts();%0A        if ((failures
  %3E (
@@ -5876,29 +5876,24 @@
 tage) * 
-getA
+a
 ttempts
-()
 +0.0001)
@@ -5896,29 +5896,24 @@
 001) %7C%7C 
-getA
+a
 ttempts
-()
 %3C=0) %7B%0A 
@@ -5994,29 +5994,24 @@
   %09%09%22(%22+
-getF
+f
 ailures
-()
 +%22 faile
@@ -6017,29 +6017,24 @@
 ed of %22+
-getA
+a
 ttempts
-()
 +%22 monit

add waitForAtLeastOneAttempt