/**
 *
 * Copyright (c) 2014, the Railo Company Ltd. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public 
 * License along with this library.  If not, see <http://www.gnu.org/licenses/>.
 * 
 **/
/**
 * Implements the CFML Function dateadd
 */
package lucee.runtime.functions.dateTime;

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

import lucee.commons.date.JREDateTimeUtil;
import lucee.runtime.PageContext;
import lucee.runtime.exp.ExpressionException;
import lucee.runtime.ext.function.Function;
import lucee.runtime.type.dt.DateTime;
import lucee.runtime.type.dt.DateTimeImpl;

public final class DateAdd implements Function {

	private static final long serialVersionUID = -5827644560609841341L;

	public static DateTime call(PageContext pc , String datepart, double number, DateTime date) throws ExpressionException {
		return _call(pc,pc.getTimeZone(), datepart, number, date);
	}
	
	public synchronized static DateTime _call(PageContext pc ,TimeZone tz, String datepart, double number, DateTime date) throws ExpressionException {
		datepart=datepart.toLowerCase();
		long l=(long)number;
		int n=(int) l;
		char first=datepart.length()==1?datepart.charAt(0):(char)0;

		if(first=='l') 			return new DateTimeImpl(pc,date.getTime()+l,false);
		else if(first=='s') 	return new DateTimeImpl(pc,date.getTime()+(l*1000),false);
		else if(first=='n')		return new DateTimeImpl(pc,date.getTime()+(l*60000),false);
		else if(first=='h')		return new DateTimeImpl(pc,date.getTime()+(l*3600000),false);
		
		
		Calendar c=JREDateTimeUtil.getThreadCalendar();
		//if (c == null)c=JREDateTimeUtil.newInstance();
        //synchronized (c) {
        	//c.clear();
        	c.setTimeZone(tz);
        	c.setTimeInMillis(date.getTime());
			
			if(datepart.equals("yyyy")) {
				c.set(Calendar.YEAR,c.get(Calendar.YEAR)+n);
			}
			else if(datepart.equals("ww")) c.add(Calendar.WEEK_OF_YEAR,n);
			else if(first=='q') c.add(Calendar.MONTH,(n*3));
			else if(first=='m') c.add(Calendar.MONTH,n);
			else if(first=='y') c.add(Calendar.DAY_OF_YEAR,n);
			else if(first=='d') c.add(Calendar.DATE,n);
			else if(first=='w') {
				int dow = c.get(Calendar.DAY_OF_WEEK);
	            int offset;
	            // -
	            if(n < 0) {
	                if(Calendar.SUNDAY==dow) offset=2;
	                else offset=-(6-dow);
	            } 
	            // +
	            else {
	                if(Calendar.SATURDAY==dow) offset=-2;
	                else offset=dow-2;
	            }
	            c.add(Calendar.DAY_OF_WEEK, -offset);
	            
	            if(dow==Calendar.SATURDAY || dow==Calendar.SUNDAY) {
	                if(n>0) n--;
	                else if(n<0) n++;
	            }
	            else n+=offset;
	            c.add(Calendar.DAY_OF_WEEK, (n / 5) * 7 + n % 5);
	            
			}
			
			else {
				throw new ExpressionException("invalid datepart identifier ["+datepart+"] for function dateAdd");
			}
			return new DateTimeImpl(pc,c.getTimeInMillis(),false);
        //}
	}
}
@@ -1017,24 +1017,147 @@
 nException;%0A
+import lucee.runtime.exp.FunctionException;%0Aimport lucee.runtime.exp.PageException;%0Aimport lucee.runtime.ext.function.BIF;%0A
 import lucee
@@ -1184,24 +1184,56 @@
 n.Function;%0A
+import lucee.runtime.op.Caster;%0A
 import lucee
@@ -1334,30 +1334,74 @@
 Add 
-implements Function %7B%0A
+extends BIF %7B%0A// do not change this is used in the chart extension
 %0A%09pr
@@ -1652,16 +1652,312 @@
 ate);%0A%09%7D
+%0A%0A%09@Override%0A%09public Object invoke(PageContext pc, Object%5B%5D args) throws PageException %7B%0A%09%09if(args.length!=3) throw new FunctionException(pc, %22DateAdd%22, 3, 3, args.length);%0A%09%09return call(pc, Caster.toString(args%5B0%5D),  Caster.toDoubleValue(args%5B1%5D),  Caster.toDate(args%5B2%5D,pc.getTimeZone()));%0A%09%7D%0A%09
 %0A%09%0A%09publ

add BIF interface to DateAdd function