package org.logicng.functions;

import static org.logicng.formulas.cache.FunctionCacheEntry.NUMBER_OF_ATOMS;

import org.logicng.formulas.BinaryOperator;
import org.logicng.formulas.Formula;
import org.logicng.formulas.FormulaFunction;
import org.logicng.formulas.NAryOperator;
import org.logicng.formulas.Not;

/**
 * A function that computes the number of atoms occurring in a given formula.
 * @version 2.2.0
 * @since 2.2.0
 */
public class NumberOfAtomsFunction implements FormulaFunction<Long> {

    private final static NumberOfAtomsFunction INSTANCE = new NumberOfAtomsFunction();

    /**
     * Private empty constructor.  Singleton class.
     */
    private NumberOfAtomsFunction() {
        // Intentionally left empty
    }

    /**
     * Returns the singleton of the function.
     * @return the function instance
     */
    public static NumberOfAtomsFunction get() {
        return INSTANCE;
    }

    @Override
    public Long apply(final Formula formula, final boolean cache) {
        final Object cached = formula.functionCacheEntry(NUMBER_OF_ATOMS);
        if (cached != null) {
            return (Long) cached;
        }
        long result = 0L;
        switch (formula.type()) {
            case FALSE:
            case TRUE:
            case LITERAL:
            case PBC:
                result = 1L;
                break;
            case NOT:
                result = apply(((Not) formula).operand(), cache);
                break;
            case IMPL:
            case EQUIV:
                final BinaryOperator binary = (BinaryOperator) formula;
                result = apply(binary.left(), cache) + apply(binary.right(), cache);
                break;
            case OR:
            case AND:
                final NAryOperator nary = (NAryOperator) formula;
                for (final Formula op : nary) {
                    result += apply(op, cache);
                }
                break;
            default:
                throw new IllegalStateException("Unknown formula type " + formula.type());
        }
        if (cache) {
            formula.setFunctionCacheEntry(NUMBER_OF_ATOMS, result);
        }
        return result;
    }
}
