/*
 * Copyright 2023 Thoughtworks, Inc.
 *
 * Licensed 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 com.thoughtworks.go.util;

import org.apache.commons.lang3.StringUtils;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class XsdErrorTranslator extends DefaultHandler {
    private boolean validationError = false;
    private SAXParseException saxParseException = null;

    static List<MappingEntry> errorMapping = new ArrayList<>();

    private static final int NONE = 0;
    private static final int CAPITALIZE = 1;
    private static final int HUMANIZE = 2;
    private static final int REMOVE_TYPE_SUFFIX = 2;

    static {
        addMapping(
                "cvc-attribute.3: The value '(.*)' of attribute '(.*)' on element '(.*)' is not valid with respect to its type, '(.*)'",
                "\"{0}\" is invalid for {2} {1}",
                NONE, NONE, CAPITALIZE);

        addMapping(
                "cvc-complex-type.4: Attribute '(.*)' must appear on element '(.*)'.",
                "\"{0}\" is required for {1}",
                HUMANIZE | CAPITALIZE , CAPITALIZE);

        //Add more things to group 4 apart from Mingle. This is to handle xsd inline element type.

        addMapping(
                "cvc-pattern-valid: Value '(.*)' is not facet-valid with respect to pattern 'https\\?://.+' for type '#AnonType_siteUrlserverAttributeGroup'.",
                "siteUrl \"{0}\" is invalid. It must start with ‘http://’ or ‘https://’", NONE);

        addMapping(
                 "cvc-pattern-valid: Value '(.*)' is not facet-valid with respect to pattern 'https://.+' for type '#AnonType_secureSiteUrlserverAttributeGroup'.",
                 "secureSiteUrl \"{0}\" is invalid. It must be a secure URL (should start with ‘https://’)", NONE) ;

        addMapping(
                 "cvc-pattern-valid: Value '(.*)' is not facet-valid with respect to pattern 'https://.+' for type '#AnonType_urlluauType'.",
                 "url \"{0}\" is invalid. It must be a secure URL (should start with ‘https://’)", NONE) ;


        addMapping(
                "cvc-pattern-valid: Value '(.*)' is not facet-valid with respect to pattern '(.*)' for type '#AnonType_(.*?)(mingle)Type'.",
                "{2} in {3} is invalid. \"{0}\" should conform to the pattern - {1}",
                NONE, NONE, HUMANIZE | CAPITALIZE | REMOVE_TYPE_SUFFIX, CAPITALIZE) ;

        addMapping(
                "cvc-pattern-valid: Value '(.*)' is not facet-valid with respect to pattern '(.*)' for type '(.*)'.",
                "{2} is invalid. \"{0}\" should conform to the pattern - {1}",
                NONE, NONE, HUMANIZE | CAPITALIZE | REMOVE_TYPE_SUFFIX) ;

        addMapping(
                "cvc-minLength-valid: Value '(.*)' with length = '0' is not facet-valid with respect to minLength '1' for type '#AnonType_commandexec'.",
                "Command attribute cannot be blank in a command snippet.",
                NONE, NONE) ;

        addMapping(
                "cvc-elt.1: Cannot find the declaration of element '(.*)'.",
                "Invalid XML tag \"{0}\" found.",
                NONE) ;


        addMapping(
                "cvc-[^:]+: (.*)",
                "{0}");


    }

    private static void addMapping(String pattern, String replacement, int... transforms) {
        errorMapping.add(new MappingEntry(pattern, replacement, transforms));
    }

    private static class MappingEntry {
        public final Pattern pattern;
        public final String replacement;
        private final int[] transforms;

        public MappingEntry(String pattern, String replacement, int[] transforms) {
            this.transforms = transforms;
            this.pattern = Pattern.compile(pattern);
            this.replacement = replacement;
        }

        public String translate(String message) {
            final Matcher matcher = pattern.matcher(message);
            if (matcher.matches()) {
                return MessageFormat.format(replacement, applyTransforms(extractArguments(matcher)));
            }
            return null;
        }

        private String[] extractArguments(Matcher matcher) {
            String[] args = new String[matcher.groupCount()];
            for (int i = 1; i <= matcher.groupCount(); i++) {
                args[i - 1] = matcher.group(i);
            }
            return args;
        }

        private Object[] applyTransforms(String[] args) {
            for (int i = 0, transformsLength = transforms.length; i < transformsLength; i++) {
                int transform = transforms[i];
                if ((transform & HUMANIZE) != 0) {
                    args[i] = StringUtil.humanize(args[i]);
                }
                if ((transform & CAPITALIZE) != 0) {
                    args[i] = StringUtils.capitalize(args[i]);
                }
                if ((transform & REMOVE_TYPE_SUFFIX) != 0) {
                    args[i] = StringUtils.replace(args[i], " type", "");
                }
            }
            return args;
        }
    }

    @Override
    public void error(SAXParseException exception) {
        if (!validationError) {
            validationError = true;
            saxParseException = exception;
        }
    }

    @Override
    public void fatalError(SAXParseException exception) {
        if (!validationError) {
            validationError = true;
            saxParseException = exception;
        }
    }

    @Override
    public void warning(SAXParseException exception) {

    }

    public String translate() {
        String msg = saxParseException.getMessage();
        for (MappingEntry mappingEntry : errorMapping) {
            String translated = mappingEntry.translate(msg);
            if (translated != null) {
                return translated;
            }
        }
        return msg;
    }

    public boolean hasValidationError() {
        return validationError;
    }
}
