/*
 * Copyright 2013-2020 Real Logic Limited.
 * Copyright 2017 MarketFactory 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
 *
 * 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 uk.co.real_logic.sbe.xml;

import uk.co.real_logic.sbe.Tests;

import org.junit.jupiter.api.Test;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import static uk.co.real_logic.sbe.xml.XmlSchemaParser.parse;

public class ErrorHandlerTest
{
    @Test
    public void shouldNotExitOnTypeErrorsAndWarnings() throws Exception
    {
        final String testXmlString =
            "<types>" +
            "<enum name=\"NullBoolean\" encodingType=\"uint8\" nullValue=\"255\" semanticType=\"Boolean\">" +
            "    <validValue name=\"false\">0</validValue>" +
            "    <validValue name=\"true\">1</validValue>" +
            "</enum>" +
            "<enum name=\"DupNameBoolean\" encodingType=\"uint8\" semanticType=\"Boolean\">" +
            "    <validValue name=\"false\">0</validValue>" +
            "    <validValue name=\"anotherFalse\">0</validValue>" +
            "    <validValue name=\"true\">1</validValue>" +
            "</enum>" +
            "<enum name=\"DupValBoolean\" encodingType=\"uint8\" semanticType=\"Boolean\">" +
            "    <validValue name=\"false\">0</validValue>" +
            "    <validValue name=\"false\">2</validValue>" +
            "    <validValue name=\"true\">1</validValue>" +
            "</enum>" +
            "<set name=\"DupValueSet\" encodingType=\"uint8\">" +
            "    <choice name=\"Bit0\">0</choice>" +
            "    <choice name=\"AnotherBit0\">0</choice>" +
            "</set>" +
            "<set name=\"DupNameSet\" encodingType=\"uint8\">" +
            "    <choice name=\"Bit0\">0</choice>" +
            "    <choice name=\"Bit0\">1</choice>" +
            "</set>" +
            "<composite name=\"decimal\">" +
            "    <type name=\"mantissa\" primitiveType=\"int64\"/>" +
            "    <type name=\"mantissa\" primitiveType=\"int64\"/>" +
            "    <type name=\"exponent\" primitiveType=\"int8\"/>" +
            "</composite>" +
            "<type name=\"ConstButNoValue\" primitiveType=\"char\" presence=\"constant\"></type>" +
            "<type name=\"NullButNotOptional\" primitiveType=\"int8\" presence=\"required\" nullValue=\"10\"/>" +
            "</types>";

        final Map<String, Type> map = new HashMap<>();
        final ParserOptions options = ParserOptions.builder().suppressOutput(true).build();
        final ErrorHandler handler = new ErrorHandler(options);

        parseTestXmlAddToMap(map, "/types/composite", testXmlString, handler);
        parseTestXmlAddToMap(map, "/types/type", testXmlString, handler);
        parseTestXmlAddToMap(map, "/types/enum", testXmlString, handler);
        parseTestXmlAddToMap(map, "/types/set", testXmlString, handler);

        assertThat(handler.errorCount(), is(3));
        assertThat(handler.warningCount(), is(33));
    }

    @Test
    public void shouldExitAfterTypes() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).build();
            parse(Tests.getLocalResource("error-handler-types-schema.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 2 errors", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    @Test
    public void shouldExitAfterTypesWhenDupTypesDefined() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).warningsFatal(true).build();
            parse(Tests.getLocalResource("error-handler-types-dup-schema.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 1 warnings", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    @Test
    public void shouldExitAfterMessageWhenDupMessageIdsDefined() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).warningsFatal(true).build();
            parse(Tests.getLocalResource("error-handler-dup-message-schema.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 1 errors", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    @Test
    public void shouldExitAfterMessage() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).warningsFatal(true).build();
            parse(Tests.getLocalResource("error-handler-message-schema.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 13 errors", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    @Test
    public void shouldExitAfterMessageWhenGroupDimensionsNotComposite() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).warningsFatal(true).build();
            parse(Tests.getLocalResource("error-handler-group-dimensions-schema.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 1 errors", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    @Test
    public void shouldExitAfterTypesWhenCompositeOffsetsIncorrect() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).warningsFatal(true).build();
            parse(Tests.getLocalResource("error-handler-invalid-composite-offsets-schema.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 2 errors", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    @Test
    public void shouldExitAfterTypesWhenCompositeHasInvalidTypes() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).warningsFatal(true).build();
            parse(Tests.getLocalResource("error-handler-invalid-composite.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 2 errors", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    @Test
    public void shouldExitInvalidFieldNames() throws Exception
    {
        try
        {
            final ParserOptions options = ParserOptions.builder().suppressOutput(true).warningsFatal(true).build();
            parse(Tests.getLocalResource("error-handler-invalid-name.xml"), options);
        }
        catch (final IllegalStateException ex)
        {
            assertEquals("had 16 warnings", ex.getMessage());
            return;
        }

        fail("expected IllegalStateException");
    }

    private static void parseTestXmlAddToMap(
        final Map<String, Type> map, final String xPathExpr, final String xml, final ErrorHandler handler)
        throws ParserConfigurationException, XPathExpressionException, IOException, SAXException
    {
        final Document document = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(
            new ByteArrayInputStream(xml.getBytes()));
        final XPath xPath = XPathFactory.newInstance().newXPath();
        final NodeList list = (NodeList)xPath.compile(xPathExpr).evaluate(document, XPathConstants.NODESET);

        document.setUserData(XmlSchemaParser.ERROR_HANDLER_KEY, handler, null);

        for (int i = 0, size = list.getLength(); i < size; i++)
        {
            Type type = null;

            if (xPathExpr.endsWith("enum"))
            {
                type = new EnumType(list.item(i));
            }
            else if (xPathExpr.endsWith("set"))
            {
                type = new SetType(list.item(i));
            }
            else if (xPathExpr.endsWith("type"))
            {
                type = new EncodedDataType(list.item(i));
            }
            else if (xPathExpr.endsWith("composite"))
            {
                type = new CompositeType(list.item(i));
            }

            if (type != null)
            {
                map.put(type.name(), type);
            }
        }
    }
}