/*
 * 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.camel.component.rest.openapi;

import java.net.URI;
import java.util.Map;

import org.apache.camel.CamelContext;
import org.apache.camel.Endpoint;
import org.apache.camel.SSLContextParametersAware;
import org.apache.camel.spi.Metadata;
import org.apache.camel.spi.RestProducerFactory;
import org.apache.camel.spi.annotations.Component;
import org.apache.camel.support.DefaultComponent;
import org.apache.camel.support.jsse.SSLContextParameters;

import static org.apache.camel.component.rest.openapi.RestOpenApiHelper.isHostParam;
import static org.apache.camel.component.rest.openapi.RestOpenApiHelper.isMediaRange;
import static org.apache.camel.util.ObjectHelper.notNull;
import static org.apache.camel.util.StringHelper.notEmpty;

/**
 * An awesome REST component backed by OpenApi specifications. Creates endpoints that connect to REST APIs defined by
 * OpenApi specification. This component delegates to other {@link RestProducerFactory} components to act as REST
 * clients, but it configures them from OpenApi specification. Client needs to point to operation that it wants to
 * invoke via REST, provide any additional HTTP headers as headers in the Camel message, and any payload as the body of
 * the incoming message.
 * <p>
 * Example usage using Java DSL:
 * <p>
 *
 * <pre>
 * from(...).to("rest-openapi:https://petstore3.swagger.io/api/v3/openapi.json#getPetById")
 * </pre>
 *
 * This relies on only one {@link RestProducerFactory} component being available to Camel, you can use specific, for
 * instance preconfigured component by using the {@code componentName} endpoint property. For example using Undertow
 * component in Java DSL:
 * <p>
 *
 * <pre>
 * Component undertow = new UndertowComponent();
 * undertow.setSslContextParameters(...);
 * //...
 * camelContext.addComponent("myUndertow", undertow);
 *
 * from(...).to("rest-openapi:https://petstore3.swagger.io/api/v3/openapi.json#getPetById?componentName=myUndertow")
 * </pre>
 *
 * The most concise way of using this component would be to define it in the Camel context under a meaningful name, for
 * example:
 *
 * <pre>
 * Component petstore = new RestOpenApiComponent();
 * petstore.setSpecificationUri("https://petstore3.swagger.io/api/v3/openapi.json");
 * petstore.setComponentName("undertow");
 * //...
 * camelContext.addComponent("petstore", petstore);
 *
 * from(...).to("petstore:getPetById")
 * </pre>
 */
@Component("rest-openapi")
public final class RestOpenApiComponent extends DefaultComponent implements SSLContextParametersAware {
    public static final String DEFAULT_BASE_PATH = "/";

    static final URI DEFAULT_SPECIFICATION_URI = URI.create(RestOpenApiComponent.DEFAULT_SPECIFICATION_URI_STR);

    static final String DEFAULT_SPECIFICATION_URI_STR = "openapi.json";

    @Metadata(
              description = "API basePath, for example \"`/v2`\". Default is unset, if set overrides the value present in OpenApi specification.",
              defaultValue = "", label = "producer")
    private String basePath = "";

    @Metadata(description = "Name of the Camel component that will perform the requests. The component must be present"
                            + " in Camel registry and it must implement RestProducerFactory service provider interface. If not set"
                            + " CLASSPATH is searched for single component that implements RestProducerFactory SPI. Can be overridden in"
                            + " endpoint configuration.",
              label = "producer", required = false)
    private String componentName;

    @Metadata(
              description = "What payload type this component capable of consuming. Could be one type, like `application/json`"
                            + " or multiple types as `application/json, application/xml; q=0.5` according to the RFC7231. This equates"
                            + " to the value of `Accept` HTTP header. If set overrides any value found in the OpenApi specification."
                            + " Can be overridden in endpoint configuration",
              label = "producer")
    private String consumes;

    @Metadata(description = "Scheme hostname and port to direct the HTTP requests to in the form of"
                            + " `http[s]://hostname[:port]`. Can be configured at the endpoint, component or in the corresponding"
                            + " REST configuration in the Camel Context. If you give this component a name (e.g. `petstore`) that"
                            + " REST configuration is consulted first, `rest-openapi` next, and global configuration last. If set"
                            + " overrides any value found in the OpenApi specification, RestConfiguration. Can be overridden in endpoint"
                            + " configuration.",
              label = "producer")
    private String host;

    @Metadata(
              description = "What payload type this component is producing. For example `application/json`"
                            + " according to the RFC7231. This equates to the value of `Content-Type` HTTP header. If set overrides"
                            + " any value present in the OpenApi specification. Can be overridden in endpoint configuration.",
              label = "producer")
    private String produces;

    @Metadata(description = "Path to the OpenApi specification file. The scheme, host base path are taken from this"
                            + " specification, but these can be overridden with properties on the component or endpoint level. If not"
                            + " given the component tries to load `openapi.json` resource. Note that the `host` defined on the"
                            + " component and endpoint of this Component should contain the scheme, hostname and optionally the"
                            + " port in the URI syntax (i.e. `https://api.example.com:8080`). Can be overridden in endpoint"
                            + " configuration.",
              defaultValue = DEFAULT_SPECIFICATION_URI_STR, label = "producer")
    private URI specificationUri;

    @Metadata(description = "Customize TLS parameters used by the component. If not set defaults to the TLS parameters"
                            + " set in the Camel context ",
              label = "security")
    private SSLContextParameters sslContextParameters;

    @Metadata(description = "Enable usage of global SSL context parameters.", label = "security",
              defaultValue = "false")
    private boolean useGlobalSslContextParameters;

    public RestOpenApiComponent() {
    }

    public RestOpenApiComponent(final CamelContext context) {
        super(context);
    }

    @Override
    protected Endpoint createEndpoint(final String uri, final String remaining, final Map<String, Object> parameters)
            throws Exception {
        Endpoint endpoint = new RestOpenApiEndpoint(uri, remaining, this, parameters);
        setProperties(endpoint, parameters);
        return endpoint;
    }

    public String getBasePath() {
        return basePath;
    }

    public String getComponentName() {
        return componentName;
    }

    public String getConsumes() {
        return consumes;
    }

    public String getHost() {
        return host;
    }

    public String getProduces() {
        return produces;
    }

    public URI getSpecificationUri() {
        return specificationUri;
    }

    public SSLContextParameters getSslContextParameters() {
        return sslContextParameters;
    }

    @Override
    public boolean isUseGlobalSslContextParameters() {
        return useGlobalSslContextParameters;
    }

    public void setBasePath(final String basePath) {
        this.basePath = notEmpty(basePath, "basePath");
    }

    public void setComponentName(final String componentName) {
        this.componentName = notEmpty(componentName, "componentName");
    }

    public void setConsumes(final String consumes) {
        this.consumes = isMediaRange(consumes, "consumes");
    }

    public void setHost(final String host) {
        this.host = isHostParam(host);
    }

    public void setProduces(final String produces) {
        this.produces = isMediaRange(produces, "produces");
    }

    public void setSpecificationUri(final URI specificationUri) {
        this.specificationUri = notNull(specificationUri, "specificationUri");
    }

    public void setSslContextParameters(final SSLContextParameters sslContextParameters) {
        this.sslContextParameters = sslContextParameters;
    }

    @Override
    public void setUseGlobalSslContextParameters(final boolean useGlobalSslContextParameters) {
        this.useGlobalSslContextParameters = useGlobalSslContextParameters;
    }

}
