package railo.runtime.orm;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Iterator;

import org.w3c.dom.Attr;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;

import railo.commons.digest.MD5;
import railo.commons.io.res.Resource;
import railo.commons.io.res.util.ResourceUtil;
import railo.commons.lang.StringUtil;
import railo.runtime.PageContext;
import railo.runtime.PageSource;
import railo.runtime.config.Config;
import railo.runtime.config.ConfigWebImpl;
import railo.runtime.engine.ThreadLocalPageContext;
import railo.runtime.exp.ExpressionException;
import railo.runtime.listener.ApplicationContext;
import railo.runtime.op.Caster;
import railo.runtime.op.Decision;
import railo.runtime.type.Array;
import railo.runtime.type.ArrayImpl;
import railo.runtime.type.Collection;
import railo.runtime.type.Collection.Key;
import railo.runtime.type.KeyImpl;
import railo.runtime.type.Struct;
import railo.runtime.type.StructImpl;
import railo.runtime.type.util.KeyConstants;
import railo.runtime.type.util.ListUtil;

public class ORMConfigurationImpl implements ORMConfiguration {
	public static final int DBCREATE_NONE=0;
	public static final int DBCREATE_UPDATE=1;
	public static final int DBCREATE_DROP_CREATE=2;
	

	public static final Key AUTO_GEN_MAP = KeyImpl.init("autogenmap");
	public static final Key CATALOG = KeyImpl.init("catalog");
	public static final Key IS_DEFAULT_CFC_LOCATION = KeyImpl.init("isDefaultCfclocation");
	public static final Key DB_CREATE = KeyImpl.init("dbCreate");
	public static final Key DIALECT = KeyImpl.init("dialect");
	public static final Key FLUSH_AT_REQUEST_END = KeyImpl.init("flushAtRequestEnd");
	public static final Key LOG_SQL = KeyImpl.init("logSql");
	public static final Key SAVE_MAPPING = KeyImpl.init("savemapping");
	public static final Key SCHEMA = KeyImpl.init("schema");
	public static final Key SECONDARY_CACHE_ENABLED = KeyImpl.init("secondarycacheenabled");
	public static final Key SQL_SCRIPT = KeyImpl.init("sqlscript");
	public static final Key USE_DB_FOR_MAPPING = KeyImpl.init("useDBForMapping");
	public static final Key CACHE_CONFIG = KeyImpl.init("cacheconfig");
	public static final Key CACHE_PROVIDER = KeyImpl.init("cacheProvider");
	public static final Key ORM_CONFIG = KeyImpl.init("ormConfig");
	public static final Key EVENT_HANDLING = KeyImpl.init("eventHandling");
	public static final Key EVENT_HANDLER = KeyImpl.init("eventHandler");
	public static final Key AUTO_MANAGE_SESSION = KeyImpl.init("autoManageSession");
	public static final Key NAMING_STRATEGY = KeyImpl.init("namingstrategy");
	public static final Key CFC_LOCATION = KeyImpl.init("cfcLocation");
	
	
	private boolean autogenmap=true;
	private String catalog;
	private Resource[] cfcLocations;
	private int dbCreate=DBCREATE_NONE;
	private String dialect;
	private Boolean eventHandling=null;
	private boolean flushAtRequestEnd=true;
	private boolean logSQL;
	private boolean saveMapping;
	private String schema;
	private boolean secondaryCacheEnabled;
	private Resource sqlScript;
	private boolean useDBForMapping=true;
	private Resource cacheConfig;
	private String cacheProvider;
	private Resource ormConfig;
	private String eventHandler;
	private String namingStrategy;
	private boolean isDefaultCfcLocation=true;
	private boolean skipCFCWithError=true;
	private boolean autoManageSession=true;

	private ORMConfigurationImpl(){
		autogenmap=true;
		dbCreate=DBCREATE_NONE;
		flushAtRequestEnd=true;
		useDBForMapping=true;		
	}
	
	
	
	


	public static ORMConfiguration load(Config config,ApplicationContext ac, Element el, Resource defaultCFCLocation,ORMConfiguration defaultConfig) {
		return _load(config,ac, new _GetElement(el),defaultCFCLocation,defaultConfig);
	}
	
