/*
@license.text@
 */
package com.hammurapi.common;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;

import com.hammurapi.convert.ConversionException;
import com.hammurapi.convert.ConvertingService;


public class ResourceLoaderImpl implements ResourceLoader {

	private ResourceSet resourceSet;
	private String[] resolutionPaths;
	
	public ResourceLoaderImpl(ResourceSet resourceSet, String... resolutionPaths) {
		this.resourceSet = resourceSet;
		this.resolutionPaths = resolutionPaths;
	}
	
	/**
	 * 
	 * @param profile Profile, can be null
	 * @param extension Extension, can be null
	 * @return
	 */
	public InputStream getResourceStream(String scriptName, String extension) {
		return getResourceStream(scriptName, Locale.getDefault(), extension);
	}
	
	@Override
	public URL getResource(String scriptName, String extension) {
		return getResource(scriptName, Locale.getDefault(), extension);
	}
	
	private enum Variant {
		LOCALE, LANG_COUNTRY, LANG, GENERIC 
	}

	/**
	 * @param scriptName
	 * @param extension
	 * @return
	 */
	public InputStream getResourceStream(final String scriptName, final Locale locale, final String extension) {
		Locale actualLocale = locale==null ? Locale.getDefault() : locale;
		for (String path: resolutionPaths) {
			for (Variant v: Variant.values()) {
				String variant=path;
				if (scriptName!=null) {
					variant+="!"+scriptName;
				}
				
				switch (v) {
				case LOCALE:
					variant+="_"+actualLocale;
					break;
				case LANG_COUNTRY:
					variant+="_"+actualLocale.getLanguage();
					if (actualLocale.getCountry().length()!=0) {
						variant+="_"+actualLocale.getCountry();
					}
					break;
				case LANG:
					variant+="_"+actualLocale.getLanguage();
					break;
				case GENERIC:
					break;	
				}
				
				if (extension!=null) {
					variant+="."+extension;
				}
							
				InputStream ret = resourceSet.getResourceStream(variant);
				if (ret!=null) {
					return ret;
				}
			}			
		}

		return null;
	}

	/**
	 * @param scriptName
	 * @param extension
	 * @return
	 */
	public URL getResource(final String scriptName, final Locale locale, final String extension) {
		Locale actualLocale = locale==null ? Locale.getDefault() : locale;
		for (String path: resolutionPaths) {
			for (Variant v: Variant.values()) {
				String variant=path;
				if (scriptName!=null) {
					variant+="!"+scriptName;
				}
				
				switch (v) {
				case LOCALE:
					variant+="_"+actualLocale;
					break;
				case LANG_COUNTRY:
					variant+="_"+actualLocale.getLanguage();
					if (actualLocale.getCountry().length()!=0) {
						variant+="_"+actualLocale.getCountry();
					}
					break;
				case LANG:
					variant+="_"+actualLocale.getLanguage();
					break;
				case GENERIC:
					break;	
				}
				
				if (extension!=null) {
					variant+="."+extension;
				}
							
				URL ret = resourceSet.getResource(variant);
				if (ret!=null) {
					return ret;
				}
			}			
		}

		return null;
	}

	/**
	 * Collects properties from class hierarchy.  
	 * @param profile Profile, can be null
	 * @param extension Extension, can be null
	 * @return
	 */
	public Map<String, Object> getProperties(String profile, String extension) {
		return getProperties(profile, Locale.getDefault(), extension);
	}
	
	/**
	 * Collects properties from class hierarchy.  
	 * @param profile
	 * @param extension
	 * @return
	 */
	public Map<String, Object> getProperties(final String profile, final Locale locale, final String extension) {				
		return getProperties(profile, locale, extension, Thread.currentThread().getContextClassLoader());
	}
	
	/**
	 * Collects properties from class hierarchy. Property can have type. Type name is enclosed into parenthesis, e.g.
	 * <code>attempts(java.lang.Integer)=33</code>. If property is defined with type then string value of the property is converted to the
	 * target type. If not, property type is String. 
	 * @param profile
	 * @param extension
	 * @return
	 */
	public Map<String, Object> getProperties(final String profile, final Locale locale, final String extension, ClassLoader classLoader) {				
		Map<String, Object> ret = new HashMap<String, Object>(); 
		Locale actualLocale = locale==null ? Locale.getDefault() : locale;
		for (String path: resolutionPaths) {
			for (Variant v: Variant.values()) {
				String variant=path;
				if (profile!=null) {
					variant+="!"+profile;
				}
				
				switch (v) {
				case LOCALE:
					variant+="_"+actualLocale;
					break;
				case LANG_COUNTRY:
					variant+="_"+actualLocale.getLanguage();
					if (actualLocale.getCountry().length()!=0) {
						variant+="_"+actualLocale.getCountry();
					}
					break;
				case LANG:
					variant+="_"+actualLocale.getLanguage();
					break;
				case GENERIC:
					break;
				}
				if (extension!=null) {
					variant+="."+extension;
				}
							
				InputStream s = resourceSet.getResourceStream(variant);
				if (s!=null) {
					Properties cp=new Properties();
					try {
						cp.load(s);
						for (Map.Entry<Object, Object> entry: cp.entrySet()) {
							String key = (String) entry.getKey();
							Object value = entry.getValue();							
							int idx = key.charAt(key.length()-1)==')' ? key.lastIndexOf('(') : -1;
							String propertyClassName = null;
							if (idx>0) {
								propertyClassName = key.substring(idx+1, key.length()-1);
								key = key.substring(0, idx);								
							}
							if (!ret.containsKey(key)) {
								if (propertyClassName!=null) {
									try {
										Class<?> propertyClass = classLoader.loadClass(propertyClassName);
										value = ConvertingService.convert(value, propertyClass, classLoader);
									} catch (ClassNotFoundException e) {
										throw new ConversionException(e);
									}
								}
								ret.put(key, value);							
							}						
						}
					} catch (IOException e) {
						throw new RuntimeException("Cannot load properties from resource "+variant, e);
					}
					
					break;
				}
			}			
		}
				
		
		return ret;
	}
}
