package edu.caltech.nanodb.functions;


import java.util.HashSet;
import java.util.List;

import edu.caltech.nanodb.expressions.ArithmeticOperator;
import edu.caltech.nanodb.expressions.Expression;

import edu.caltech.nanodb.relations.ColumnType;
import edu.caltech.nanodb.relations.Schema;


/**
 * This aggregate function can be used to compute both SUM and AVERAGE
 * functions. It computes both the sum and average of a collection of values.
 */
public class SumAvgAggregate extends AggregateFunction {

    /**
     * This value is set to true if we want to compute the average value.
     * Otherwise, we compute the sum.
     */
    private boolean computeAverage;


    /** Contains the actual value of the sum */
    private Object sum;


    /** The current count of items that have been added */
    private int count;


    /** Indicates whether we want distinct values or not */
    private boolean distinct;


    /** Set to keep track of distinct values */
    HashSet<Object> set;


    public SumAvgAggregate(boolean computeAverage, boolean distinct) {
        super(/* supportsDistinct */ true);
        this.computeAverage = computeAverage;
        this.distinct = distinct;
        if (distinct)
            set = new HashSet<Object>();
    }


    @Override
    public void clearResult() {
        sum = null;
        count = 0;
        if (distinct)
            set.clear();
    }


    @Override
    public void addValue(Object value) {
        if (value == null)
            return;

        // If we are doing distinct, then only add the values if it has not
        // yet appeared
        if (distinct && set.contains(value))
            return;
        else if (distinct)
            set.add(value);

        if (sum == null) {
            // This is the first value.  Store it.
            sum = value;
        }
        else {
            // Add in the new value.
            sum = ArithmeticOperator.evalObjects(ArithmeticOperator.Type.ADD,
                sum, value);
        }

        if (computeAverage)
            count++;
    }


    @Override
    public Object getResult() {
        if (sum == null) {
            return null;
        }
        else if (computeAverage) {
            // TODO:  Need to generate NUMERIC result.  Using double right now.
            // Compute average from the sum and count.
            return ArithmeticOperator.evalObjects(
                ArithmeticOperator.Type.DIVIDE, sum, (double) count);
        }
        else {
            // Just return the sum.
            return sum;
        }
    }


    @Override
    public ColumnType getReturnType(List<Expression> args, Schema schema) {
        if (args.size() != 1) {
            throw new IllegalArgumentException(
                "Sum/average aggregate function takes 1 argument; got " +
                args.size());
        }

        // When finding the min or max, the resulting aggregate column is the
        // same type as the values of the column.
        return args.get(0).getColumnInfo(schema).getType();
    }
}