	public static ORMConfiguration load(Config config,ApplicationContext ac,Struct settings, Resource defaultCFCLocation,ORMConfiguration defaultConfig) {
		return _load(config,ac, new _GetStruct(settings),defaultCFCLocation,defaultConfig);
	}

	private static ORMConfiguration _load(Config config,ApplicationContext ac, _Get settings, Resource defaultCFCLocation,ORMConfiguration dc) {
		
		if(dc==null)dc=new ORMConfigurationImpl();
		ORMConfigurationImpl c = ((ORMConfigurationImpl)dc).duplicate();
		c.cfcLocations=defaultCFCLocation==null?new Resource[0]:new Resource[]{defaultCFCLocation};
		
		// autogenmap
		c.autogenmap=Caster.toBooleanValue(settings.get(AUTO_GEN_MAP,dc.autogenmap()),dc.autogenmap());
		
		// catalog
		c.catalog=StringUtil.trim(Caster.toString(settings.get(CATALOG,dc.getCatalog()),dc.getCatalog()),dc.getCatalog());
		
		// cfclocation
		Object obj = settings.get(KeyConstants._cfcLocation,null);
		
		if(obj!=null){
			java.util.List<Resource> list=loadCFCLocation(config,ac,obj,true);
			
			if(list!=null && list.size()>0){
				c.cfcLocations=list.toArray(new Resource[list.size()]);
				c.isDefaultCfcLocation=false;
			}
		}
		if(c.cfcLocations == null)
			c.cfcLocations=defaultCFCLocation==null?new Resource[0]:new Resource[]{defaultCFCLocation};
		
		// dbcreate
		obj = settings.get(DB_CREATE,null);
		if(obj!=null){
			String str = Caster.toString(obj,"").trim().toLowerCase();
			c.dbCreate=dbCreateAsInt(str);
		}
		
		// dialect
		c.dialect = StringUtil.trim(Caster.toString(settings.get(DIALECT,dc.getDialect()),dc.getDialect()),dc.getDialect());
		
		// namingstrategy
		c.namingStrategy=Caster.toString(settings.get(NAMING_STRATEGY,dc.namingStrategy()),dc.namingStrategy());
		
		// eventHandler
		c.eventHandler=Caster.toString(settings.get(EVENT_HANDLER,dc.eventHandler()),dc.eventHandler());
		
		// eventHandling
		Boolean b=Caster.toBoolean(settings.get(EVENT_HANDLING,null),null);
		if(b==null) {
			if(dc.eventHandling()) 
				b=Boolean.TRUE;
			else 
				b=!StringUtil.isEmpty(c.eventHandler,true);
		}
		c.eventHandling=b;
		
		// flushatrequestend
		c.flushAtRequestEnd=Caster.toBooleanValue(settings.get(FLUSH_AT_REQUEST_END,dc.flushAtRequestEnd()),dc.flushAtRequestEnd());
		
		// logSQL
		c.logSQL=Caster.toBooleanValue(settings.get(LOG_SQL,dc.logSQL()),dc.logSQL());
		

		// autoManageSession
		c.autoManageSession=Caster.toBooleanValue(settings.get(AUTO_MANAGE_SESSION,dc.autoManageSession()),dc.autoManageSession());
		
		// skipCFCWithError
		c.skipCFCWithError=Caster.toBooleanValue(settings.get(KeyConstants._skipCFCWithError,dc.skipCFCWithError()),dc.skipCFCWithError());
		
		// savemapping
		c.saveMapping=Caster.toBooleanValue(settings.get(SAVE_MAPPING,dc.saveMapping()),dc.saveMapping());
		
		// schema
		c.schema=StringUtil.trim(Caster.toString(settings.get(SCHEMA,dc.getSchema()),dc.getSchema()),dc.getSchema());
		
		// secondarycacheenabled
		c.secondaryCacheEnabled=Caster.toBooleanValue(settings.get(SECONDARY_CACHE_ENABLED,dc.secondaryCacheEnabled()),dc.secondaryCacheEnabled());
		
		// sqlscript
		obj = settings.get(SQL_SCRIPT,null);
		if(!StringUtil.isEmpty(obj)){
			try {
				c.sqlScript=toRes(config, obj, true);
			} catch (ExpressionException e) {
				//print.e(e);
			}
		}
		
		// useDBForMapping
		c.useDBForMapping=Caster.toBooleanValue(settings.get(USE_DB_FOR_MAPPING,dc.useDBForMapping()),dc.useDBForMapping());
		
		// cacheconfig
		obj = settings.get(CACHE_CONFIG,null);
		if(!StringUtil.isEmpty(obj)){
			try {
				c.cacheConfig=toRes(config, obj, true);
			} catch (ExpressionException e) {
				//print.printST(e);
			}
		}
		
		// cacheprovider
		c.cacheProvider=StringUtil.trim(Caster.toString(settings.get(CACHE_PROVIDER,dc.getCacheProvider()),dc.getCacheProvider()),dc.getCacheProvider());
		
		// ormconfig
		obj = settings.get(ORM_CONFIG,null);
		if(!StringUtil.isEmpty(obj)){
			try {
				c.ormConfig=toRes(config, obj, true);
			} catch (ExpressionException e) {
				//print.printST(e);
			}
		}
		
		return c;
	}	

