/*
 * 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.nifi.hl7.query.evaluator.comparison;

import org.apache.nifi.hl7.model.HL7Field;
import org.apache.nifi.hl7.query.evaluator.BooleanEvaluator;
import org.apache.nifi.hl7.query.evaluator.Evaluator;

import java.util.Collection;
import java.util.Map;

public abstract class AbstractComparisonEvaluator extends BooleanEvaluator {

    private final Evaluator<?> lhs;
    private final Evaluator<?> rhs;

    public AbstractComparisonEvaluator(final Evaluator<?> lhs, final Evaluator<?> rhs) {
        this.lhs = lhs;
        this.rhs = rhs;
    }

    @Override
    public final Boolean evaluate(final Map<String, Object> objectMap) {
        final Object lhsValue = lhs.evaluate(objectMap);
        if (lhsValue == null) {
            return false;
        }

        final Object rhsValue = rhs.evaluate(objectMap);
        if (rhsValue == null) {
            return false;
        }

        return compareRaw(lhsValue, rhsValue);
    }

    private Boolean compareRaw(Object lhsValue, Object rhsValue) {
        if (lhsValue == null || rhsValue == null) {
            return false;
        }

        if (lhsValue instanceof HL7Field) {
            lhsValue = ((HL7Field) lhsValue).getValue();
        }

        if (rhsValue instanceof HL7Field) {
            rhsValue = ((HL7Field) rhsValue).getValue();
        }

        if (lhsValue == null || rhsValue == null) {
            return false;
        }

        /*
         * both are collections, and compare(lhsValue, rhsValue) is false.
         * this would be the case, for instance, if we compared field 1 of one segment to
         * a field in another segment, and both fields had components.
         */
        if (lhsValue instanceof Collection && rhsValue instanceof Collection) {
            return false;
        }

        /*
         * if one side is a collection but the other is not, check if any element in that
         * collection compares to the other element in a way that satisfies the condition.
         * this would happen, for instance, if we check Segment1.Field5 = 'X' and field 5 repeats
         * with a value "A~B~C~X~Y~Z"; in this case we do want to consider Field 5 = X as true.
         */
        if (lhsValue instanceof Collection) {
            for (final Object lhsObject : (Collection<?>) lhsValue) {
                if (compareRaw(lhsObject, rhsValue)) {
                    return true;
                }
            }

            return false;
        }

        if (rhsValue instanceof Collection) {
            for (final Object rhsObject : (Collection<?>) rhsValue) {
                if (compareRaw(rhsObject, lhsValue)) {
                    return true;
                }
            }

            return false;
        }

        if (lhsValue != null && rhsValue != null && compare(lhsValue, rhsValue)) {
            return true;
        }

        return false;
    }

    protected abstract boolean compare(Object lhs, Object rhs);
}
