/*
 * Copyright 2014 Loic Merckel
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software distributed under the License
 * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 * or implied. See the License for the specific language governing permissions and limitations under
 * the License.
 */

package io.uploader.drive.config;


import io.uploader.drive.config.auth.AuthenticationSettingsImpl;
import io.uploader.drive.config.auth.HasAuthenticationSettings;
import io.uploader.drive.config.proxy.HasProxySettings;
import io.uploader.drive.config.proxy.Proxy;
import io.uploader.drive.config.proxy.ProxySettingsImpl;

import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.net.Authenticator;
import java.net.PasswordAuthentication;

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.XMLConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.api.client.auth.oauth2.Credential;

public enum Configuration implements HasConfiguration {
	INSTANCE ;
	
	final private Logger logger = LoggerFactory.getLogger(Configuration.class);
	
	private final String defaultXmlPath ; 
	
	private final String appName = "Drive Uploader" ;
	private final String appVersion = "1.1-SNAPSHOT" ;
	
	@Override
	public String getAppName ()
	{
		return appName ;
	}
	
	@Override
	public String getAppVersion ()
	{
		return appVersion ;
	}
	
	@Override
	public String getDataStoreDirectory() {
		String app = appName.toLowerCase().replaceAll(" ", "") ; 
		StringBuilder sb = new StringBuilder ();
		sb.append(System.getProperty("user.home")) ;
		sb.append(File.separator) ;
		sb.append(".") ;
		sb.append(app) ;
		sb.append(File.separator) ;
		sb.append(appVersion) ;
		sb.append(File.separator) ;
		sb.append("store") ;
		sb.append(File.separator) ;
		return sb.toString() ;
	}
	
	
	@Override
	public String getTmpDirectory() {
		String app = appName.toLowerCase().replaceAll(" ", "") ; 
		StringBuilder sb = new StringBuilder ();
		sb.append(System.getProperty("user.home")) ;
		sb.append(File.separator) ;
		sb.append(".") ;
		sb.append(app) ;
		sb.append(File.separator) ;
		sb.append(appVersion) ;
		sb.append(File.separator) ;
		sb.append("tmp") ;
		sb.append(File.separator) ;
		File dir = new File (sb.toString()) ;
		if (!dir.exists()) {
			dir.mkdirs() ;
		}
		return sb.toString() ;
	}
	
	
	private Configuration ()
	{
		String app = appName.toLowerCase().replaceAll(" ", "") ; 
		StringBuilder sb = new StringBuilder ();
		sb.append(System.getProperty("user.home")) ;
		sb.append(File.separator) ;
		sb.append(".") ;
		sb.append(app) ;
		sb.append(File.separator) ;
		sb.append(appVersion) ;
		sb.append(File.separator) ;
		sb.append(app) ;
		sb.append("-settings.xml") ;
		defaultXmlPath = sb.toString() ;
	}
	
	private volatile XMLConfiguration config = null ;
	
	private final ProxySettingsImpl httpProxySettings = new ProxySettingsImpl ("proxy.http", 80, "http") ;
	private final ProxySettingsImpl httpsProxySettings = new ProxySettingsImpl ("proxy.https", 443, "https") ;
	
	private Credential credential = null ;
	private AuthenticationSettingsImpl authenticationSettingsImpl = null ;