	public static java.util.List<Resource> loadCFCLocation(Config config, ApplicationContext ac,Object obj, boolean onlyDir) {
		
		Resource res;
		if(!Decision.isArray(obj)){
			String list = Caster.toString(obj,null);
			if(!StringUtil.isEmpty(list)) {
				obj=ListUtil.listToArray(list, ',');
			}
		}
		
		if(Decision.isArray(obj)) {
			Array arr=Caster.toArray(obj,null);
			java.util.List<Resource> list=new ArrayList<Resource>();
			Iterator<Object> it = arr.valueIterator();
			while(it.hasNext()){
				try	{
					res=toResourceExisting(config,ac,it.next(),onlyDir);
					if(res!=null) list.add(res);
				}
				catch(Throwable t){}
			}
			return list;
		}
		
		
		return null;
	}






	private static Resource toRes(Config config, Object obj, boolean existing) throws ExpressionException {
		PageContext pc = ThreadLocalPageContext.get();
		if(pc!=null)return Caster.toResource(pc, obj, existing);
		return Caster.toResource(config, obj, existing);
	}

	private static Resource toResourceExisting(Config config, ApplicationContext ac,Object obj, boolean onlyDir) {
		//Resource root = config.getRootDirectory();
		String path = Caster.toString(obj,null);
		if(StringUtil.isEmpty(path,true)) return null;
		path=path.trim();
		Resource res;
		PageContext pc = ThreadLocalPageContext.get();
		
		// first check relative to application.cfc
		if(pc!=null) {
			if(ac==null) ac= pc.getApplicationContext();
			
			// abs path
			if(path.startsWith("/")){
				ConfigWebImpl cwi=(ConfigWebImpl) config;
				PageSource ps = cwi.getPageSourceExisting(
						pc, ac==null?null:ac.getMappings(), path, false, false, true, false);
				//res=cwi.getPhysicalResourceExistingX(
				//		pc, 
				//		ac==null?null:ac.getMappings(), path, 
				//		false, false, true);
				if(ps!=null){
					res=ps.getResource();
					if(res!=null && (!onlyDir || res.isDirectory())) return res;
				}
				
			}
			// real path
			else {
				Resource src= ac!=null?ac.getSource():null;
				if(src!=null) {
					res=src.getParentResource().getRealResource(path);
					if(res!=null && (!onlyDir || res.isDirectory())) return res;
				}
				// happens when this is called from within the application.cfc (init)
				else {
					res=ResourceUtil.toResourceNotExisting(pc, path);
					if(res!=null && (!onlyDir || res.isDirectory())) return res;
				}
			}
		}
		
		
		
		// then in the webroot
		res=config.getRootDirectory().getRealResource(path);
		if(res!=null && (!onlyDir || res.isDirectory())) return res;
		
		// then absolute
		res = ResourceUtil.toResourceNotExisting(config, path);
		
		if(res!=null && (!onlyDir || res.isDirectory())) return res;
		return null;
	}






