/*
 * 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.generator.swagger;

import java.io.IOException;
import java.time.Instant;
import java.util.function.Function;
import java.util.stream.Collector;

import javax.annotation.Generated;
import javax.lang.model.element.Modifier;

import com.squareup.javapoet.AnnotationSpec;
import com.squareup.javapoet.ClassName;
import com.squareup.javapoet.JavaFile;
import com.squareup.javapoet.MethodSpec;
import com.squareup.javapoet.TypeSpec;
import io.swagger.models.Info;
import io.swagger.models.Swagger;
import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.util.ObjectHelper;

import static org.apache.camel.util.StringHelper.notEmpty;

/**
 * Generates Java source code
 */
public abstract class RestDslSourceCodeGenerator<T> extends RestDslGenerator<RestDslSourceCodeGenerator<T>> {
    static final String DEFAULT_CLASS_NAME = "RestDslRoute";

    static final String DEFAULT_PACKAGE_NAME = "rest.dsl.generated";

    private static final String DEFAULT_INDENT = "    ";

    private Function<Swagger, String> classNameGenerator = RestDslSourceCodeGenerator::generateClassName;

    private Instant generated = Instant.now();

    private String indent = DEFAULT_INDENT;

    private Function<Swagger, String> packageNameGenerator = RestDslSourceCodeGenerator::generatePackageName;

    private boolean sourceCodeTimestamps;

    RestDslSourceCodeGenerator(final Swagger swagger) {
        super(swagger);
    }

    public abstract void generate(T destination) throws IOException;

    public RestDslSourceCodeGenerator<T> withClassName(final String className) {
        notEmpty(className, "className");
        this.classNameGenerator = s -> className;

        return this;
    }

    public RestDslSourceCodeGenerator<T> withIndent(final String indent) {
        this.indent = ObjectHelper.notNull(indent, "indent");

        return this;
    }

    public RestDslSourceCodeGenerator<T> withoutSourceCodeTimestamps() {
        sourceCodeTimestamps = false;

        return this;
    }

    public RestDslSourceCodeGenerator<T> withPackageName(final String packageName) {
        notEmpty(packageName, "packageName");
        this.packageNameGenerator = s -> packageName;

        return this;
    }

    public RestDslSourceCodeGenerator<T> withSourceCodeTimestamps() {
        sourceCodeTimestamps = true;

        return this;
    }

    MethodSpec generateConfigureMethod(final Swagger swagger) {
        final MethodSpec.Builder configure = MethodSpec.methodBuilder("configure").addModifiers(Modifier.PUBLIC)
                .returns(void.class).addJavadoc("Defines Apache Camel routes using REST DSL fluent API.\n");

        final MethodBodySourceCodeEmitter emitter = new MethodBodySourceCodeEmitter(configure);

        if (restComponent != null) {
            configure.addCode("\n");
            configure.addCode("restConfiguration().component(\"" + restComponent + "\")");
            if (restContextPath != null) {
                configure.addCode(".contextPath(\"" + restContextPath + "\")");
            }
            if (ObjectHelper.isNotEmpty(apiContextPath)) {
                configure.addCode(".apiContextPath(\"" + apiContextPath + "\")");
            }
            if (clientRequestValidation) {
                configure.addCode(".clientRequestValidation(true)");
            }
            configure.addCode(";\n\n");
        }

        final PathVisitor<MethodSpec> restDslStatement
                = new PathVisitor<>(swagger.getBasePath(), emitter, filter, destinationGenerator());
        swagger.getPaths().forEach(restDslStatement::visit);

        return emitter.result();
    }

    Instant generated() {
        return generated;
    }

    JavaFile generateSourceCode() {
        final MethodSpec methodSpec = generateConfigureMethod(swagger);

        final String classNameToUse = classNameGenerator.apply(swagger);

        final AnnotationSpec.Builder generatedAnnotation = AnnotationSpec.builder(Generated.class).addMember("value",
                "$S", getClass().getName());
        if (sourceCodeTimestamps) {
            generatedAnnotation.addMember("date", "$S", generated());
        }

        TypeSpec.Builder builder = TypeSpec.classBuilder(classNameToUse).superclass(RouteBuilder.class)
                .addModifiers(Modifier.PUBLIC, Modifier.FINAL).addMethod(methodSpec)
                .addAnnotation(generatedAnnotation.build())
                .addJavadoc("Generated from Swagger specification by Camel REST DSL generator.\n");
        if (springComponent) {
            final AnnotationSpec.Builder springAnnotation
                    = AnnotationSpec.builder(ClassName.bestGuess("org.springframework.stereotype.Component"));
            builder.addAnnotation(springAnnotation.build());
        }
        TypeSpec generatedRouteBuilder = builder.build();

        final String packageNameToUse = packageNameGenerator.apply(swagger);

        return JavaFile.builder(packageNameToUse, generatedRouteBuilder).indent(indent).build();
    }

    RestDslSourceCodeGenerator<T> withGeneratedTime(final Instant generated) {
        this.generated = generated;

        return this;
    }

    static String generateClassName(final Swagger swagger) {
        final Info info = swagger.getInfo();
        if (info == null) {
            return DEFAULT_CLASS_NAME;
        }

        final String title = info.getTitle();
        if (title == null) {
            return DEFAULT_CLASS_NAME;
        }

        final String className = title.chars().filter(Character::isJavaIdentifierPart).filter(c -> c < 'z').boxed()
                .collect(Collector.of(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append,
                        StringBuilder::toString));

        if (className.isEmpty() || !Character.isJavaIdentifierStart(className.charAt(0))) {
            return DEFAULT_CLASS_NAME;
        }

        return className;
    }

    static String generatePackageName(final Swagger swagger) {
        final String host = swagger.getHost();

        if (ObjectHelper.isNotEmpty(host)) {
            final StringBuilder packageName = new StringBuilder();

            final String hostWithoutPort = host.replaceFirst(":.*", "");

            if ("localhost".equalsIgnoreCase(hostWithoutPort)) {
                return DEFAULT_PACKAGE_NAME;
            }

            final String[] parts = hostWithoutPort.split("\\.");

            for (int i = parts.length - 1; i >= 0; i--) {
                packageName.append(parts[i]);
                if (i != 0) {
                    packageName.append('.');
                }
            }

            return packageName.toString();
        }

        return DEFAULT_PACKAGE_NAME;
    }
}
