package expression;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;

/**
 * @author Doschennikov Nikita (me@fymio.us)
 */
public abstract class AbstractBinaryOperation extends AbstractExpression {

    protected final AbstractExpression left;
    protected final AbstractExpression right;

    protected AbstractBinaryOperation(
        AbstractExpression left,
        AbstractExpression right
    ) {
        this.left = left;
        this.right = right;
    }

    protected abstract String getOperator();

    protected abstract int getPriority();

    protected abstract boolean isRightAssoc();

    protected abstract int applyInt(int a, int b);

    protected abstract BigInteger applyBi(BigInteger a, BigInteger b);

    protected abstract BigDecimal applyBd(BigDecimal a, BigDecimal b);

    @Override
    public int evaluate(int x) {
        return applyInt(left.evaluate(x), right.evaluate(x));
    }

    @Override
    public int evaluate(int x, int y, int z) {
        return applyInt(left.evaluate(x, y, z), right.evaluate(x, y, z));
    }

    @Override
    public int evaluate(List<Integer> vars) {
        return applyInt(left.evaluate(vars), right.evaluate(vars));
    }

    @Override
    public BigInteger evaluateBi(List<BigInteger> vars) {
        return applyBi(left.evaluateBi(vars), right.evaluateBi(vars));
    }

    @Override
    public BigDecimal evaluateBd(List<BigDecimal> vars) {
        return applyBd(left.evaluateBd(vars), right.evaluateBd(vars));
    }

    @Override
    public String toString() {
        return "(" + left + " " + getOperator() + " " + right + ")";
    }

    @Override
    public String toMiniString() {
        return miniLeft() + " " + getOperator() + " " + miniRight();
    }

    private String miniLeft() {
        if (
            left instanceof AbstractBinaryOperation op &&
            op.getPriority() < getPriority()
        ) {
            return "(" + op.toMiniString() + ")";
        }
        return left.toMiniString();
    }

    private String miniRight() {
        if (right instanceof AbstractBinaryOperation op) {
            boolean samePriority = op.getPriority() == getPriority();
            boolean lowerPriority = op.getPriority() < getPriority();
            boolean needParens =
                lowerPriority ||
                (samePriority && isRightAssoc()) ||
                (samePriority && op.isRightAssoc() && getPriority() > 1) ||
                (samePriority &&
                    !getOperator().equals(op.getOperator()) &&
                    !op.isRightAssoc() &&
                    !isRightAssoc());
            if (needParens) {
                return "(" + op.toMiniString() + ")";
            }
        }
        return right.toMiniString();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        AbstractBinaryOperation other = (AbstractBinaryOperation) obj;
        return left.equals(other.left) && right.equals(other.right);
    }

    @Override
    public int hashCode() {
        return (
            31 * (31 * left.hashCode() + right.hashCode()) +
            getClass().hashCode()
        );
    }
}
