package org.spbu.benchmark;

import java.text.DecimalFormat;
import java.text.NumberFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;

import org.spbu.benchmark.app.AppScenario;
import org.spbu.benchmark.web.HttpScenarioResult;

public class DefaultBenchmark extends LoggingSupport implements Benchmark {
	public int TIMEOUT = 210;
	private ThreadingSettings threadingSettings;
	private List<Scenario> scenarios;
	private int threadsLaunched = 0;
	private int threadsRunning = 0;
	
	class ScenarioContainer implements Runnable{
		private Scenario scenario;
		private int index;
		private int type;
		private boolean completed = false;
		private ScenarioResult scenarioResult;
		public ScenarioContainer(Scenario scenario, int index, int type){
			this.scenario = scenario;
			this.index = index;
			this.type = type;
		}
		public void run(){
			try{
				scenarioResult = scenario.start();
			}catch(BenchmarkException be){
				log.error("Error in scenario number "+index, be);
			}finally{
				completed = true;
				log.debug("Thread number "+index+" has finished");
				stopped();
			}
		}
		public ScenarioResult getResult(){
			return scenarioResult;
		}
		public int getIndex() {
			return index;
		}
		public int getType() {
			return type;
		}
		public Scenario getScenario(){
			return scenario;
		}
	}
	private synchronized void launched(){
		threadsRunning++;
		threadsLaunched++;
	}
	private synchronized void stopped(){
		threadsRunning--;
	}
	public DefaultBenchmark(ThreadingSettings settings){
		this.scenarios = new ArrayList<Scenario>();
		this.threadingSettings = settings;
	}
	public DefaultBenchmark(List<Scenario> scenarios, ThreadingSettings settings){
		this.scenarios = scenarios;
		this.threadingSettings = settings;
	}
	
	@Override
	public BenchmarkResult start() throws BenchmarkException {
		List<ScenarioContainer> threads = new ArrayList<ScenarioContainer>();
		while(threadsLaunched < threadingSettings.getTotalThreads()){
			try{
				Thread.sleep(threadingSettings.getLaunchInterval());
			}catch(InterruptedException ie){
				continue;
			}			
			if(threadsRunning >= threadingSettings.getMaxConcurrentThreads()) continue;
			int type = (new Random()).nextInt(scenarios.size());
			ScenarioContainer scenario = new ScenarioContainer(scenarios.get(type), threadsLaunched + 1, type+1);
			Thread thread = new Thread(scenario);
			threads.add(scenario);
			thread.start();
			launched();
			log.debug("Launched thread number "+threadsLaunched+" , "+threadsRunning+" threads running now");
		}
		//all threads have started now
		//wait till all have completed work
		int i = 0;
		
		if(threadsLaunched > 0){
			while(threadsRunning > 0){
				try{
					if(i>=TIMEOUT) {
						for(ScenarioContainer scenario : threads){
							log.debug("Timeout expired, killing thread "+scenario.getIndex());
							scenario.getScenario().kill();
						}
						break;
					}
					Thread.sleep(1000);
					i++;
				}catch(InterruptedException ie){
					continue;
				}						
			}
		}
		NumberFormat f = new DecimalFormat("00.00000");
		int success = 0;
		int failed = 0;
		List<ScenarioResult> scenarioResults = new ArrayList<ScenarioResult>();		
		for(ScenarioContainer scenarioContainer : threads){
			if((scenarioContainer.getResult()!=null)&&(scenarioContainer.getResult().isSuccessful())) {
				log.debug("Scenario number " + scenarioContainer.getIndex() + 
						", type " + scenarioContainer.getType() 
						+ " has completed successfully in "
						+f.format((float)scenarioContainer.getResult().getTime()/1000)
						+" seconds");
				success++;
				scenarioResults.add(scenarioContainer.getResult());
			}
			else{
				log.debug("Scenario number " + scenarioContainer.getIndex() + 
						" of type " + scenarioContainer.getType() 
						+ " has failed to complete");
				failed++;
			}
				
		}
		BenchmarkResult result = new DefaultBenchmarkResult(scenarioResults,success,failed);
		return result;
	}


	public ThreadingSettings getThreadingSettings() {
		return threadingSettings;
	}

	public void setThreadingSettings(ThreadingSettings threadingSettings) {
		this.threadingSettings = threadingSettings;
	}

	public List<? extends Scenario> getScenarios() {
		return scenarios;
	}

	public void setScenarios(List<? extends Scenario> scenarios) {
		this.scenarios = new ArrayList<Scenario>();
		this.scenarios.addAll(scenarios);
	}
	public void addScenario(Scenario scenario){
		this.scenarios.add(scenario);
	}
}
