/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.sis.filter;

import java.util.List;
import java.util.Collection;
import java.util.Objects;
import java.util.Optional;
import org.apache.sis.xml.NilReason;
import org.apache.sis.filter.internal.Node;

// Specific to the main branch:
import org.apache.sis.pending.geoapi.filter.ComparisonOperatorName;


/**
 * Base class for expressions, comparators or filters performing operations on one expressions.
 * The nature of the operation depends on the subclass.
 *
 * @author  Johann Sorel (Geomatys)
 * @author  Martin Desruisseaux (Geomatys)
 *
 * @param  <R>  the type of resources (e.g. {@code Feature}) used as inputs.
 * @param  <V>  the type of value computed by the expression.
 */
class UnaryFunction<R,V> extends Node {
    /**
     * For cross-version compatibility.
     */
    private static final long serialVersionUID = 2020526901451551162L;

    /**
     * The expression to be used by this operator.
     *
     * @see #getExpression()
     */
    @SuppressWarnings("serial")         // Most SIS implementations are serializable.
    protected final Expression<R, ? extends V> expression;

    /**
     * Creates a new unary operator.
     */
    UnaryFunction(final Expression<R, ? extends V> expression) {
        this.expression = Objects.requireNonNull(expression);
    }

    /**
     * Returns the class of resources expected by this filter.
     * Defined for {@link Filter#getResourceClass()} implementations.
     *
     * @return type of resources accepted by this filter, or {@code null} if inconsistent.
     */
    public final Class<? super R> getResourceClass() {
        return expression.getResourceClass();
    }

    /**
     * Returns the expression used as parameter by this function.
     * Defined for {@link Expression#getParameters()} implementations.
     */
    public final List<Expression<R,?>> getParameters() {
        return getExpressions();
    }

    /**
     * Returns the expression used as parameter by this filter.
     * Defined for {@link Filter#getExpressions()} implementations.
     *
     * @return a list of size 1 containing the singleton expression.
     */
    public final List<Expression<R,?>> getExpressions() {
        return List.of(expression);
    }

    /**
     * Returns the expression used by this operator possibly completed in subclasses with other parameters.
     * This is used for {@link #toString()}, {@link #hashCode()} and {@link #equals(Object)} implementations.
     */
    @Override
    protected Collection<?> getChildren() {
        return getExpressions();
    }


    /**
     * Filter operator that checks if an expression's value is {@code null}.  A {@code null}
     * is equivalent to no value present. The value 0 is a valid value and is not considered
     * {@code null}.
     *
     * @param  <R>  the type of resources used as inputs.
     */
    static final class IsNull<R> extends UnaryFunction<R,Object>
            implements Filter<R>, Optimization.OnFilter<R>
 {
        /** For cross-version compatibility. */
        private static final long serialVersionUID = 2960285515924533419L;

        /** Creates a new operator. */
        IsNull(final Expression<R,?> expression) {
            super(expression);
        }

        @Override public ComparisonOperatorName getOperatorType() {
            return ComparisonOperatorName.PROPERTY_IS_NULL;
        }

        /** Creates a new filter of the same type but different parameters. */
        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
            return new IsNull<>(effective[0]);
        }

        /** Identification of the operation. */
        @Override protected char symbol() {
            return '∅';
        }

        /** Evaluate this filter on the given object. */
        @Override public boolean test(final R object) {
            return expression.apply(object) == null;
        }
    }


    /**
     * Filter operator that checks if an expression's value is nil.
     * The difference with {@link IsNull} is that a value should exist but
     * cannot be provided for the reason given by {@link #getNilReason()}.
     *
     * @param  <R>  the type of resources used as inputs.
     */
    static final class IsNil<R> extends UnaryFunction<R,Object>
            implements Filter<R>, Optimization.OnFilter<R>
    {
        /** For cross-version compatibility. */
        private static final long serialVersionUID = -7540765433296725888L;

        /** The reason why the value is nil, or {@code null} for accepting any reason. */
        private final String nilReason;

        /** Creates a new operator. */
        IsNil(final Expression<R,?> expression, final String nilReason) {
            super(expression);
            this.nilReason = nilReason;
        }

        @Override public ComparisonOperatorName getOperatorType() {
            return ComparisonOperatorName.PROPERTY_IS_NIL;
        }

        /** Creates a new filter of the same type but different parameters. */
        @Override public Filter<R> recreate(final Expression<R,?>[] effective) {
            return new IsNil<>(effective[0], nilReason);
        }

        /** Returns the reason why the value is nil. */
        public Optional<String> getNilReason() {
            return Optional.ofNullable(nilReason);
        }

        /** Evaluate this filter on the given object. */
        @Override public boolean test(final R object) {
            final NilReason value = NilReason.forObject(expression.apply(object));
            if (value     == null) return false;
            if (nilReason == null) return true;
            final String explanation = value.equals(NilReason.OTHER) ? value.getOtherExplanation() : value.toString();
            return nilReason.equalsIgnoreCase(explanation);
        }
    }
}
