package org.basex.query.expr;
import static org.basex.query.item.AtomType.*;
import java.math.BigDecimal;
import org.basex.query.QueryException;
import org.basex.query.item.DTd;
import org.basex.query.item.Dat;
import org.basex.query.item.Date;
import org.basex.query.item.Dbl;
import org.basex.query.item.Dec;
import org.basex.query.item.Dtm;
import org.basex.query.item.Dur;
import org.basex.query.item.Flt;
import org.basex.query.item.Item;
import org.basex.query.item.Int;
import org.basex.query.item.Tim;
import org.basex.query.item.Type;
import org.basex.query.item.YMd;
import org.basex.query.util.Err;
import static org.basex.query.util.Err.*;
import org.basex.util.InputInfo;
/**
* Calculation.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public enum Calc {
/** Addition. */
PLUS("+") {
@Override
public Item ev(final InputInfo ii, final Item a, final Item b)
throws QueryException {
final Type ta = a.type, tb = b.type;
final boolean t1 = ta.isNumber() || ta.isUntyped();
final boolean t2 = tb.isNumber() || tb.isUntyped();
if(t1 ^ t2) errNum(ii, !t1 ? a : b);
if(t1 && t2) {
final Type t = type(ta, tb);
if(t == ITR) {
final long l1 = a.itr(ii);
final long l2 = b.itr(ii);
checkRange(ii, l1 + (double) l2);
return Int.get(l1 + l2);
}
if(t == DBL) return Dbl.get(a.dbl(ii) + b.dbl(ii));
if(t == FLT) return Flt.get(a.flt(ii) + b.flt(ii));
return Dec.get(a.dec(ii).add(b.dec(ii)));
}
if(ta == tb) {
if(!ta.isDuration()) errNum(ii, !t1 ? a : b);
if(ta == YMD) return new YMd((YMd) a, (YMd) b, true);
if(ta == DTD) return new DTd((DTd) a, (DTd) b, true);
}
if(ta == DTM) return new Dtm((Date) a, checkDur(ii, b), true, ii);
if(tb == DTM) return new Dtm((Date) b, checkDur(ii, a), true, ii);
if(ta == DAT) return new Dat((Date) a, checkDur(ii, b), true, ii);
if(tb == DAT) return new Dat((Date) b, checkDur(ii, a), true, ii);
if(ta == TIM) {
if(tb != DTD) errType(ii, DTD, b);
return new Tim((Tim) a, (DTd) b, true, ii);
}
if(tb == TIM) {
if(ta != DTD) errType(ii, DTD, b);
return new Tim((Tim) b, (DTd) a, true, ii);
}
errType(ii, ta, b);
return null;
}
},
/** Subtraction. */
MINUS("-") {
@Override
public Item ev(final InputInfo ii, final Item a, final Item b)
throws QueryException {
final Type ta = a.type, tb = b.type;
final boolean t1 = ta.isNumber() || ta.isUntyped();
final boolean t2 = tb.isNumber() || tb.isUntyped();
if(t1 ^ t2) errNum(ii, !t1 ? a : b);
if(t1 && t2) {
final Type t = type(ta, tb);
if(t == ITR) {
final long l1 = a.itr(ii);
final long l2 = b.itr(ii);
checkRange(ii, l1 - (double) l2);
return Int.get(l1 - l2);
}
if(t == DBL) return Dbl.get(a.dbl(ii) - b.dbl(ii));
if(t == FLT) return Flt.get(a.flt(ii) - b.flt(ii));
return Dec.get(a.dec(ii).subtract(b.dec(ii)));
}
if(ta == tb) {
if(ta == DTM || ta == DAT || ta == TIM)
return new DTd((Date) a, (Date) b);
if(ta == YMD) return new YMd((YMd) a, (YMd) b, false);
if(ta == DTD) return new DTd((DTd) a, (DTd) b, false);
errNum(ii, !t1 ? a : b);
}
if(ta == DTM) return new Dtm((Date) a, checkDur(ii, b), false, ii);
if(ta == DAT) return new Dat((Date) a, checkDur(ii, b), false, ii);
if(ta == TIM) {
if(tb != DTD) errType(ii, DTD, b);
return new Tim((Tim) a, (DTd) b, false, ii);
}
errType(ii, ta, b);
return null;
}
},
/** Multiplication. */
MULT("*") {
@Override
public Item ev(final InputInfo ii, final Item a, final Item b)
throws QueryException {
final Type ta = a.type, tb = b.type;
if(ta == YMD) {
if(!tb.isNumber()) errNum(ii, b);
return new YMd((Dur) a, b.dbl(ii), true, ii);
}
if(tb == YMD) {
if(!ta.isNumber()) errNum(ii, a);
return new YMd((Dur) b, a.dbl(ii), true, ii);
}
if(ta == DTD) {
if(!tb.isNumber()) errNum(ii, b);
return new DTd((Dur) a, b.dbl(ii), true, ii);
}
if(tb == DTD) {
if(!ta.isNumber()) errNum(ii, a);
return new DTd((Dur) b, a.dbl(ii), true, ii);
}
final boolean t1 = ta.isNumber() || ta.isUntyped();
final boolean t2 = tb.isNumber() || tb.isUntyped();
if(t1 ^ t2) errType(ii, ta, b);
if(t1 && t2) {
final Type t = type(ta, tb);
if(t == ITR) {
final long l1 = a.itr(ii);
final long l2 = b.itr(ii);
checkRange(ii, l1 * (double) l2);
return Int.get(l1 * l2);
}
if(t == DBL) return Dbl.get(a.dbl(ii) * b.dbl(ii));
if(t == FLT) return Flt.get(a.flt(ii) * b.flt(ii));
return Dec.get(a.dec(ii).multiply(b.dec(ii)));
}
errNum(ii, !t1 ? a : b);
return null;
}
},
/** Division. */
DIV("div") {
@Override
public Item ev(final InputInfo ii, final Item a, final Item b)
throws QueryException {
final Type ta = a.type, tb = b.type;
if(ta == tb) {
if(ta == YMD) {
final BigDecimal bd = BigDecimal.valueOf(((YMd) b).ymd());
if(bd.equals(BigDecimal.ZERO)) DATEZERO.thrw(ii, info());
return Dec.get(BigDecimal.valueOf(((YMd) a).ymd()).divide(
bd, 20, BigDecimal.ROUND_HALF_EVEN));
}
if(ta == DTD) {
final BigDecimal bd = ((DTd) b).dtd();
if(bd.equals(BigDecimal.ZERO)) DATEZERO.thrw(ii, info());
return Dec.get(((DTd) a).dtd().divide(bd, 20,
BigDecimal.ROUND_HALF_EVEN));
}
}
if(ta == YMD) {
if(!tb.isNumber()) errNum(ii, b);
return new YMd((Dur) a, b.dbl(ii), false, ii);
}
if(ta == DTD) {
if(!tb.isNumber()) errNum(ii, b);
return new DTd((Dur) a, b.dbl(ii), false, ii);
}
checkNum(ii, a, b);
final Type t = type(ta, tb);
if(t == DBL) return Dbl.get(a.dbl(ii) / b.dbl(ii));
if(t == FLT) return Flt.get(a.flt(ii) / b.flt(ii));
final BigDecimal b1 = a.dec(ii);
final BigDecimal b2 = b.dec(ii);
if(b2.signum() == 0) DIVZERO.thrw(ii, a);
final int s = Math.max(18, Math.max(b1.scale(), b2.scale()));
return Dec.get(b1.divide(b2, s, BigDecimal.ROUND_HALF_EVEN));
}
},
/** Integer division. */
IDIV("idiv") {
@Override
public Item ev(final InputInfo ii, final Item a, final Item b)
throws QueryException {
checkNum(ii, a, b);
final double d1 = a.dbl(ii);
final double d2 = b.dbl(ii);
if(d2 == 0) DIVZERO.thrw(ii, a);
final double d = d1 / d2;
if(Double.isNaN(d) || Double.isInfinite(d)) DIVFLOW.thrw(ii, d1, d2);
return Int.get(type(a.type, b.type) == ITR ?
a.itr(ii) / b.itr(ii) : (long) d);
}
},
/** Modulo. */
MOD("mod") {
@Override
public Item ev(final InputInfo ii, final Item a, final Item b)
throws QueryException {
checkNum(ii, a, b);
final Type t = type(a.type, b.type);
if(t == DBL) return Dbl.get(a.dbl(ii) % b.dbl(ii));
if(t == FLT) return Flt.get(a.flt(ii) % b.flt(ii));
if(t == ITR) {
final long b1 = a.itr(ii);
final long b2 = b.itr(ii);
if(b2 == 0) DIVZERO.thrw(ii, a);
return Int.get(b1 % b2);
}
final BigDecimal b1 = a.dec(ii);
final BigDecimal b2 = b.dec(ii);
if(b2.signum() == 0) DIVZERO.thrw(ii, a);
final BigDecimal q = b1.divide(b2, 0, BigDecimal.ROUND_DOWN);
return Dec.get(b1.subtract(q.multiply(b2)));
}
};
/** Name of operation. */
final String name;
/**
* Constructor.
* @param n name
*/
Calc(final String n) {
name = n;
}
/**
* Performs the calculation.
* @param ii input info
* @param a first item
* @param b second item
* @return result type
* @throws QueryException query exception
*/
public abstract Item ev(final InputInfo ii, final Item a, final Item b)
throws QueryException;
/**
* Returns the numeric type with the highest precedence.
* @param a first item type
* @param b second item type
* @return type
*/
static Type type(final Type a, final Type b) {
if(a == DBL || b == DBL || a.isUntyped() || b.isUntyped()) return DBL;
if(a == FLT || b == FLT) return FLT;
if(a == DEC || b == DEC) return DEC;
return ITR;
}
/**
* Returns a type error.
* @param ii input info
* @param t expected type
* @param it item
* @throws QueryException query exception
*/
final void errType(final InputInfo ii, final Type t, final Item it)
throws QueryException {
Err.type(ii, info(), t, it);
}
/**
* Returns a numeric type error.
* @param ii input info
* @param it item
* @throws QueryException query exception
*/
final void errNum(final InputInfo ii, final Item it) throws QueryException {
XPTYPENUM.thrw(ii, info(), it.type);
}
/**
* Returns a duration type error.
* @param ii input info
* @param it item
* @return duration
* @throws QueryException query exception
*/
final Dur checkDur(final InputInfo ii, final Item it) throws QueryException {
final Type ip = it.type;
if(!ip.isDuration()) XPDUR.thrw(ii, info(), ip);
if(ip == DUR) throw SIMPLDUR.thrw(ii, info(), it);
return (Dur) it;
}
/**
* Checks if the specified items are numeric or untyped.
* @param ii input info
* @param a first item
* @param b second item
* @throws QueryException query exception
*/
final void checkNum(final InputInfo ii, final Item a, final Item b)
throws QueryException {
final Type ta = a.type;
final Type tb = b.type;
if(!ta.isUntyped() && !ta.isNumber()) errNum(ii, a);
if(!tb.isUntyped() && !tb.isNumber()) errNum(ii, b);
}
/**
* Checks if the specified value is outside the integer range.
* @param ii input info
* @param d value to be checked
* @throws QueryException query exception
*/
static final void checkRange(final InputInfo ii, final double d)
throws QueryException {
if(d < Long.MIN_VALUE || d > Long.MAX_VALUE) RANGE.thrw(ii, d);
}
/**
* Returns a string representation of the operator.
* @return string
*/
final String info() {
return '\'' + name + "' operator";
}
@Override
public String toString() {
return name;
}
}