/* $Id: WebDavProcessor.java 349 2011-09-11 15:02:16Z ohura $ */
package webdav;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
import java.util.Vector;

import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.DefaultHttpMethodRetryHandler;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpHost;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.InputStreamRequestEntity;
import org.apache.commons.httpclient.methods.OptionsMethod;
import org.apache.commons.httpclient.methods.PutMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.params.HttpMethodParams;
import org.apache.commons.httpclient.protocol.Protocol;
import org.apache.commons.httpclient.util.URIUtil;
import org.apache.webdav.lib.Lock;
import org.apache.webdav.lib.Property;
import org.apache.webdav.lib.ResponseEntity;
import org.apache.webdav.lib.methods.LockMethod;
import org.apache.webdav.lib.methods.PropFindMethod;
import org.apache.webdav.lib.methods.UnlockMethod;
import org.apache.webdav.lib.properties.LockDiscoveryProperty;

import smart_gs.logical.Preference;
import webdav.ssl.EasySSLProtocolSocketFactory;

public class WebDavProcessor {
	protected String host;
	protected int port;
	protected Protocol protocol;
	protected String urlStr;
	protected URL url;
	protected boolean login = false;
	protected boolean ssl = false;
	
	protected static WebDavProcessor singleton;
	protected HttpClient client=null;
	
	public static WebDavProcessor getInstance(boolean reset) {
		if(reset == true){
			singleton = null;
		}
		return getInstance();
	}
	public static boolean isLogin() {
		if(singleton == null){
			return false;
		}else{
			return true;
		}
	}
	public static WebDavProcessor getInstance() {
		if(singleton == null){
			boolean ssl_ = false;
			Preference preference = Preference.getInstance();
			String urlStr = preference.getWebdavServerPathString();
			URL url_ = null;
			try {
				url_ = new URL(urlStr);
			} catch (MalformedURLException e1) {
				return null;
			}
			String host = url_.getHost();
			int port = url_.getPort();
			if(port == -1){
				if(urlStr.startsWith("https")){
					port = 443;
				}else{
					port = 80;
				}
			}
			if(urlStr.startsWith("https")){
				ssl_ = true;
			}
			Protocol protocol = Protocol.getProtocol(url_.getProtocol());
			String username = preference.getWebdavUserName();
			String password = preference.getWebdavPassword();
			if(!urlStr.endsWith("/")){
				urlStr+="/";
			}
			String proxyaddress = null;
			int proxyport = -1;
			
			if(preference.getProxyAddress() != null && !preference.getProxyAddress().equals("")){
				proxyaddress = preference.getProxyAddress();
			}
			try{
				proxyport = Integer.parseInt(preference.getProxyPort());
			}catch(NumberFormatException e){}
			
			try{
				if(ssl_){
					Protocol.registerProtocol("https", new Protocol("https",new EasySSLProtocolSocketFactory(), port));
				}
				singleton = new WebDavProcessor(urlStr,url_,host,port,protocol,ssl_);
				if(!singleton.login(username,password,proxyaddress,proxyport)){
					singleton = null;
				}
			}catch(Exception e){
				singleton = null;
			}
		}
		return singleton;
	}
	public WebDavProcessor(String urlStr,URL url,String host,int port,Protocol protocol,boolean ssl){
		this.urlStr  = urlStr;
		this.url  = url;
		this.host = host;
		this.port = port;
		this.protocol = protocol;
		this.ssl = ssl;
	}
	
