package com.bigdata.rdf.internal.constraints;

import javax.xml.datatype.DatatypeFactory;
import javax.xml.datatype.Duration;
import javax.xml.datatype.XMLGregorianCalendar;

import org.openrdf.model.Literal;
import org.openrdf.model.URI;
import org.openrdf.model.datatypes.XMLDatatypeUtil;
import org.openrdf.model.vocabulary.XMLSchema;

import com.bigdata.rdf.error.SparqlTypeErrorException;
import com.bigdata.rdf.internal.IV;
import com.bigdata.rdf.internal.constraints.MathBOp.MathOp;
import com.bigdata.rdf.internal.impl.literal.XSDNumericIV;
import com.bigdata.rdf.model.BigdataLiteral;
import com.bigdata.rdf.model.BigdataValueFactory;
import com.bigdata.rdf.sparql.ast.DummyConstantNode;

public class DateTimeUtility {
    static protected final DatatypeFactory datatypeFactory;
    static {
        try {
            datatypeFactory = DatatypeFactory.newInstance();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    static public boolean checkDateTimeDatatype(final Literal... args) {
    	for (Literal lit : args) {
    		final URI dt = lit.getDatatype();
    		if (dt == null)
    			return false;
    		if (!(XMLDatatypeUtil.isCalendarDatatype(dt) || dt.equals(XMLSchema.DURATION)))
				return false;
    	}
    	return true;
    }
    
    static public IV dateTimeMath(
    		final Literal l1, final IV iv1, 
    		final Literal l2, final IV iv2, 
    		final MathOp op, 
    		final BigdataValueFactory vf) {
    	
        if (!checkDateTimeDatatype(l1, l2))
        	throw new SparqlTypeErrorException();
    	
    	final URI dt1 = l1.getDatatype();
        final URI dt2 = l2.getDatatype();
        XMLGregorianCalendar c1 = XMLDatatypeUtil.isCalendarDatatype(dt1) ? l1.calendarValue() : null;
        XMLGregorianCalendar c2 = XMLDatatypeUtil.isCalendarDatatype(dt2) ? l2.calendarValue() : null;
        Duration d1 = dt1.equals(XMLSchema.DURATION) ? datatypeFactory.newDuration(l1.getLabel()) : null;
        Duration d2 = dt2.equals(XMLSchema.DURATION) ? datatypeFactory.newDuration(l2.getLabel()) : null;
        if (op == MathOp.PLUS) {
            if (c1 != null && c2 != null) {
                throw new IllegalArgumentException("Cannot add 2 calendar literals:" + l1 + ":" + l2);
            } else if (c1 != null && d2 != null) {
                c1.add(d2);
                final BigdataLiteral str = vf.createLiteral(c1);
                return DummyConstantNode.toDummyIV(str);
            } else if (c2 != null && d1 != null) {
                c2.add(d1);
                final BigdataLiteral str = vf.createLiteral(c2);
                return DummyConstantNode.toDummyIV(str);
            } else if (d1 != null && d2 != null) {
                Duration result = d1.add(d2);
                final BigdataLiteral str = vf.createLiteral(result.toString(), XMLSchema.DURATION);
                return DummyConstantNode.toDummyIV(str);
            } else {
                throw new IllegalArgumentException("Cannot add process datatype literals:" + l1 + ":" + l2);
            }
        } else if (op == MathOp.MINUS) {
            if (c1 != null && c2 != null) {
                long milliseconds = c1.toGregorianCalendar().getTimeInMillis() - c2.toGregorianCalendar().getTimeInMillis();
                double days = ((double) milliseconds) / ((double) (1000 * 60 * 60 * 24));
                return new XSDNumericIV(days);
            } else if (d1 != null && d2 != null) {
                Duration result = d1.subtract(d2);
                final BigdataLiteral str = vf.createLiteral(result.toString(), XMLSchema.DURATION);
                return DummyConstantNode.toDummyIV(str);
            } else {
                throw new IllegalArgumentException("Cannot add process datatype literals:" + l1 + ":" + l2);
            }

        } else if (op == MathOp.MIN) {
            if (c1 != null && c2 != null) {
                int comp = c1.compare(c2);
                if (comp <= 0) {
                    return iv1;
                } else {
                    return iv2;
                }
            } else if (d1 != null && d2 != null) {
                int comp= d1.compare(d2);
                if(comp<=0){
                    return iv1;
                }else{
                    return iv2;
                }
            } else {
                throw new IllegalArgumentException("Cannot add process datatype literals:" + l1 + ":" + l2);
            }
        } else if (op == MathOp.MAX) {
            if (c1 != null && c2 != null) {
                int comp = c1.compare(c2);
                if (comp >= 0) {
                    return iv1;
                } else {
                    return iv2;
                }
            } else if (d1 != null && d2 != null) {
               int comp= d1.compare(d2);
               if(comp>=0){
                   return iv1;
               }else{
                   return iv2;
               }
            } else {
                throw new IllegalArgumentException("Cannot add process datatype literals:" + l1 + ":" + l2);
            }
        } else {
            throw new SparqlTypeErrorException();
        }
    }
    
}
