/*
 * 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.tomcat.util.xreflection;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Set;

final class ReflectionLessCodeGenerator {
    private static final String INDENT = "    ";

    static StringBuilder getIndent(int multiplier) {
        StringBuilder indent = new StringBuilder();
        while ((multiplier--) > 0) {
            indent.append(INDENT);
        }
        return indent;
    }

    static void generateCode(File directory, String className, String packageName, Set<SetPropertyClass> baseClasses)
            throws IOException {
        //@formatter:off
        // begin - class
        StringBuilder code = new StringBuilder(AL20_HEADER)
            .append("package ")
            .append(packageName)
            .append(';')
            .append(System.lineSeparator())
            .append(System.lineSeparator())
            .append("final class ")
            .append(className)
            .append(" {")
            .append(System.lineSeparator())
            .append(System.lineSeparator());

        // begin - isEnabled method
        code.append(getIndent(1))
            .append("static boolean isEnabled() {")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("return true;")
            .append(System.lineSeparator())
            .append(getIndent(1))
            .append('}')
            .append(System.lineSeparator())
            .append(System.lineSeparator())
        ;
        // end - isEnabled method

        // begin - getInetAddress method
        code.append(getIndent(1))
            .append("private static java.net.InetAddress getInetAddress(String value) {")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("try {")
            .append(System.lineSeparator())
            .append(getIndent(3))
            .append("return java.net.InetAddress.getByName(value);")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("} catch (java.net.UnknownHostException x) { throw new RuntimeException(x); }")
            .append(System.lineSeparator())
            .append(getIndent(1))
            .append('}')
            .append(System.lineSeparator())
            .append(System.lineSeparator())
            ;
        // end - getInetAddress method

        // begin - getPropertyInternal method
        code.append(getIndent(1))
            .append("static Object getPropertyInternal(Object ")
            .append(SetPropertyClass.OBJECT_VAR_NAME)
            .append(", String ")
            .append(SetPropertyClass.NAME_VAR_NAME)
            .append(") {")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("Class<?> checkThisClass = o.getClass();")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("Object result = null;")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("while (checkThisClass != Object.class && result == null) {")
            .append(System.lineSeparator())
            .append(getIndent(3))
            .append("switch (checkThisClass.getName()) {")
            .append(System.lineSeparator());

        // generate case statements for getPropertyInternal
        generateCaseStatementsForGetPropertyInternal(baseClasses, code);


        code
            .append(getIndent(3))
            .append('}')
            .append(System.lineSeparator())
            .append(getIndent(3))
            .append("checkThisClass = checkThisClass.getSuperclass();")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append('}')
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("return result;")
            .append(System.lineSeparator())
            .append(getIndent(1))
            .append('}')
            .append(System.lineSeparator());
        // end - getPropertyInternal method

        // begin - getPropertyForXXX methods
        generateGetPropertyForMethods(baseClasses, code);
        // end - getPropertyForXXX methods

        // begin - setPropertyInternal method
        code.append(getIndent(1))
            .append("static boolean setPropertyInternal(Object ")
            .append(SetPropertyClass.OBJECT_VAR_NAME)
            .append(", String ")
            .append(SetPropertyClass.NAME_VAR_NAME)
            .append(", String ")
            .append(SetPropertyClass.VALUE_VAR_NAME)
            .append(", boolean ")
            .append(SetPropertyClass.SETP_VAR_NAME)
            .append(") {")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("Class<?> checkThisClass = o.getClass();")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("while (checkThisClass != Object.class) {")
            .append(System.lineSeparator())
            .append(getIndent(3))
            .append("switch (checkThisClass.getName()) {")
            .append(System.lineSeparator());

        // generate case statements for setPropertyInternal
        generateCaseStatementsForSetPropertyInternal(baseClasses, code);


        code
            .append(getIndent(3))
            .append('}')
            .append(System.lineSeparator())
            .append(getIndent(3))
            .append("checkThisClass = checkThisClass.getSuperclass();")
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append('}')
            .append(System.lineSeparator())
            .append(getIndent(2))
            .append("return false;")
            .append(System.lineSeparator())
            .append(getIndent(1))
            .append('}')
            .append(System.lineSeparator());
        // end - setPropertyInternal method

        // begin - setPropertyForXXX methods
        generateSetPropertyForMethods(baseClasses, code);
        // end - setPropertyForXXX methods

        code.append('}')
            .append(System.lineSeparator());
        // end - class
        //@formatter:on
        File destination = new File(directory, className + ".java");
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(destination, false))) {
            writer.write(code.toString());
            writer.flush();
        }

    }

    private static void generateCaseStatementForSetPropertyInternal(SetPropertyClass clazz, StringBuilder code) {
        for (SetPropertyClass child : clazz.getChildren()) {
            generateCaseStatementForSetPropertyInternal(child, code);
        }
        if (!clazz.isAbstract()) {
            code.append(clazz.generateInvocationSetForPropertyCaseStatement(4));
        }
    }

    private static void generateCaseStatementsForSetPropertyInternal(Set<SetPropertyClass> baseClasses,
            StringBuilder code) {
        for (SetPropertyClass clazz : baseClasses) {
            generateCaseStatementForSetPropertyInternal(clazz, code);
        }
    }

    private static void generateSetPropertyForMethod(SetPropertyClass clazz, StringBuilder code) {
        for (SetPropertyClass child : clazz.getChildren()) {
            generateSetPropertyForMethod(child, code);
        }
        code.append(clazz.generateSetPropertyForMethod()).append(System.lineSeparator()).append(System.lineSeparator());
    }

    private static void generateSetPropertyForMethods(Set<SetPropertyClass> baseClasses, StringBuilder code) {
        for (SetPropertyClass clazz : baseClasses) {
            generateSetPropertyForMethod(clazz, code);
        }
    }


    private static void generateCaseStatementForGetPropertyInternal(SetPropertyClass clazz, StringBuilder code) {
        for (SetPropertyClass child : clazz.getChildren()) {
            generateCaseStatementForGetPropertyInternal(child, code);
        }
        if (!clazz.isAbstract()) {
            code.append(clazz.generateInvocationGetForPropertyCaseStatement(4));
        }
    }

    private static void generateCaseStatementsForGetPropertyInternal(Set<SetPropertyClass> baseClasses,
            StringBuilder code) {
        for (SetPropertyClass clazz : baseClasses) {
            generateCaseStatementForGetPropertyInternal(clazz, code);
        }
    }

    private static void generateGetPropertyForMethod(SetPropertyClass clazz, StringBuilder code) {
        for (SetPropertyClass child : clazz.getChildren()) {
            generateGetPropertyForMethod(child, code);
        }
        code.append(clazz.generateGetPropertyForMethod()).append(System.lineSeparator()).append(System.lineSeparator());
    }

    private static void generateGetPropertyForMethods(Set<SetPropertyClass> baseClasses, StringBuilder code) {
        for (SetPropertyClass clazz : baseClasses) {
            generateGetPropertyForMethod(clazz, code);
        }
    }

    private static final String AL20_HEADER = """
            /*
             * 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.
             */
            """;
}
