/*
 * 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
 *
 *   https://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.plc4x.test.migration;
 
import java.util.Map;
 
import org.apache.commons.lang3.RegExUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.plc4x.java.spi.generation.*;
import org.apache.plc4x.test.driver.exceptions.DriverTestsuiteException;
import org.dom4j.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xmlunit.builder.DiffBuilder;
import org.xmlunit.diff.Diff;
 
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.net.URI;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.regex.Pattern;
 
public class MessageValidatorAndMigrator {
 
    private final static Logger LOGGER = LoggerFactory.getLogger(MessageValidatorAndMigrator.class);
 
    /**
     * Validates a outbound message and migrates it to the expectation if the parameter {@code autoMigrate} is set to true.
     * <p>
     * Passed options should contain a single 'package' option or 'protocolName' and 'outputFlavor'.
     * In case if package is not specified then protocol name and output flavor (e.g read-write) are
     * used to construct lookup package.
     *
     * @param testCaseName    name of the testcase
     * @param options         map with specific test/lookup options.
     * @param referenceXml    the xml we expect the outbound message to be
     * @param parserArguments the parser arguments to create an instance of the message
     * @param data            the bytes of the message
     * @param byteOrder       the byte-oder being used
     * @param autoMigrate     indicates if we want to migrate to a new version
     * @param siteURI         the file which we want to auto migrate
     * @throws DriverTestsuiteException if something goes wrong
     */
    @SuppressWarnings({"rawtypes"})
    public static void validateOutboundMessageAndMigrate(String testCaseName, Map<String, String> options, Element referenceXml, List<String> parserArguments, byte[] data, ByteOrder byteOrder, boolean autoMigrate, URI siteURI) throws DriverTestsuiteException {
        MessageInput<?> messageInput = MessageResolver.getMessageInput(options, referenceXml.getName());
        validateOutboundMessageAndMigrate(testCaseName, messageInput, referenceXml, parserArguments, data, byteOrder, autoMigrate, siteURI);
    }
 
    /**
     * Validates a outbound message and migrates it to the expectation if the parameter {@code autoMigrate} is set to true
     *
     * @param testCaseName    name of the testcase
     * @param messageInput    the pre-constructed MessageInput
     * @param referenceXml    the xml we expect the outbound message to be
     * @param parserArguments the parser arguments to create an instance of the message
     * @param data            the bytes of the message
     * @param byteOrder       the byte-order being used
     * @param autoMigrate     indicates if we want to migrate to a new version
     * @param siteURI         the file which we want to auto migrate
     * @return true if migration happened
     * @throws DriverTestsuiteException if something goes wrong
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static boolean validateOutboundMessageAndMigrate(String testCaseName, MessageInput<?> messageInput, Element referenceXml, List<String> parserArguments, byte[] data, ByteOrder byteOrder, boolean autoMigrate, URI siteURI) throws DriverTestsuiteException {
        final ReadBufferByteBased readBuffer = new ReadBufferByteBased(data, byteOrder);
 
        try {
            final Message parsedOutput = (Message) messageInput.parse(readBuffer, parserArguments.toArray());
            final String referenceXmlString = referenceXml.asXML();
            try {
                // First try to use the native xml writer
                WriteBufferXmlBased writeBufferXmlBased = new WriteBufferXmlBased();
                parsedOutput.serialize(writeBufferXmlBased);
                String xmlString = writeBufferXmlBased.getXmlString();
                final Diff diff = DiffBuilder.compare(referenceXmlString)
                    .withTest(xmlString).checkForSimilar().ignoreComments().ignoreWhitespace()
                    .build();
                if (diff.hasDifferences()) {
                    String border = StringUtils.repeat("=", 100);
                    String centeredDiffDetectedMessage = StringUtils.center(" Diff detected ", 100, "=");
                    String centeredTestCaseName = StringUtils.center(testCaseName, 100, "=");
                    LOGGER.warn(String.format(
                        "\n" +
                            // Border
                            "%1$s\n" +
                            // Testcase name
                            "%5$s\n" +
                            // diff detected message
                            "%2$s\n" +
                            // Border
                            "%1$s\n" +
                            // xml
                            "%3$s\n" +
                            // Border
                            "%1$s\n%1$s\n" +
                            // Text
                            "Differences were found after parsing (Use the above xml in the testsuite to disable this warning).\n" +
                            // Diff
                            "%4$s\n" +
                            // Double Border
                            "%1$s\n%1$s\n",
                        border,
                        centeredDiffDetectedMessage,
                        xmlString,
                        diff,
                        centeredTestCaseName));
                    throw new MigrationException(xmlString);
                }
                return false;
            } catch (RuntimeException | SerializationException e) {
                if (!(e instanceof MigrationException)) {
                    LOGGER.error("Error in serializer", e);
                }
                if (autoMigrate && e instanceof MigrationException) {
                    Path path = Paths.get(siteURI);
                    LOGGER.info("Migrating {} now", path);
                    Charset charset = StandardCharsets.UTF_8;
 
                    String content;
                    try {
                        // REMARK: In know IntelliJ tells us this is "optimizable", don't do it as it will break the build.
                        content = new String(Files.readAllBytes(path), charset);
                        // Make sure this also works on Windows
                        // (Mainly when using git to check out Windows style and commit in Unix style)
                        content = content.replaceAll("\r\n", "\n");
                    } catch (IOException ioException) {
                        throw new RuntimeException(ioException);
                    }
                    String indent = TestCasePatcher.determineIndent(content, referenceXmlString);
                    String newXml = ((MigrationException) e).newXml;
                    newXml = TestCasePatcher.indent(newXml, indent);
                    Pattern patternForReferenceXmlString = TestCasePatcher.getPatternForFragment(referenceXmlString);
                    if (!patternForReferenceXmlString.matcher(content).find()) {
                        throw new RuntimeException("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\nAutomigration failed: Can't match content. Patching won't work..\nTry to copy the above xml manually. \n!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
                    }
                    content = RegExUtils.replaceFirst(content, patternForReferenceXmlString, newXml + "\n");
                    try {
                        Files.write(path, content.getBytes(charset));
                    } catch (IOException ioException) {
                        throw new RuntimeException(ioException);
                    }
                    LOGGER.info("Done migrating {}", path);
                    return true;
                } else {
                    throw new RuntimeException("Output doesn't match. Set to auto migrate to fix", e);
                }
            }
        } catch (ParseException e) {
            throw new DriverTestsuiteException("Error parsing message", e);
        } catch (RuntimeException e) {
            LOGGER.error("Something went wrong: siteURI='{}'", siteURI, e);
            throw e;
        }
    }
 
    /**
     * Validates a inbound message and migrates it to the expectation if the parameter {@code autoMigrate} is set to true
     *
     * @param options         Options which contain custom 'package' name or keys 'protocolName' (name of the protocol)
     *                        and 'outputFlavor' (flavor of the output e.g read-write) which are used to construct
     *                        class lookup root package.
     * @param referenceXml    the xml we expect the outbound message
     * @param parserArguments the parser arguments to create an instance of the message
     * @return the message if all went well
     */
    @SuppressWarnings("rawtypes")
    public static Message validateInboundMessageAndGet(Map<String, String> options, Element referenceXml, List<String> parserArguments) {
        MessageInput<?> messageIO = MessageResolver.getMessageInput(options, referenceXml.getName());
        return validateInboundMessageAndGet(messageIO, referenceXml, parserArguments);
    }
 
    /**
     * Validates a inbound message and migrates it to the expectation if the parameter {@code autoMigrate} is set to true
     *
     * @param messageInput    the pre-constructed MessageInput
     * @param referenceXml    the xml we expect the outbound messag
     * @param parserArguments the parser arguments to create an instance of the message
     * @return the message if all went well
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public static Message validateInboundMessageAndGet(MessageInput messageInput, Element referenceXml, List<String> parserArguments) {
        final String referenceXmlString = referenceXml.asXML();
        try {
            return (Message) messageInput.parse(new ReadBufferXmlBased(new ByteArrayInputStream(referenceXmlString.getBytes(StandardCharsets.UTF_8))), parserArguments.toArray(new String[0]));
        } catch (RuntimeException | ParseException e) {
            throw new DriverTestsuiteException(String.format("Error parsing message from:\n%s", referenceXmlString), e);
        }
    }
}

V6046 Incorrect format. A different number of format items is expected. Arguments not used: 1.