package com.subgraph.orchid.directory;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;

import com.subgraph.orchid.DirectoryServer;
import com.subgraph.orchid.KeyCertificate;
import com.subgraph.orchid.RouterStatus;
import com.subgraph.orchid.data.HexDigest;

public class DirectoryServerImpl extends RouterImpl implements DirectoryServer {
	
	private List<KeyCertificate> certificates = new CopyOnWriteArrayList<KeyCertificate>();

	private boolean isHiddenServiceAuthority = false;
	private boolean isBridgeAuthority = false;
	private boolean isExtraInfoCache = false;
	private int port;
	private HexDigest v3Ident;
	
	DirectoryServerImpl(RouterStatus status) {
		super(status);
	}
	
	void setHiddenServiceAuthority() { isHiddenServiceAuthority = true; }
	void unsetHiddenServiceAuthority() { isHiddenServiceAuthority = false; }
	void setBridgeAuthority() { isBridgeAuthority = true; }
	void setExtraInfoCache() { isExtraInfoCache = true; }
	void setPort(int port) { this.port = port; }
	void setV3Ident(HexDigest fingerprint) { this.v3Ident = fingerprint; }
	
	public boolean isTrustedAuthority() {
		return true;
	}
	
	/**
	 * Return true if this DirectoryServer entry has
	 * complete and valid information.
	 * @return
	 */
	public boolean isValid() {
		return true;
	}
	
	public boolean isV2Authority() {
		return hasFlag("Authority") && hasFlag("V2Dir");
	}
	
	public boolean isV3Authority() {
		return hasFlag("Authority") && v3Ident != null;
	}
	
	public boolean isHiddenServiceAuthority() {
		return isHiddenServiceAuthority;
	}
	
	public boolean isBridgeAuthority() {
		return isBridgeAuthority;
	}
	
	public boolean isExtraInfoCache() {
		return isExtraInfoCache;
	}
	
	public HexDigest getV3Identity() {
		return v3Ident;
	}

	public KeyCertificate getCertificateByFingerprint(HexDigest fingerprint) {
		for(KeyCertificate kc: getCertificates()) {
			if(kc.getAuthoritySigningKey().getFingerprint().equals(fingerprint)) {
				return kc;
			}
		}
		return null;
	}
	
	public List<KeyCertificate> getCertificates() {
		purgeExpiredCertificates();
		purgeOldCertificates();
		return new ArrayList<KeyCertificate>(certificates);
	}

	private void purgeExpiredCertificates() {
		Iterator<KeyCertificate> it = certificates.iterator();
		while(it.hasNext()) {
			KeyCertificate elem = it.next();
			if(elem.isExpired()) {
				it.remove();
			}
		}
	}
	
	private void purgeOldCertificates() {
		if(certificates.size() < 2) {
			return;
		}
		final KeyCertificate newest = getNewestCertificate();
		final Iterator<KeyCertificate> it = certificates.iterator();
		while(it.hasNext()) {
			KeyCertificate elem = it.next();
			if(elem != newest && isMoreThan48HoursOlder(newest, elem)) {
				it.remove();
			}
		}
	}
	
	private KeyCertificate getNewestCertificate() {
		KeyCertificate newest = null;
		for(KeyCertificate kc : certificates) {
			if(newest == null || getPublishedMilliseconds(newest) > getPublishedMilliseconds(kc)) {
				newest = kc;
			}
		}
		return newest;
	}
	
	private boolean isMoreThan48HoursOlder(KeyCertificate newer, KeyCertificate older) {
		final long milliseconds = 48 * 60 * 60 * 1000;
		return (getPublishedMilliseconds(newer) - getPublishedMilliseconds(older)) > milliseconds;
	}
	
	private long getPublishedMilliseconds(KeyCertificate certificate) {
		return certificate.getKeyPublishedTime().getDate().getTime();
	}
	
	public void addCertificate(KeyCertificate certificate) {
		if(!certificate.getAuthorityFingerprint().equals(v3Ident)) {
			throw new IllegalArgumentException("This certificate does not appear to belong to this directory authority");
		}
		certificates.add(certificate);
	}
	
	public String toString() {
		if(v3Ident != null) 
			return "(Directory: "+ getNickname() +" "+ getAddress() +":"+ port +" fingerprint="+ getIdentityHash() +" v3ident="+ 
				v3Ident +")";
		else
			return "(Directory: "+ getNickname() +" "+ getAddress() +":"+ port +" fingerprint="+ getIdentityHash() +")";

	}
}

@@ -114,58 +114,8 @@
 ist;
-%0Aimport java.util.concurrent.CopyOnWriteArrayList;
 %0A%0Aim
@@ -420,19 +420,8 @@
 new 
-CopyOnWrite
 Arra
@@ -2042,16 +2042,48 @@
 tes() %7B%0A
+%09%09synchronized(certificates) %7B%0A%09
 %09%09purgeE
@@ -2106,16 +2106,17 @@
 es();%0A%09%09
+%09
 purgeOld
@@ -2131,16 +2131,17 @@
 ates();%0A
+%09
 %09%09return
@@ -2186,16 +2186,20 @@
 cates);%0A
+%09%09%7D%0A
 %09%7D%0A%0A%09pri
@@ -3642,16 +3642,48 @@
 %22);%0A%09%09%7D%0A
+%09%09synchronized(certificates) %7B%0A%09
 %09%09certif
@@ -3707,16 +3707,20 @@
 icate);%0A
+%09%09%7D%0A
 %09%7D%0A%09%0A%09pu

Use locking because remove() method not supported on COW iterator