	public boolean login(String username,String password) throws IOException{
		return login(username,password,null,-1);
	}
	public boolean login(String username,String password,String proxyaddress,int proxyport) throws IOException{
		System.setProperty("org.apache.commons.logging.Log","org.apache.commons.logging.impl.SimpleLog");
		System.setProperty("org.apache.commons.logging.simplelog.defaultlog","error");
		// USERAGENTを設定
		System.setProperty("httpclient.useragent","SMART-GS");

		if(urlStr == null){
			return false;
		}
		if(!urlStr.endsWith("/")){
			urlStr += "/";
		}


		Credentials loginCredentials = null;
		AuthScope loginAuthScope = null;
		HttpClient client_ = new HttpClient();
		{
			if(username == null){
				username = "";
			}
			if(password == null){
				password = "";
			}
			if(!username.equals("")){
				loginCredentials = new UsernamePasswordCredentials(username, password);
				loginAuthScope   = new AuthScope(host,port);
				client_.getState().setCredentials(loginAuthScope, loginCredentials);
				client_.getParams().setAuthenticationPreemptive(true);
			}
		}
		// Proxyの設定
		{
			String proxy_host = proxyaddress;
			int proxy_port = proxyport;
			if(proxyaddress == null){
				proxy_host = "";
			}
			if(!proxy_host.trim().equals("")){
				AuthScope authScope = new AuthScope(proxy_host, proxy_port);
				HttpHost proxy = new HttpHost(proxy_host, proxy_port); 
//				String proxy_user =  PropertiesManager.getProperty(PropertiesConstants.PORXY_USER);
//				String proxy_password = PropertiesManager.getProperty(PropertiesConstants.PORXY_PASSWORD);
//				if(proxy_user == null){
//					proxy_user = "";
//				}
//				if(proxy_password == null){
//					proxy_password = "";
//				}
				UsernamePasswordCredentials usernamePasswordCredentials = new UsernamePasswordCredentials("","");
				client_.getState().setProxyCredentials(authScope, usernamePasswordCredentials);
				client_.getHostConfiguration().setProxy(proxy_host, proxy_port);  
			}
		}		

		
		// Httpメソッドを作成.
		OptionsMethod method = new OptionsMethod(urlStr);
		// Httpメソッドパラメータを設定.
		// 今回はリトライ回数を設定.
		method.getParams().setParameter(HttpMethodParams.RETRY_HANDLER,new DefaultHttpMethodRetryHandler(3, false));
		method.setDoAuthentication(true);
		try {
			int state = client_.executeMethod(method);
	        if ( state == HttpStatus.SC_OK ) {
	        	login = true;
	        	this.client = client_;
	        }else{
		        login =  false;
	    	}
		} catch (HttpException he) {
//		    he.printStackTrace();
	        login =  false;
		} catch (IOException ioe) {
		    ioe.printStackTrace();
	        login =  false;
		} finally {
		    // 接続開放.
		    method.releaseConnection();
		}
		return login;
	}
	public PropFindInfo[] getPropFindInfo(String path,int depth) throws IOException{
		return getPropFindInfo(path,null,depth);
	}
	
