package org.basex.query.func;
import static org.basex.query.util.Err.*;
import static org.basex.query.item.AtomType.*;
import org.basex.query.QueryContext;
import org.basex.query.QueryException;
import org.basex.query.expr.Calc;
import org.basex.query.expr.CmpV.Op;
import org.basex.query.expr.Expr;
import org.basex.query.item.Dbl;
import org.basex.query.item.Item;
import org.basex.query.item.Int;
import org.basex.query.item.Type;
import org.basex.query.iter.Iter;
import org.basex.util.InputInfo;
/**
* Aggregating functions.
*
* @author BaseX Team 2005-12, BSD License
* @author Christian Gruen
*/
public final class FNAggr extends StandardFunc {
/**
* Constructor.
* @param ii input info
* @param f function definition
* @param e arguments
*/
public FNAggr(final InputInfo ii, final Function f, final Expr... e) {
super(ii, f, e);
}
@Override
public Item item(final QueryContext ctx, final InputInfo ii)
throws QueryException {
final Iter iter = ctx.iter(expr[0]);
switch(sig) {
case COUNT:
long c = iter.size();
if(c == -1) do ++c; while(iter.next() != null);
return Int.get(c);
case MIN:
return minmax(iter, Op.GT, ctx);
case MAX:
return minmax(iter, Op.LT, ctx);
case SUM:
Item it = iter.next();
return it != null ? sum(iter, it, false) :
expr.length == 2 ? expr[1].item(ctx, input) : Int.get(0);
case AVG:
it = iter.next();
return it == null ? null : sum(iter, it, true);
default:
return super.item(ctx, ii);
}
}
@Override
public Expr cmp(final QueryContext ctx) throws QueryException {
final Expr e = expr[0];
final long c = e.size();
if(c < 0 || e.uses(Use.NDT) || e.uses(Use.CNS)) return this;
switch(sig) {
case COUNT:
return Int.get(c);
case SUM:
return c == 0 ? expr.length == 2 ? expr[1] : Int.get(0) : this;
default:
return this;
}
}
/**
* Sums up the specified item(s).
* @param iter iterator
* @param it first item
* @param avg calculate average
* @return summed up item
* @throws QueryException query exception
*/
private Item sum(final Iter iter, final Item it, final boolean avg)
throws QueryException {
Item res = it.type.isUntyped() ? Dbl.get(it.string(input), input) : it;
Type t = res.type;
if(!t.isNumber() && (!t.isDuration() || t == DUR))
SUMTYPE.thrw(input, this, t);
final boolean n = t.isNumber();
int c = 1;
for(Item i; (i = iter.next()) != null;) {
t = i.type;
final boolean un = t.isUntyped() || t.isNumber();
if(n && !un) FUNNUM.thrw(input, this, t);
if(!n && un) FUNDUR.thrw(input, this, t);
res = Calc.PLUS.ev(input, res, i);
++c;
}
return avg ? Calc.DIV.ev(input, res, Int.get(c)) : res;
}
/**
* Returns a minimum or maximum item.
* @param iter values to be compared
* @param cmp comparator
* @param ctx query context
* @return resulting item
* @throws QueryException query exception
*/
private Item minmax(final Iter iter, final Op cmp,
final QueryContext ctx) throws QueryException {
if(expr.length == 2) checkColl(expr[1], ctx);
Item res = iter.next();
if(res == null) return null;
// check if first item is comparable
cmp.eval(input, res, res);
// strings or dates
if(!res.type.isUntyped() && res.type.isString() || res.type.isDate()) {
for(Item it; (it = iter.next()) != null;) {
if(it.type != res.type) {
FUNCMP.thrw(input, description(), res.type, it.type);
}
if(cmp.eval(input, res, it)) res = it;
}
return res;
}
// durations or numbers
Type t = res.type.isUntyped() ? DBL : res.type;
if(res.type != t) res = t.cast(res, ctx, input);
for(Item it; (it = iter.next()) != null;) {
t = type(res, it);
if(!it.type.isDuration() && Double.isNaN(it.dbl(input)) ||
cmp.eval(input, res, it))
res = it;
if(res.type != t) res = t.cast(res, ctx, input);
}
return res;
}
/**
* Returns the type with the highest precedence.
* @param a input item
* @param b result item
* @return result
* @throws QueryException query exception
*/
private Type type(final Item a, final Item b) throws QueryException {
final Type ta = a.type, tb = b.type;
if(tb.isUntyped()) {
if(!ta.isNumber()) FUNCMP.thrw(input, this, ta, tb);
return DBL;
}
if(ta.isNumber() && !tb.isUntyped() && tb.isString())
FUNCMP.thrw(input, this, ta, tb);
if(ta == tb) return ta;
if(ta == DBL || tb == DBL) return DBL;
if(ta == FLT || tb == FLT) return FLT;
if(ta == DEC || tb == DEC) return DEC;
if(ta == BLN || ta.isNumber() && !tb.isNumber() ||
tb.isNumber() && !ta.isNumber()) FUNCMP.thrw(input, this, ta, tb);
return ta.isNumber() || tb.isNumber() ? ITR : ta;
}
}