/*
 * Copyright (c) 2016 Network New Technologies Inc.
 *
 * Licensed 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 com.networknt.validator.parameter;

import com.networknt.status.Status;
import com.networknt.validator.SchemaValidator;
import io.swagger.models.parameters.Parameter;
import io.swagger.models.parameters.SerializableParameter;

import java.util.*;

import static java.util.Objects.requireNonNull;

/**
 * A validator for array parameters.
 * <p>
 * This is a special-case validator as it needs to handle single and collection types for validation.
 *
 * @author Steve Hu
 */
public class ArrayParameterValidator extends BaseParameterValidator {

    public static final String ARRAY_PARAMETER_TYPE = "array";

    private final SchemaValidator schemaValidator;

    private enum CollectionFormat {
        CSV(","),
        SSV(" "),
        TSV("\t"),
        PIPES("\\|"),
        MULTI(null);

        final String separator;
        CollectionFormat(String separator) {
            this.separator = separator;
        }

        Collection<String> split(final String value) {
            if (separator == null) {
                return Collections.singleton(value);
            }
            return Arrays.asList(value.split(separator));
        }

        static CollectionFormat from(final SerializableParameter parameter) {
            requireNonNull(parameter, "A parameter is required");
            return valueOf(parameter.getCollectionFormat().toUpperCase());
        }
    }

    public ArrayParameterValidator(final SchemaValidator schemaValidator) {
        this.schemaValidator = schemaValidator == null ? new SchemaValidator() : schemaValidator;
    }

    @Override
    public String supportedParameterType() {
        return ARRAY_PARAMETER_TYPE;
    }

    @Override
    public Status validate(final String value, final Parameter p) {

        if (!supports(p)) {
            return null;
        }

        final SerializableParameter parameter = (SerializableParameter)p;

        if (parameter.getRequired() && (value == null || value.trim().isEmpty())) {
            return new Status("ERR11001", parameter.getName());
        }

        if (value == null || value.trim().isEmpty()) {
            return null;
        }

        return doValidate(value, parameter);
    }

    public Status validate(final Collection<String> values, final Parameter p) {
        if (p == null) {
            return null;
        }

        final SerializableParameter parameter = (SerializableParameter)p;
        if (parameter.getRequired() && (values == null || values.isEmpty())) {
            return new Status("ERR11001", parameter.getName());
        }

        if (values == null) {
            return null;
        }

        if (!parameter.getCollectionFormat().equalsIgnoreCase(CollectionFormat.MULTI.name())) {
            return new Status("ERR11005", p.getName(), parameter.getCollectionFormat(), "multi");
        }

        return doValidate(values, parameter);
    }

    @Override
    protected Status doValidate(final String value, final SerializableParameter parameter) {
        return doValidate(CollectionFormat.from(parameter).split(value), parameter);
    }

    private Status doValidate(final Collection<String> values,
                            final SerializableParameter parameter) {

        if (parameter.getMaxItems() != null && values.size() > parameter.getMaxItems()) {
            return new Status("ERR11006", parameter.getName(), parameter.getMaxItems(), values.size());
        }

        if (parameter.getMinItems() != null && values.size() < parameter.getMinItems()) {
            return new Status("ERR11007", parameter.getName(), parameter.getMinItems(), values.size());
        }

        if (Boolean.TRUE.equals(parameter.isUniqueItems()) &&
                values.stream().distinct().count() != values.size()) {
            return new Status("ERR11008", parameter.getName());
        }

        if (parameter.getEnum() != null && !parameter.getEnum().isEmpty()) {
            final Set<String> enumValues = new HashSet<>(parameter.getEnum());
            Optional<String> value =
                values.stream()
                    .filter(v -> !enumValues.contains(v))
                    .findFirst();
            if(value.isPresent()) {
                return new Status("ERR11009", value.get(), parameter.getName(), parameter.getEnum());
            }
        }

        Optional<Status> optional =
                values.stream()
                .map(v -> schemaValidator.validate(v, parameter.getItems()))
                .filter(s -> s != null)
                .findFirst();
        if(optional.isPresent()) {
            return optional.get();
        } else {
            return null;
        }
    }
}