	private ORMConfigurationImpl duplicate() {
		
		ORMConfigurationImpl other = new ORMConfigurationImpl();
		
		
		
		other.autogenmap=autogenmap;
		other.catalog=catalog;
		other.cfcLocations=cfcLocations;
		other.isDefaultCfcLocation=isDefaultCfcLocation;
		other.dbCreate=dbCreate;
		other.dialect=dialect;
		other.eventHandler=eventHandler;
		other.namingStrategy=namingStrategy;
		other.eventHandling=eventHandling;
		other.flushAtRequestEnd=flushAtRequestEnd;
		other.logSQL=logSQL;
		other.saveMapping=saveMapping;
		other.schema=schema;
		other.secondaryCacheEnabled=secondaryCacheEnabled;
		other.sqlScript=sqlScript;
		other.useDBForMapping=useDBForMapping;
		other.cacheConfig=cacheConfig;
		other.cacheProvider=cacheProvider;
		other.ormConfig=ormConfig;
		other.autoManageSession=autoManageSession;
		other.skipCFCWithError=skipCFCWithError;
		return other;
	}

	public String hash() {
		
		String data=autogenmap+":"+catalog+":"+isDefaultCfcLocation
		+":"+dbCreate+":"+dialect+":"+eventHandling+":"+namingStrategy+":"+eventHandler+":"+flushAtRequestEnd+":"+logSQL+":"+autoManageSession+":"+skipCFCWithError+":"+saveMapping+":"+schema+":"+secondaryCacheEnabled+":"+
		useDBForMapping+":"+cacheProvider
		
		+":"+toStr(cfcLocations)+":"+toStr(sqlScript)+":"+toStr(cacheConfig)+":"+toStr(ormConfig)
		;
		
		try {
			return MD5.getDigestAsString(data);
		} catch (IOException e) {
			return null;
		}
	}





	private String toStr(Resource res) {
		if(res==null) return "";
		return res.getAbsolutePath();
	}
	private String toStr(Resource[] reses) {
		if(reses==null) return "";
		StringBuilder sb=new StringBuilder();
		for(int i=0;i<reses.length;i++){
			sb.append(toStr(reses[i]));
		}
		return sb.toString();
	}






	/**
	 * @return the autogenmap
	 */
	public boolean autogenmap() {
		return autogenmap;
	}

	/**
	 * @return the catalog
	 */
	public String getCatalog() {
		return catalog;
	}

	/**
	 * @return the cfcLocation
	 */
	public Resource[] getCfcLocations() {
		return cfcLocations;
	}
	public boolean isDefaultCfcLocation() {
		return isDefaultCfcLocation;
	}

	/**
	 * @return the dbCreate
	 */
	public int getDbCreate() {
		return dbCreate;
	}

	/**
	 * @return the dialect
	 */
	public String getDialect() {
		return dialect;
	}

	/**
	 * @return the eventHandling
	 */
	public boolean eventHandling() {
		return eventHandling==null?false:eventHandling.booleanValue();
	}

	public String eventHandler() {
		return eventHandler;
	}

	public String namingStrategy() {
		return namingStrategy;
	}
	
	

	/**
	 * @return the flushAtRequestEnd
	 */
	public boolean flushAtRequestEnd() {
		return flushAtRequestEnd;
	}

	/**
	 * @return the logSQL
	 */
	public boolean logSQL() {
		return logSQL;
	}

	/**
	 * @return the saveMapping
	 */
	public boolean saveMapping() {
		return saveMapping;
	}

	/**
	 * @return the schema
	 */
	public String getSchema() {
		return schema;
	}

	/**
	 * @return the secondaryCacheEnabled
	 */
	public boolean secondaryCacheEnabled() {
		return secondaryCacheEnabled;
	}

	/**
	 * @return the sqlScript
	 */
	public Resource getSqlScript() {
		return sqlScript;
	}

	/**
	 * @return the useDBForMapping
	 */
	public boolean useDBForMapping() {
		return useDBForMapping;
	}

	/**
	 * @return the cacheConfig
	 */
	public Resource getCacheConfig() {
		return cacheConfig;
	}

	/**
	 * @return the cacheProvider
	 */
	public String getCacheProvider() {
		return cacheProvider;
	}

	/**
	 * @return the ormConfig
	 */
	public Resource getOrmConfig() {
		return ormConfig;
	}

	public boolean skipCFCWithError() {
		return skipCFCWithError;
	}
	public boolean autoManageSession() {
		return autoManageSession;
	}




