package railo.commons.io.res.type.ftp;



import java.io.IOException;
import java.net.SocketException;
import java.util.HashMap;
import java.util.Map;

import railo.commons.io.res.Resource;
import railo.commons.io.res.ResourceProvider;
import railo.commons.io.res.Resources;
import railo.commons.io.res.util.ResourceLockImpl;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.SizeOf;
import railo.commons.lang.StringUtil;
import railo.runtime.net.proxy.Proxy;
import railo.runtime.op.Caster;
import railo.runtime.type.Sizeable;

// TODO check connection timeout
public final class FTPResourceProvider implements ResourceProvider,Sizeable {
	private String scheme="ftp";
	private final Map clients=new HashMap();
	private int clientTimeout=60000;
	private int socketTimeout=-1;
	private int lockTimeout=20000;
	private int cache=20000;

	private FTPResourceClientCloser closer=null;
	private final ResourceLockImpl lock=new ResourceLockImpl(lockTimeout,true);
	private Map arguments;

	public ResourceProvider init(String scheme, Map arguments) {
		setScheme(scheme);
		
		if(arguments!=null) {
			this.arguments=arguments;
			// client-timeout
			String strTimeout=(String) arguments.get("client-timeout");
			if(strTimeout!=null) {
				clientTimeout=Caster.toIntValue(strTimeout,clientTimeout);
			}
			// socket-timeout
			strTimeout=(String) arguments.get("socket-timeout");
			if(strTimeout!=null) {
				socketTimeout=Caster.toIntValue(strTimeout,socketTimeout);
			}
			// lock-timeout
			strTimeout=(String) arguments.get("lock-timeout");
			if(strTimeout!=null) {
				lockTimeout=Caster.toIntValue(strTimeout,lockTimeout);
			}
			// cache
			String strCache=(String) arguments.get("cache");
			if(strCache!=null) {
				cache=Caster.toIntValue(strCache,cache);
			}
		}
		lock.setLockTimeout(lockTimeout);
		
		return this;
	}
	
	@Override
	public Resource getResource(String path) {
		path=ResourceUtil.removeScheme(scheme,path);
		FTPConnectionData data=new FTPConnectionData();
		path=data.load(path);
		
		return 	new FTPResource(this,data,path);
	}
		
		
	

	FTPResourceClient getClient(FTPConnectionData data) throws IOException {
		
		FTPResourceClient client=(FTPResourceClient) clients.remove(data.key());
		if(client==null) {
			client = new FTPResourceClient(data,cache);
			if(socketTimeout>0)client.setSoTimeout(socketTimeout);
		}
		
		if(!client.isConnected()) { 
			if(data.hasProxyData()) {
				try {
		        	Proxy.start(
		            		data.getProxyserver(), 
		            		data.getProxyport(), 
		            		data.getProxyuser(), 
		            		data.getProxypassword()
		            );
		        	connect(client,data);
		        }
		        finally {
		        	Proxy.end();
		        }
			}
			else {
				connect(client,data);
			}
			
			int replyCode = client.getReplyCode();
			if(replyCode>=400)
				throw new FTPException(replyCode);
		}
		startCloser();
		return client;
	}
		
	private synchronized void startCloser() {
		if(closer==null || !closer.isAlive()) {
			closer=new FTPResourceClientCloser(this);
			closer.start();
		}
		
	}
	private void connect(FTPResourceClient client, FTPConnectionData data) throws SocketException, IOException {
		if(data.port>0)client.connect(data.host,data.port);
		else client.connect(data.host);
		if(!StringUtil.isEmpty(data.username))client.login(data.username,data.password);
	}

	public void returnClient(FTPResourceClient client) {
		if(client==null)return;
		client.touch();
		clients.put(client.getFtpConnectionData().key(), client);
	}

	@Override
	public String getScheme() {
		return scheme;
	}

	public void setScheme(String scheme) {
		if(!StringUtil.isEmpty(scheme))this.scheme=scheme;
	}

	@Override
	public void setResources(Resources resources) {
		//this.resources=resources;
	}
	
	@Override
	public void lock(Resource res) throws IOException {
		lock.lock(res);
	}

	@Override
	public void unlock(Resource res) {
		lock.unlock(res);
	}

	@Override
	public void read(Resource res) throws IOException {
		lock.read(res);
	}
	
	public void clean() {
		Object[] keys = clients.keySet().toArray();
		FTPResourceClient client;
		for(int i=0;i<keys.length;i++) {
			client=(FTPResourceClient) clients.get(keys[i]);
			if(client.getLastAccess()+clientTimeout<System.currentTimeMillis()) {
				//railo.print.ln("disconnect:"+client.getFtpConnectionData().key());
				if(client.isConnected()) {
					try {
						client.disconnect();
					} 
					catch (IOException e) {}
				}
				clients.remove(client.getFtpConnectionData().key());
			}
		}
	}
	
	class FTPResourceClientCloser extends Thread {

		private FTPResourceProvider provider;

		public FTPResourceClientCloser(FTPResourceProvider provider) {
			this.provider=provider;
		}
		
		public void run() {
			//railo.print.ln("closer start");
			do {
				sleepEL();
				provider.clean();
			}
			while(!clients.isEmpty());
			//railo.print.ln("closer stop");
		}

		private void sleepEL() {
			try {
				sleep(provider.clientTimeout);
			} catch (InterruptedException e) {}		
		}
	}
	
	/**
	 * @return the cache
	 */
	public int getCache() {
		return cache;
	}

	@Override
	public boolean isAttributesSupported() {
		return false;
	}

	public boolean isCaseSensitive() {
		return true;
	}

	@Override
	public boolean isModeSupported() {
		return true;
	}

	@Override
	public long sizeOf() {
		return SizeOf.size(lock)+SizeOf.size(clients);
	}

	@Override
	public Map getArguments() {
		return arguments;
	}

}

