package com.mongodb.hvdf.configuration;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DBObject;
import com.mongodb.hvdf.api.ConfigurationError;
import com.mongodb.hvdf.api.ServiceException;

public class PluginConfiguration {
	
	public static class HVDF{
		public static final String ALLOCATOR = "hvdf_allocator";
		public static final String DB = "hvdf_database";
		public static final String PREFIX = "hvdf_prefix";
		public static final String ID_FACTORY = "id_factory";
	}
	
	private static HashSet<Class<?>> registeredClasses = new HashSet<Class<?>>();
	
	static{
		registeredClasses.add(TimePeriod.class);
	}
	
	private final DBObject rawDocument;
	private final Class<?> targetClass;
	
	public PluginConfiguration(DBObject rawConfig, Class<?> target){
		
		if(rawConfig == null){
			rawConfig = new BasicDBObject();
		}
		
		this.rawDocument = rawConfig;
		this.targetClass = target;
	}

	
	public <T> T get(String itemName, Class<T> expectedType, T defaultValue) {
		
		Object itemObj = rawDocument.get(itemName);
		
		// If it exists, parse the object
		if(itemObj != null){
			return fromObject(itemName, expectedType, itemObj);
		}
		
		return defaultValue;	
	}
	
	public <T> T get(String itemName, Class<T> expectedType) {

		// If there is a valid config value then return it
		T value = get(itemName, expectedType, null);
		if(value != null) return value;

		// If not, throw a service exception
		throw new ServiceException("Required config item missing", 
				ConfigurationError.REQUIRED_CONFIG_MISSING).
					set("configuring", this.getClass()).
					set("requiredItem", itemName);	
	}	

	private <T> T fromObject(String itemName, Class<T> expectedType, Object itemObj) {
		
		try{			
			if(expectedType.equals(PluginConfiguration.class)){
				// This is an embedded config document
				return expectedType.cast((new PluginConfiguration((DBObject) itemObj, this.targetClass)));
			}
			else if(registeredClasses.contains(expectedType)){
				// One of the specially handled types, construct
				try {
					return (expectedType.getConstructor(Object.class).newInstance(itemObj));
				} catch (Exception ex) {throw new ClassCastException();} 
			}
			else{
				// This must be directly convertible to target type
				return expectedType.cast(itemObj);
			}
		} 
		catch(ClassCastException ccex){
			throw new ServiceException("Illegal type for config item", 
					ConfigurationError.INVALID_CONFIG_TYPE).
						set("configuring", targetClass).
						set(itemName, itemObj).
						set("expectedType", expectedType);	
		}				
	}

	public <T> List<T> getList(String itemName, Class<T> listOf) {
		
		List<T> list = new ArrayList<T>();
		Object itemObj = rawDocument.get(itemName);

		if(itemObj != null){
			
			// Must be a list
			if(itemObj instanceof BasicDBList){
				BasicDBList rawList = (BasicDBList)itemObj;
				for(Object listEntry : rawList){
					list.add(fromObject(itemName, listOf, listEntry));
				}
			}
			else{
				throw new ServiceException("Config item is not List", 
						ConfigurationError.INVALID_CONFIG_TYPE).
							set("configuring", targetClass).
							set(itemName, itemObj).
							set("expectedListOf", listOf);	
			}
		}
		
		// If the item doesnt exist
		return list;	
	}


	public DBObject getRaw() {
		return this.rawDocument;
	}
}