	public Object toStruct() {
		
		Resource[] locs = getCfcLocations();
		Array arrLocs=new ArrayImpl();
		if(locs!=null)for(int i=0;i<locs.length;i++){
			arrLocs.appendEL(getAbsolutePath(locs[i]));
		}
		Struct sct=new StructImpl();
		sct.setEL(AUTO_GEN_MAP,this.autogenmap());
		sct.setEL(CATALOG,StringUtil.emptyIfNull(getCatalog()));
		sct.setEL(CFC_LOCATION,arrLocs);
		sct.setEL(IS_DEFAULT_CFC_LOCATION,isDefaultCfcLocation());
		sct.setEL(DB_CREATE,dbCreateAsString(getDbCreate()));
		sct.setEL(DIALECT,StringUtil.emptyIfNull(getDialect()));
		sct.setEL(EVENT_HANDLING,eventHandling());
		sct.setEL(EVENT_HANDLER,eventHandler());
		sct.setEL(NAMING_STRATEGY,namingStrategy());
		sct.setEL(FLUSH_AT_REQUEST_END,flushAtRequestEnd());
		sct.setEL(LOG_SQL,logSQL());
		sct.setEL(SAVE_MAPPING,saveMapping());
		sct.setEL(SCHEMA,StringUtil.emptyIfNull(getSchema()));
		sct.setEL(SECONDARY_CACHE_ENABLED,secondaryCacheEnabled());
		sct.setEL(SQL_SCRIPT,StringUtil.toStringEmptyIfNull(getSqlScript()));
		sct.setEL(USE_DB_FOR_MAPPING,useDBForMapping());
		sct.setEL(CACHE_CONFIG,getAbsolutePath(getCacheConfig()));
		sct.setEL(CACHE_PROVIDER,StringUtil.emptyIfNull(getCacheProvider()));
		sct.setEL(ORM_CONFIG,getAbsolutePath(getOrmConfig()));
		
		
		
		return sct;
	}


	private static String getAbsolutePath(Resource res) {
		if(res==null )return "";
		return res.getAbsolutePath();
	}






	public static int dbCreateAsInt(String dbCreate) {
		if(dbCreate==null)dbCreate="";
		else dbCreate=dbCreate.trim().toLowerCase();
		
		if("update".equals(dbCreate))return DBCREATE_UPDATE;
		if("dropcreate".equals(dbCreate))return DBCREATE_DROP_CREATE;
		if("drop-create".equals(dbCreate))return DBCREATE_DROP_CREATE;
		
		return DBCREATE_NONE;
	}
	



	public static String dbCreateAsString(int dbCreate) {
		
		switch(dbCreate){
		case DBCREATE_DROP_CREATE: return "dropcreate";
		case DBCREATE_UPDATE: return "update";
		}
		
		return "none";
	}






	

	




	
	
	
}

interface _Get {
	public Object get(Collection.Key name,Object defaultValue);
}

class _GetStruct implements _Get {
	
	private Struct sct;
	public _GetStruct(Struct sct){
		this.sct=sct;
	}
	
	public Object get(Collection.Key name,Object defaultValue){
		return sct.get(name,defaultValue);
	}
	
	public String toString(){
		return "_GetStruct:"+sct.toString();
	}
}

class _GetElement implements _Get {
	
	private Element el;
	public _GetElement(Element el){
		this.el=el;
	}
	public Object get(Collection.Key name,Object defaultValue){
		String value=_get(name.getString());
		if(value==null)value = _get(StringUtil.camelToHypenNotation(name.getString()));
		if(value==null)value = _get(name.getLowerString());
		if(value==null){
			NamedNodeMap map = el.getAttributes();
			int len=map.getLength();
			Attr attr;
			String n;
			for(int i=0;i<len;i++){
				attr=(Attr) map.item(i);
				n=attr.getName();
				n=StringUtil.replace(n, "-", "", false).toLowerCase();
				if(n.equalsIgnoreCase(name.getLowerString())) return attr.getValue();
			}
			
		}
		
		if(value==null) return defaultValue;
		return value;
	}
	
	private String _get(String name) {
		if(el.hasAttribute(name)) return el.getAttribute(name);
		return null;
	}
}

