/**
 * Implements the CFML Function datediff
 */
package railo.runtime.functions.dateTime;

import java.util.Calendar;
import java.util.TimeZone;

import railo.commons.date.JREDateTimeUtil;
import railo.runtime.PageContext;
import railo.runtime.exp.ExpressionException;
import railo.runtime.exp.FunctionException;
import railo.runtime.ext.function.Function;
import railo.runtime.op.Decision;
import railo.runtime.type.dt.DateTime;

/**
 * 
 */
public final class DateDiff implements Function {

	//private static final int DATEPART_S = 0;
	//private static final int DATEPART_N = 1;
	//private static final int DATEPART_H = 2;
	private static final int DATEPART_D = 3;
	private static final int DATEPART_Y = DATEPART_D;
	private static final int DATEPART_YYYY = 10;
	private static final int DATEPART_M = 11;
	private static final int DATEPART_W = 12;
	private static final int DATEPART_WW = DATEPART_W;
	private static final int DATEPART_Q = 20;
	
	//private static Calendar _cRight;
	//private static Calendar _cLeft;


	/**
	 * @param pc
	 * @param s
	 * @param date
	 * @param date1
	 * @return
	 * @throws ExpressionException
	 */
	public synchronized static double call(PageContext pc , String datePart, DateTime left, DateTime right) throws ExpressionException	{
		long msLeft = left.getTime();
		long msRight = right.getTime();
		TimeZone tz = pc.getTimeZone();
		//if(true)return 0;
		// Date Part
		datePart=datePart.toLowerCase().trim();
		int dp;
		if("s".equals(datePart))		return diffSeconds(msLeft, msRight);
		else if("n".equals(datePart))	return diffSeconds(msLeft, msRight)/60L;
		else if("h".equals(datePart))	return diffSeconds(msLeft, msRight)/3600L;
		else if("d".equals(datePart))	dp=DATEPART_D;
		else if("y".equals(datePart))	dp=DATEPART_Y;
		else if("yyyy".equals(datePart))dp=DATEPART_YYYY;
		else if("m".equals(datePart))	dp=DATEPART_M;
		else if("w".equals(datePart))	dp=DATEPART_W;
		else if("ww".equals(datePart))	dp=DATEPART_WW;
		else if("q".equals(datePart))	dp=DATEPART_Q;
		else throw new FunctionException(pc,"dateDiff",3,"datePart","invalid value ["+datePart+"], valid values has to be [q,s,n,h,d,m,y,yyyy,w,ww]");
		
		
		
		// dates
		Calendar _cLeft = JREDateTimeUtil.getThreadCalendar(tz);
		_cLeft.setTimeInMillis(msLeft);
		
		Calendar _cRight = JREDateTimeUtil.newInstance(tz);
		_cRight.setTimeInMillis(msRight);
			
			
			if(msLeft>msRight) 
				return -_call(pc,dp, _cRight, msRight, _cLeft, msLeft);
			
			return _call(pc,dp, _cLeft, msLeft, _cRight, msRight);
		//}
	}
	
	public static long diffSeconds(long msLeft, long msRight) {
		if(msLeft>msRight)
			return -(long)((msLeft-msRight)/1000D);
		return (long)((msRight-msLeft)/1000D);
	}
	
	/*private static long _call(int datepart, long msLeft, long msRight) throws ExpressionException {
		
		long msDiff = msRight-msLeft;
		double diff = msDiff/1000D;
		if(DATEPART_S==datepart)	{
			return (long) diff;
		}
		if(DATEPART_N==datepart)	{
			return (long)(diff/60D);
		}
		if(DATEPART_H==datepart)	{
			return (long)(diff/3600D);
		}
		return 0;
	}*/
	
	private static long _call(PageContext pc , int datepart, Calendar cLeft, long msLeft, Calendar cRight, long msRight) throws ExpressionException {
		
		//long msDiff = msRight-msLeft;
		//double diff = msDiff/1000D;
		/*if(DATEPART_S==datepart)	{
			return (long) diff;
		}
		if(DATEPART_N==datepart)	{
			return (long)(diff/60D);
		}
		if(DATEPART_H==datepart)	{
			return (long)(diff/3600D);
		}*/
		
		long dDiff = cRight.get(Calendar.DATE)-cLeft.get(Calendar.DATE);
		long hDiff = cRight.get(Calendar.HOUR_OF_DAY)-cLeft.get(Calendar.HOUR_OF_DAY);
		long nDiff = cRight.get(Calendar.MINUTE)-cLeft.get(Calendar.MINUTE);
		long sDiff = cRight.get(Calendar.SECOND)-cLeft.get(Calendar.SECOND);
		

		if(DATEPART_D==datepart || DATEPART_W==datepart)	{

			int tmp=0;
			if(hDiff<0) 	tmp=-1;
			else if(hDiff>0);
			else if(nDiff<0) 	tmp=-1;
			else if(nDiff>0);
			else if(sDiff<0) 	tmp=-1;
			else if(sDiff>0);
			long rst = dayDiff(cLeft, cRight)+tmp;
			if(DATEPART_W==datepart)rst/=7;
			return rst;
		}
		
		long yDiff = cRight.get(Calendar.YEAR)-cLeft.get(Calendar.YEAR);
		long mDiff = cRight.get(Calendar.MONTH)-cLeft.get(Calendar.MONTH);
		if(DATEPART_YYYY==datepart)	{
			int tmp=0;
			if(mDiff<0)			tmp=-1;
			else if(mDiff>0);
			else if(dDiff<0) 	tmp=-1;
			else if(dDiff>0);
			else if(hDiff<0) 	tmp=-1;
			else if(hDiff>0);
			else if(nDiff<0) 	tmp=-1;
			else if(nDiff>0);
			else if(sDiff<0) 	tmp=-1;
			else if(sDiff>0);
			return yDiff+tmp;
		}
		if(DATEPART_M==datepart || DATEPART_Q==datepart)	{

			int tmp=0;
			if(dDiff<0) 		tmp=-1;
			else if(dDiff>0);
			else if(hDiff<0) 	tmp=-1;
			else if(hDiff>0);
			else if(nDiff<0) 	tmp=-1;
			else if(nDiff>0);
			else if(sDiff<0) 	tmp=-1;
			else if(sDiff>0);
			long rst = mDiff+(yDiff*12)+tmp;
			if(DATEPART_Q==datepart)rst/=3;
			return rst;
		}
		if(DATEPART_D==datepart)	{

			long rst = dDiff;
			//if(DATEPART_Q==datepart)rst/=3;
			return rst;
		}
		
		
		throw new FunctionException(pc,"dateDiff",3,"datePart","invalid value, valid values has to be [q,s,n,h,d,m,y,yyyy,w,ww]");
		
	}
	


	private static long dayDiff(Calendar l, Calendar r) {
		int lYear = l.get(Calendar.YEAR);
		int rYear = r.get(Calendar.YEAR);
		int lDayOfYear=l.get(Calendar.DAY_OF_YEAR);
		int rDayOfYear=r.get(Calendar.DAY_OF_YEAR);

		
		// same year
		if(lYear==rYear){
			return rDayOfYear-lDayOfYear;
		}
		
		long diff=rDayOfYear;
		diff-=lDayOfYear;
		for(int year=lYear;year<rYear;year++){
			diff+=Decision.isLeapYear(year)?366L:365L;
		}
		return diff;
	}

}