/**
 * Copyright (C) 2011 Akiban Technologies Inc.
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License, version 3,
 * as published by the Free Software Foundation.
 *
 * This program 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 Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see http://www.gnu.org/licenses.
 */

package com.akiban.server.expression.std;

import com.akiban.server.expression.Expression;
import com.akiban.server.expression.ExpressionComposer;
import com.akiban.server.expression.ExpressionEvaluation;
import com.akiban.server.expression.ExpressionType;
import com.akiban.server.service.functions.Scalar;
import com.akiban.server.types.AkType;
import java.util.List;

public class DateTimeArithExpression extends ArithExpression
{
    @Scalar("timediff")
    public static final ExpressionComposer TIMEDIFF_COMPOSER = new InternalComposer(AkType.TIME, AkType.TIME);

    @Scalar("datediff")
    public static final ExpressionComposer DATEDIFF_COMPOSER = new InternalComposer(AkType.DATE, AkType.LONG);

    private static class InternalComposer extends BinaryComposer
    {
        private final AkType topT;
        private final AkType argT;
        public InternalComposer (AkType argT, AkType topT)
        {
            this.topT = topT;
            this.argT = argT;
        }

        @Override
        protected Expression compose(Expression first, Expression second)
        {
            return new DateTimeArithExpression(first, second, topT);
        }

        @Override
        protected ExpressionType composeType(ExpressionType first, ExpressionType second)
        {
            return ExpressionTypes.newType(topT, 0, 0);
        }

        @Override
        public void argumentTypes(List<AkType> argumentTypes)
        {
            // TODO: DATETIME AND TIMESTAMP are the two types
            // that always work for both date/time ariths,
            // we dont know how to specify that in this
            // method yet
            for (int n = 0; n < argumentTypes.size(); ++n)
                argumentTypes.set(n, argT);
        }
    }

    protected static class InnerValueSource extends ArithExpression.InnerValueSource
    {
        private static final double SECS_OF_DAY = 86400;

        public InnerValueSource (ArithOp op, AkType topT)
        {
            super(op, topT);
        }

        /**
         *
         * @return number of DAY between two events
         */
        @Override
        public long getLong ()
        {
            check(AkType.LONG);
            long seconds = rawInterval() / 1000L;

            return seconds < 0 ?
                (long)Math.floor(seconds /SECS_OF_DAY) : (long) Math.ceil(seconds / SECS_OF_DAY);
        }

        /**
         *
         * @return INTERVAL between two events expressed in HOUR:MINUTE:SECOND
         */
        @Override
        public long getTime ()
        {
            check(AkType.TIME);
            long seconds = rawInterval() / 1000L;
            long sign;
            if (seconds < 0)
                seconds *= (sign = -1);
            else
                sign = 1;
            long hours = seconds / 3600;
            long minutes = (seconds - hours * 3600) / 60;
            seconds -= hours * 3600 + minutes * 60;
            return sign * (hours * 10000L + minutes * 100 + seconds);
        }
    }

    /**
     *  topT
     *  means INTERVAL expressed in this type
     *  For example, time minus time => interval
     *  if topT is set to TIME => result is INTERVAL in HOURS, SECONDS, MINUTES
     */
    private final AkType topT;
    protected DateTimeArithExpression (Expression left, Expression right, AkType topT)
    {
        super(left, ArithOps.MINUS, right);
        this.topT = topT;
    }

    @Override
    protected InnerValueSource getValueSource (ArithOp op, AkType topT)
    {
        return new InnerValueSource(op, topT);
    }

    @Override
    public ExpressionEvaluation evaluation ()
    {
        return new InnerEvaluation(op, topT, this, childrenEvaluations());
    }
}

@@ -2413,24 +2413,610 @@
    %7D%0A    %7D%0A%0A
+    protected static final class Calculator%0A    %7B%0A        /**%0A         *%0A         * @param interval: a positive number representing an interval between%0A         *                  two events in millisecs%0A         * @return interval in H,M,S%0A         */%0A        public static long%5B%5D getHMS (long interval)%0A        %7B%0A            long seconds = interval / 1000L;%0A            long hours = seconds / 3600;%0A            long minutes = (seconds - hours * 3600) / 60;%0A            seconds -= (minutes * 60 + hours * 3600);%0A            return new long%5B%5D %7Bhours, minutes, seconds%7D;%0A        %7D%0A    %7D%0A
     protecte
@@ -3845,38 +3845,37 @@
            long 
-second
+milli
 s = rawInterval(
@@ -3875,24 +3875,16 @@
 terval()
- / 1000L
 ;%0A      
@@ -3916,22 +3916,21 @@
     if (
-second
+milli
 s %3C 0)%0A 
@@ -3944,22 +3944,21 @@
         
-second
+milli
 s *= (si
@@ -4032,139 +4032,40 @@
 ng h
-ours = seconds / 3600;%0A            long minutes = (seconds - hours * 3600) / 60;%0A            seconds -= hours * 3600 + minutes * 60
+ms%5B%5D = Calculator.getHMS(millis)
 ;%0A  
@@ -4090,20 +4090,21 @@
 ign * (h
-ours
+ms%5B0%5D
  * 10000
@@ -4107,23 +4107,22 @@
 0000L + 
-minutes
+hms%5B1%5D
  * 100 +
@@ -4122,23 +4122,22 @@
 * 100 + 
-seconds
+hms%5B2%5D
 );%0A     

add static helper class to *translate* interval in millisecs to interval in time