package de.renew.formalism.function;

import de.renew.expression.Function;
import de.renew.unify.Impossible;
import de.renew.util.Value;

/**
 * Represents a basic unary function for mathematical or logical operations.
 */
public final class BasicUnaryFunction implements Function {
    private static final int ID_LNOT = 1;
    private static final int ID_NOT = 2;
    private static final int ID_NEG = 3;
    private static final int ID_POS = 4;

    /**
     * Represents the logical NOT operation
     */
    public static final BasicUnaryFunction LNOT = new BasicUnaryFunction(ID_LNOT);

    /**
     * Represents the bitwise NOT operation
     */
    public static final BasicUnaryFunction NOT = new BasicUnaryFunction(ID_NOT);

    /**
     * Represents the negation operation (returns the negative value of a numeric operand).
     */
    public static final BasicUnaryFunction NEG = new BasicUnaryFunction(ID_NEG);

    /**
     * Represents the identity operation (returns the value of the numeric operand unchanged).
     */
    public static final BasicUnaryFunction POS = new BasicUnaryFunction(ID_POS);

    /**
     * A unique identifier used to distinguish different functions in the system.
     */
    private final int _funcNum;

    private BasicUnaryFunction(int funcNum) {
        this._funcNum = funcNum;
    }

    @Override
    public Object function(Object param) throws Impossible {
        if (!(param instanceof Value)) {
            throw new Impossible();
        }

        Object obj = ((Value) param).value;

        if (obj instanceof Integer || obj instanceof Character || obj instanceof Short
            || obj instanceof Byte) {
            int arg;
            if (obj instanceof Character) {
                arg = (Character) obj;
            } else {
                arg = ((Number) obj).intValue();
            }

            int result = switch (_funcNum) {
                case ID_NOT -> ~arg;
                case ID_NEG -> -arg;
                case ID_POS -> +arg;
                default -> throw new Impossible();
            };

            return new Value(result);
        } else if (obj instanceof Long) {
            long arg = (Long) obj;

            long result = switch (_funcNum) {
                case ID_NOT -> ~arg;
                case ID_NEG -> -arg;
                case ID_POS -> +arg;
                default -> throw new Impossible();
            };

            return new Value(result);
        } else if (obj instanceof Float) {
            float arg = (Float) obj;

            float result = switch (_funcNum) {
                case ID_NEG -> -arg;
                case ID_POS -> +arg;
                default -> throw new Impossible();
            };

            return new Value(result);
        } else if (obj instanceof Double) {
            double arg = (Double) obj;

            double result = switch (_funcNum) {
                case ID_NEG -> -arg;
                case ID_POS -> +arg;
                default -> throw new Impossible();
            };

            return new Value(result);
        } else if (obj instanceof Boolean) {
            boolean arg = (Boolean) obj;

            if (_funcNum == ID_LNOT) {
                return new Value(!arg);
            }
            throw new Impossible();
        }

        throw new Impossible();
    }
}