package edu.utexas.cs.utopia.cortado.expression.ast.bool;

import edu.utexas.cs.utopia.cortado.expression.ast.Expr;
import edu.utexas.cs.utopia.cortado.expression.visitors.astvisitor.ExprRetVisitor;
import edu.utexas.cs.utopia.cortado.expression.visitors.astvisitor.ExprVisitor;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class AndExpr extends BoolExpr implements Iterable<Expr>
{
    private final Set<Expr> args = new HashSet<>();

    private final Expr[] argsAsArray;

    public AndExpr(Expr... args)
    {
        if (args.length < 2)
            throw new IllegalArgumentException("Operator and needs at least two arguments.");

        if (!Stream.of(args)
                   .filter(a -> a.getType() != BOOL_TYPE)
                   .collect(Collectors.toSet())
                   .isEmpty())
            throw new IllegalArgumentException("Non boolean argument for operator and.");

        Collections.addAll(this.args, args);
        this.argsAsArray = this.args.toArray(new Expr[0]);
    }

    public int argNum()
    {
        return args.size();
    }

    public Expr argAt(int i)
    {
        return argsAsArray[i];
    }

    @Override
    public ExprKind getKind()
    {
        return ExprKind.AND;
    }

    @Override
    public Iterator<Expr> iterator()
    {
        return args.iterator();
    }

    @Override
    public void prettyPrint(StringBuilder b, String indent)
    {
        String opStr = "(and ";
        String indentIncr = "     ";
        b.append(opStr);

        prettyPrintExprWithArgs(b, indent + indentIncr, argsAsArray);
    }

    @Override
    public void accept(ExprVisitor v)
    {
        v.visit(this);
    }

    @Override
    public <R> R accept(ExprRetVisitor<R> v)
    {
        return v.visit(this);
    }

    @Override
    public boolean equals(Object o)
    {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        AndExpr exprs = (AndExpr) o;
        return args.equals(exprs.args);
    }

    @Override
    public int hashCode()
    {
        return memoizedHashCode != 0 ? memoizedHashCode : (memoizedHashCode = Objects.hash(args, this.getClass()));
    }
}
