package org.torproject.jtor.circuits.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

import org.torproject.jtor.circuits.Circuit;
import org.torproject.jtor.circuits.CircuitManager;
import org.torproject.jtor.circuits.OpenStreamResponse;
import org.torproject.jtor.crypto.TorRandom;
import org.torproject.jtor.data.IPv4Address;
import org.torproject.jtor.directory.Directory;
import org.torproject.jtor.logging.LogManager;
import org.torproject.jtor.logging.Logger;

public class CircuitManagerImpl implements CircuitManager {
	private final static boolean DEBUG_CIRCUIT_CREATION = true;

	private final ConnectionManagerImpl connectionManager;
	private final Logger logger;
	private final Set<Circuit> pendingCircuits;
	private final Set<Circuit> activeCircuits;
	private final Set<Circuit> cleanCircuits;
	private final TorRandom random;
	private final List<StreamExitRequest> pendingExitStreams = new LinkedList<StreamExitRequest>();
	private final ScheduledExecutorService scheduledExecutor = Executors.newSingleThreadScheduledExecutor();
	private final Runnable circuitCreationTask;

	public CircuitManagerImpl(Directory directory, ConnectionManagerImpl connectionManager, LogManager logManager) {
		this.connectionManager = connectionManager;
		this.logger = logManager.getLogger("circuits");
		this.logger.enableDebug();
		this.circuitCreationTask = new CircuitCreationTask(directory, this, logger);
		this.activeCircuits = new HashSet<Circuit>();
		this.pendingCircuits = new HashSet<Circuit>();
		this.cleanCircuits = new HashSet<Circuit>();
		this.random = new TorRandom();
	}

	public void startBuildingCircuits() {
		scheduledExecutor.scheduleAtFixedRate(circuitCreationTask, 0, 1000, TimeUnit.MILLISECONDS);

		if(DEBUG_CIRCUIT_CREATION) {
			Runnable debugTask = createCircuitCreationDebugTask();
			scheduledExecutor.scheduleAtFixedRate(debugTask, 0, 30000, TimeUnit.MILLISECONDS);
		}
	}

	private Runnable createCircuitCreationDebugTask() {
		return new Runnable() { public void run() {
			logger.debug("CLEAN: "+ getCleanCircuitCount() 
					+ " PENDING: "+ getPendingCircuitCount()
					+ " ACTIVE: "+ getActiveCircuitCount());
		}};
	}

	public Circuit createNewCircuit() {
		return CircuitImpl.create(this, connectionManager, logger);
	}

	synchronized void circuitStartConnect(Circuit circuit) {
		pendingCircuits.add(circuit);
	}

	synchronized void circuitConnected(Circuit circuit) {
		pendingCircuits.remove(circuit);
		activeCircuits.add(circuit);
		cleanCircuits.add(circuit);
	}

	synchronized void circuitDirty(Circuit circuit) {
		cleanCircuits.remove(circuit);
	}

	synchronized void circuitInactive(Circuit circuit) {
		pendingCircuits.remove(circuit);
		activeCircuits.remove(circuit);
		cleanCircuits.remove(circuit);
	}

	synchronized int getCleanCircuitCount() {
		return cleanCircuits.size();
	}

	synchronized int getActiveCircuitCount() {
		return activeCircuits.size();
	}

	synchronized int getPendingCircuitCount() {
		return pendingCircuits.size();
	}

	List<Circuit> getRandomlyOrderedListOfActiveCircuits() {
		final ArrayList<Circuit> ac = new ArrayList<Circuit>(activeCircuits);
		final int sz = ac.size();
		for(int i = 0; i < sz; i++) {
			final Circuit tmp = ac.get(i);
			final int swapIdx = random.nextInt(sz);
			ac.set(i, ac.get(swapIdx));
			ac.set(swapIdx, tmp);
		}
		return ac;
	}

	public OpenStreamResponse openExitStreamTo(String hostname, int port)
			throws InterruptedException {
		return openExitStreamByRequest(new StreamExitRequest(this, hostname, port));
	}

	public OpenStreamResponse openExitStreamTo(IPv4Address address, int port)
			throws InterruptedException {
		return openExitStreamByRequest(new StreamExitRequest(this, address, port));
	}
	
	private OpenStreamResponse openExitStreamByRequest(StreamExitRequest request) throws InterruptedException {
		synchronized(pendingExitStreams) {
			pendingExitStreams.add(request);
			while(!request.isCompleted())
				pendingExitStreams.wait();
		}
		return request.getResponse();
	}
	
	List<StreamExitRequest> getPendingExitStreams() {
		synchronized(pendingExitStreams) {
			return new ArrayList<StreamExitRequest>(pendingExitStreams);
		}
	}
	
	List<Circuit> getPendingCircuits() {
		synchronized(pendingCircuits) {
			return new ArrayList<Circuit>(pendingCircuits);
		}
	}
	
	void streamRequestIsCompleted(StreamExitRequest request) {
		synchronized(pendingExitStreams) {
			pendingExitStreams.remove(request);
			pendingExitStreams.notifyAll();
		}
	}
}

@@ -69,38 +69,8 @@
 st;%0A
-import java.util.Collections;%0A
 impo

Remove unneeded import.