	public void load (String xmlPath) throws ConfigurationException
	{
		String settingFilePath = (xmlPath == null || xmlPath.isEmpty()) ? (defaultXmlPath) : (xmlPath) ;
		
		logger.debug("Load setting file {}.", settingFilePath);
		
		checkConfigFile (settingFilePath) ;
		try{
			config = new XMLConfiguration(settingFilePath);
		}
		catch (ConfigurationException e) {
			logger.error("Error occurred while opening the settings file", e);
		}
		if (config == null)
			return ;
		// TODO: when the commom-configuration 2.0 will be released
		// set the synchronizer
		//config.setSynchronizer(new ReadWriteSynchronizer());
		config.setThrowExceptionOnMissing(false);
		httpProxySettings.setConfig(config);
		httpsProxySettings.setConfig(config);
		
		setProxy () ;
	}
	
	
	private void checkConfigFile (String path)
	{
		try 
		{			
			File file = new File (path) ;
			if (!file.exists())
			{
				File parent = file.getParentFile() ;
				if (parent != null)
					parent.mkdirs() ;
				file.createNewFile() ;
				PrintWriter writer = new PrintWriter(file.getPath(), "UTF-8");
				writer.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?><settings></settings>" );
				writer.close();	
			}
		} 
		catch (IOException | SecurityException | NullPointerException e) 
		{
			logger.error("Error occurred while creating the settings file", e);
		}	
	}
	
	
	public void setAuthenticationSettingsImpl (AuthenticationSettingsImpl authenticationSettingsImpl) {
		this.authenticationSettingsImpl = authenticationSettingsImpl ;
	}
	
	
	@Override
	public HasAuthenticationSettings getAuthenticationSettings() {
		return authenticationSettingsImpl;
	}
	
	
	public void setCredential (Credential credential) {
		this.credential = credential ;
	}
	
	
	@Override
	public Credential getCredential() {
		return credential;
	}
	
	
	@Override
	public HasProxySettings getHttpProxySettings ()
	{
		return httpProxySettings ;
	}
	
	
	@Override
	public HasProxySettings getHttpsProxySettings ()
	{
		return httpsProxySettings ;
	}
	
		
	@Override
	public void updateProxy(Proxy newProxy) {
		
		try 
		{
			if ("http".equalsIgnoreCase(newProxy.getProtocol()))
			{
				httpProxySettings.update(newProxy);
				setProxySystemProperty (httpProxySettings, "http") ;
			}
			else if ("https".equalsIgnoreCase(newProxy.getProtocol()))
			{
				httpsProxySettings.update(newProxy);
				setProxySystemProperty (httpsProxySettings, "https") ;
			}
			else
			{
				logger.info("Unsupported proxy protocol: {}.", newProxy.getProtocol());
			}
		} 
		catch (ConfigurationException e) 
		{
			logger.error("Error occurred while updating the proxy settings", e);
		}
	}

	
	private static void setProxySystemProperty (HasProxySettings proxySettings, String prot)
	{
		if (proxySettings == null)
			return ;
		if (prot == null || prot.isEmpty())
			return ;
		
		prot = prot.toLowerCase() ;
		
    	if (proxySettings.isActive())
    	{
    		String host = proxySettings.getHost() ;
    		String user = proxySettings.getUsername() ;
    		String pwd = proxySettings.getPassword() ;
    		int port = proxySettings.getPort() ;
    		
    		if (host != null)
    			System.setProperty(prot + ".proxyHost", host);
	    	System.setProperty(prot + ".proxyPort", String.valueOf(port));
	    	if (user != null)
	    		System.setProperty(prot + ".proxyUser", user);
	    	if (pwd != null)
	    		System.setProperty(prot + ".proxyPassword", pwd);
    	}
	}

	
	
    private void setProxy ()
    {    	
    	setProxySystemProperty (httpProxySettings, "http") ;
    	setProxySystemProperty (httpsProxySettings, "https") ;
    	    	
		Authenticator.setDefault(new Authenticator() {
		    @Override
		    protected PasswordAuthentication getPasswordAuthentication() 
		    {
		        if (getRequestorType() == RequestorType.PROXY) 
		        {
		            String prot = getRequestingProtocol().toLowerCase();
		            
		            String host = System.getProperty(prot + ".proxyHost", "");
		            String port = System.getProperty(prot + ".proxyPort", "80");
		            String user = System.getProperty(prot + ".proxyUser", "");
		            String password = System.getProperty(prot + ".proxyPassword", "");

		            if (getRequestingHost().equalsIgnoreCase(host)) 
		            {
		                if (Integer.parseInt(port) == getRequestingPort()) 
		                    return new PasswordAuthentication(user, password.toCharArray());  
		            }
		        }
		        return null;
		    }  
		});
    }
}