	public PropFindInfo[] getPropFindInfo(String path_,String name,int depth) throws IOException{
		String path = getPath(path_);
		if(name != null){
			path += name;
		}
		String requestpath = URIUtil.encodePath(urlStr + path);

		ArrayList list = new ArrayList();
		PropFindMethod  propfindMethod = null;
		try{
		
			Vector props = new Vector();
			// PRPFINDに問い合わせる値を設定
			props.add("lockdiscovery");
			props.add("resourcetype");
			props.add("getlastmodified");
			props.add("getcontentlength");
			props.add("displayname");
			
			propfindMethod = new PropFindMethod(requestpath, depth, props.elements());
			propfindMethod.setDoAuthentication(true);
//			propfindMethod.setType(1);
//			propfindMethod.setDebug(1);
			// 問い合わせ
			int state = client.executeMethod(propfindMethod);
		
			if ( state != HttpStatus.SC_MULTI_STATUS ) {
				return null;
			}
            URL requestURL = new URL(requestpath);
            
            // String href_ = "http://" + requestURL.getHost() + requestURL.getPath();
            
			Enumeration responses = propfindMethod.getResponses();
            // 自分自身の比較はdecodeして行うこと
			String selfpath =  URIUtil.decode(requestURL.getPath());
	        while (responses.hasMoreElements()) {
	            ResponseEntity response = (ResponseEntity) responses.nextElement();
	            String href = response.getHref();
	            String href_src = href;
	            // 2010/04/8
	            // hrefにulrが記載されてい場合の対応
	            if(href.toLowerCase().startsWith("http")){
		            if(href.toLowerCase().indexOf("://") != -1){
		            	String temp = href.substring(href.indexOf("://") + "://".length(),href.length());
		            	href = temp.substring(temp.indexOf("/"),temp.length());
		            	if(href.startsWith("//")){
		            		href = href.substring(1,href.length());
		            	}
		            }
	            }
	            String rengpath = href;
	            if(!rengpath.endsWith("/")){
	            	rengpath+="/";
	            }
				String rengpathDecode = URIUtil.decode(rengpath);
	            if(rengpathDecode.toLowerCase().equals(selfpath.toLowerCase())){
	            	if(depth != 0){
		            	continue;
	            	}
	            }
	            PropFindInfo propFindInfo = new PropFindInfo();
	            String abstpath =  rengpath.substring(url.getPath().length(),rengpath.length());
	            propFindInfo.setPath(URIUtil.decode(abstpath));
	            if(abstpath.equals("")){
	            	propFindInfo.setDisplayname("/");
	            }else{
	            	if(path.equals("")){
	            		String displayname = URIUtil.decode(abstpath);
	            		if(displayname.endsWith("/")){
	            			displayname = displayname.substring(0,displayname.length() - 1);
	            		}
	            		if(displayname.startsWith("/")){
	            			displayname = displayname.substring(1,displayname.length());
	            		}
		            	propFindInfo.setDisplayname(displayname);
	            	}else{
	            		String displayname = abstpath.substring(URIUtil.encodePath(path).length(),abstpath.length());
	            		displayname = URIUtil.decode(displayname);
	            		if(displayname.endsWith("/")){
	            			displayname = displayname.substring(0,displayname.length() - 1);
	            		}
	            		if(displayname.startsWith("/")){
	            			displayname = displayname.substring(1,displayname.length());
	            		}
            			propFindInfo.setDisplayname(displayname);
	            	}
	            }
	            Enumeration responseProperties = propfindMethod.getResponseProperties(href_src);
	            while (responseProperties.hasMoreElements()) {
	                Property property =
	                    (Property) responseProperties.nextElement();
	            	
	                if(property.getLocalName().equals("resourcetype")){
	            		if(property.getPropertyAsString().equals("COLLECTION")){
	            			propFindInfo.setDirectory(true);
	            		}else{
	            			propFindInfo.setPath(path);
	            		}
	            	}else if(property.getLocalName().equals("getcontentlength")){
	            		try{
	            			long contentlength = Long.parseLong(property.getPropertyAsString());
	            			propFindInfo.setContentlength(contentlength);
	            		}catch(NumberFormatException e){}
	            	}else if(property.getLocalName().equals("getlastmodified")){
	        			Date lastmodified;
						try {
					        SimpleDateFormat sdf = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US);
					        lastmodified = sdf.parse(property.getPropertyAsString());
							propFindInfo.setLastmodified(lastmodified);
						} catch (ParseException e) {}
            		}else if(property.getLocalName().equals("lockdiscovery")){
            			if(property instanceof LockDiscoveryProperty){
            				LockDiscoveryProperty lockDiscoveryProperty = (LockDiscoveryProperty)property;
            				Lock[] locks = lockDiscoveryProperty.getActiveLocks();
            				if(locks != null){
            					propFindInfo.setLock(true);
            					for(int i=0;i<locks.length;i++){
            						propFindInfo.addLocks(locks[i]);
            					}
              			                 					
            				}
            			}
            		}else if(property.getLocalName().equals("displayname")){
            			if(!property.getPropertyAsString().equals("")){
                			propFindInfo.setDisplayname(property.getPropertyAsString());
            			}
            		}

	            }
	            list.add(propFindInfo);
	        }
	        
		}finally{
			if(propfindMethod != null){
				propfindMethod.releaseConnection();
			}
		}
		if(list.size() == 0){
			return null;
		}
		PropFindInfo[] propFindInfos = new PropFindInfo[list.size()];
		for(int i=0;i<list.size();i++){
			propFindInfos[i] = (PropFindInfo)list.get(i);
		}
		return propFindInfos;
	}
	
	public boolean download(String path_,String name,File file) throws IOException{
		String path = getPath(path_);
		String requestpath = URIUtil.encodePath(urlStr + path);

		GetMethod getMethod = null;
		try{
			getMethod = new GetMethod(requestpath + URIUtil.encodePath(name));
			getMethod.setDoAuthentication(true);

			// 問い合わせ
	    	int state =  client.executeMethod(getMethod);
		
			if ( state != HttpStatus.SC_OK ) {
				return false;
			}
			FileOutputStream fo = new FileOutputStream(file);
			byte buf[]=new byte[1024];
			int len;
			InputStream is = getMethod.getResponseBodyAsStream();
			while((len=is.read(buf))!=-1){
				fo.write(buf,0,len);
			}
			fo.flush();
			fo.close();
		}finally{
			if(getMethod != null){
				getMethod.releaseConnection();
			}
		}
		return true;
	}

	
	public boolean  download(String path_,String name,String outputpath) throws IOException{
		String path = getPath(path_);
		String requestpath = URIUtil.encodePath(urlStr + path);

		GetMethod getMethod = null;
		try{
			getMethod = new GetMethod(requestpath + URIUtil.encodePath(name));
			getMethod.setDoAuthentication(true);

			// 問い合わせ
	    	int state =  client.executeMethod(getMethod);
		
			if ( state != HttpStatus.SC_OK ) {
				return false;
			}
			if(!outputpath.endsWith("/")){
				outputpath+="/";
			}
			FileOutputStream fo = new FileOutputStream(outputpath + name);
			byte buf[]=new byte[1024];
			int len;
			InputStream is = getMethod.getResponseBodyAsStream();
			while((len=is.read(buf))!=-1){
				fo.write(buf,0,len);
			}
			fo.flush();
			fo.close();
		}finally{
			if(getMethod != null){
				getMethod.releaseConnection();
			}
		}
		return true;
	}
	public boolean  upload(String path_,String name,File file,String lockToken) throws IOException{
		String path = getPath(path_);
		String requestpath = URIUtil.encodePath(urlStr + path);

		PutMethod putMethod = null;
		try{
			putMethod = new PutMethod(requestpath + URIUtil.encodePath(name));
            if( lockToken != null ){
            	putMethod.setRequestHeader("If","<" + requestpath + name +"> (<"+lockToken+">)" );
            }
            putMethod.setDoAuthentication(true);

			RequestEntity requestEntity = null;
			if(file.isFile() && file.exists()){
				long size = file.length();
				FileInputStream fis = new FileInputStream(file);
				requestEntity = new InputStreamRequestEntity(fis,size); 
			}else{
				putMethod.releaseConnection();
				return false;
			}
			putMethod.setRequestEntity(requestEntity);

			int state =  client.executeMethod(putMethod);
			if (!(state >= 200 && state < 300)) {
	        	return false;
	        }else{
				return true;
	        }
		}finally{
			if(putMethod != null){
				putMethod.releaseConnection();
			}
		}
	}
	public String lock(String path_,String name,String owner) throws IOException{
		String path = getPath(path_);
		if(name != null){
			path += name;
		}
		String requestpath = URIUtil.encodePath(urlStr + path);

		String lockToken = null;
		LockMethod lockMethod = null;
		try{
			lockMethod = new LockMethod(requestpath,owner,LockMethod.SCOPE_EXCLUSIVE,LockMethod.TIMEOUT_INFINITY);
			lockMethod.setDoAuthentication(true);
//			lockMethod.setDebug(1);
			int state = client.executeMethod(lockMethod);
			if ( state != HttpStatus.SC_OK ) {
				return null;
			}
			lockToken = lockMethod.getLockToken();
		}finally{
			if(lockMethod != null){
				lockMethod.releaseConnection();
			}
		}
		return lockToken;
	}
	public boolean unlock(String path_,String name,String locktoken) throws IOException{
		if(locktoken == null){
			return true;
		}
		String path = getPath(path_);

		if(name != null){
			path += name;
		}
		String requestpath = URIUtil.encodePath(urlStr + path);

		UnlockMethod unlockMethod = null;
		try{
			unlockMethod = new UnlockMethod(requestpath,locktoken);
			unlockMethod.setDoAuthentication(true);
//			unlockMethod.setDebug(1);
			int	state = client.executeMethod(unlockMethod);;
			if ( state != HttpStatus.SC_OK ) {
				return false;
			}
		}finally{
			if(unlockMethod != null){
				unlockMethod.releaseConnection();
			}
		}
		return true;
	}
	public String getPath(String path_){
		String path = path_;
		if(path == null){
			path = "";
		}
		if(!path.equals("")){
			if(!path.equals("/")){
				if(!path.endsWith("/")){
					path+="/";
				}
			}else{
				path = "";
			}
			if(path.startsWith("/")){
				path = path.substring(1,path.length());
			}
		}
		return path;
	}
}
