package com.tibbo.aggregate.common.filter;

import java.math.BigDecimal;
import java.text.ParseException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

import com.tibbo.aggregate.common.datatable.field.DateFieldFormat;
import com.tibbo.aggregate.common.util.DateUtils;

public class InFunctionOperation extends ColumnFunctionOperation {

    public InFunctionOperation(Expression[] operands) {
        super(FilterFunctions.IN.getName(), operands);
        if (operands.length < 2) {
            throw new IllegalArgumentException("included in list is expected");
        }
    }

    @Override
    public Object evaluate() {
        Object value1 = operands[0].evaluate();
        if (value1 == null) {
            return null;
        }
        List<Object> value2 = Arrays.stream(operands).skip(1).map(Expression::evaluate).collect(Collectors.toList());

        if (!(value1 instanceof Number) && !(value1 instanceof String) && !(value1 instanceof Date)) {
            throw new SmartFilterIllegalOperandException(this, Number.class, String.class, Date.class);
        }

        if (value2.stream()
                .filter(v -> !(v instanceof Number))
                .filter(v -> !(v instanceof String)).anyMatch(v -> !(v instanceof Date))) {

            throw new SmartFilterIllegalOperandException(this, Number.class, String.class, Date.class);
        }

        Object operand = castAndNormalizeOperand(value1, value2);

        if (operand instanceof Number)
        {
            return value2.stream().anyMatch(o -> ((BigDecimal) o).compareTo(new BigDecimal(operand.toString())) == 0);
        }

        return value2.stream().anyMatch(o -> o.equals(operand));
    }

    private Object castAndNormalizeOperand(Object operand, List<Object> listOfValues) {
        if (operand instanceof Number) {
            // cast
            for (int i = 0; i < listOfValues.size(); i++) {
                Object lv = listOfValues.get(i);
                if (lv instanceof Number) {
                    listOfValues.set(i, new BigDecimal(lv.toString()));
                }
            }
        } else if (operand instanceof Date) {
            String editor = ((ColumnName) operands[0]).getFieldFormat().getEditor();
            if (DateFieldFormat.EDITOR_DATE.equals(editor)) {
                LocalDate ld = ((Date) operand).toInstant()
                        .atZone(ZoneId.systemDefault())
                        .toLocalDate();
                operand = Date.from(ld.atStartOfDay(ZoneId.systemDefault()).toInstant());
            }

            List<Date> normalized = new ArrayList<>(listOfValues.size());
            try {
                for (Object v : listOfValues) {
                    Date parsed = DateUtils.parseSmart(v.toString());
                    normalized.add(parsed);
                }

            } catch (ParseException pe) {
                throw new SmartFilterIllegalOperandException(this, "Operand: \"" + listOfValues + "\" not a valid Date");
            }

            listOfValues.clear();
            listOfValues.addAll(normalized);
        }

        return operand;
    }
}
