/**
 * 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.cxf.jaxrs.openapi;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

import javax.validation.constraints.DecimalMax;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.Max;
import javax.validation.constraints.Min;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;
import javax.ws.rs.Consumes;
import javax.ws.rs.MatrixParam;

import com.fasterxml.jackson.annotation.JsonView;

import io.swagger.v3.core.converter.ModelConverters;
import io.swagger.v3.core.converter.ResolvedSchema;
import io.swagger.v3.jaxrs2.ResolvedParameter;
import io.swagger.v3.jaxrs2.ext.AbstractOpenAPIExtension;
import io.swagger.v3.jaxrs2.ext.OpenAPIExtension;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.PathParameter;

/**
 * Adds matrix parameters support.
 */
public class JaxRs2Extension extends AbstractOpenAPIExtension {

    @Override
    public ResolvedParameter extractParameters(
            final List<Annotation> annotations,
            final Type type,
            final Set<Type> typesToSkip,
            final Components components,
            final Consumes classConsumes,
            final Consumes methodConsumes,
            final boolean includeRequestBody,
            final JsonView jsonViewAnnotation,
            final Iterator<OpenAPIExtension> chain) {

        if (shouldIgnoreType(type, typesToSkip)) {
            return new ResolvedParameter();
        }

        List<Parameter> parameters = annotations.stream().
                filter(annotation -> annotation instanceof MatrixParam).
                map(annotation -> {
                    MatrixParam param = (MatrixParam) annotation;
                    Parameter mp = new PathParameter().name(param.value());
                    mp.setStyle(Parameter.StyleEnum.MATRIX);

                    ResolvedSchema resolvedSchema = ModelConverters.getInstance().readAllAsResolvedSchema(type);
                    if (resolvedSchema != null) {
                        mp.setSchema(resolvedSchema.schema);
                    }
                    applyBeanValidatorAnnotations(mp, annotations);

                    return mp;
                }).collect(Collectors.toList());

        // Only call down to the other items in the chain if no parameters were produced
        if (parameters.isEmpty()) {
            return super.extractParameters(
                    annotations, type, typesToSkip, components, classConsumes,
                    methodConsumes, includeRequestBody, jsonViewAnnotation, chain);
        }

        ResolvedParameter resolved = new ResolvedParameter();
        resolved.parameters = parameters;
        return resolved;
    }

    /**
     * This is mostly a duplicate of {@link io.swagger.v3.core.jackson.ModelResolver#applyBeanValidatorAnnotations}.
     *
     * @param parameter
     * @param annotations
     */
    private void applyBeanValidatorAnnotations(final Parameter parameter, final List<Annotation> annotations) {
        Map<String, Annotation> annos = new HashMap<>();
        if (annotations != null) {
            annotations.forEach(annotation -> {
                annos.put(annotation.annotationType().getName(), annotation);
            });
        }

        if (annos.containsKey(NotNull.class.getName())) {
            parameter.setRequired(true);
        }

        Schema<?> schema = parameter.getSchema();

        if (annos.containsKey(Min.class.getName())) {
            Min min = (Min) annos.get(Min.class.getName());
            schema.setMinimum(BigDecimal.valueOf(min.value()));
        }
        if (annos.containsKey(Max.class.getName())) {
            Max max = (Max) annos.get(Max.class.getName());
            schema.setMaximum(BigDecimal.valueOf(max.value()));
        }
        if (annos.containsKey(Size.class.getName())) {
            Size size = (Size) annos.get(Size.class.getName());

            schema.setMinimum(BigDecimal.valueOf(size.min()));
            schema.setMaximum(BigDecimal.valueOf(size.max()));

            schema.setMinItems(size.min());
            schema.setMaxItems(size.max());
        }
        if (annos.containsKey(DecimalMin.class.getName())) {
            DecimalMin min = (DecimalMin) annos.get(DecimalMin.class.getName());
            if (min.inclusive()) {
                schema.setMinimum(BigDecimal.valueOf(Double.valueOf(min.value())));
            } else {
                schema.setExclusiveMinimum(!min.inclusive());
            }
        }
        if (annos.containsKey(DecimalMax.class.getName())) {
            DecimalMax max = (DecimalMax) annos.get(DecimalMax.class.getName());
            if (max.inclusive()) {
                schema.setMaximum(BigDecimal.valueOf(Double.valueOf(max.value())));
            } else {
                schema.setExclusiveMaximum(!max.inclusive());
            }
        }
        if (annos.containsKey(Pattern.class.getName())) {
            Pattern pattern = (Pattern) annos.get(Pattern.class.getName());
            schema.setPattern(pattern.regexp());
        }
    }

}
