/*******************************************************************************
 * Copyright 2017 xlate.io LLC, http://www.xlate.io
 *
 * 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 io.xlate.edi.internal.stream;

import static io.xlate.edi.test.StaEDITestUtil.assertLocation;
import static io.xlate.edi.test.StaEDITestUtil.assertTextLocation;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.logging.Logger;
import java.util.stream.Collectors;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import io.xlate.edi.internal.schema.SchemaUtils;
import io.xlate.edi.schema.EDIComplexType;
import io.xlate.edi.schema.EDIReference;
import io.xlate.edi.schema.EDISchemaException;
import io.xlate.edi.schema.EDISimpleType;
import io.xlate.edi.schema.EDIType;
import io.xlate.edi.schema.EDIType.Type;
import io.xlate.edi.schema.Schema;
import io.xlate.edi.schema.SchemaFactory;
import io.xlate.edi.stream.EDIInputFactory;
import io.xlate.edi.stream.EDIStreamConstants.Delimiters;
import io.xlate.edi.stream.EDIStreamEvent;
import io.xlate.edi.stream.EDIStreamException;
import io.xlate.edi.stream.EDIStreamReader;
import io.xlate.edi.stream.EDIStreamValidationError;
import io.xlate.edi.stream.Location;

@SuppressWarnings({ "resource", "unused" })
class StaEDIStreamReaderTest implements ConstantsTest {

    private static final Logger LOG = Logger.getLogger(StaEDIStreamReaderTest.class.getName());
    private Set<EDIStreamEvent> possible = new HashSet<>();

    public StaEDIStreamReaderTest() {
        possible.addAll(Arrays.asList(EDIStreamEvent.ELEMENT_DATA,
                                      EDIStreamEvent.START_INTERCHANGE,
                                      EDIStreamEvent.START_GROUP,
                                      EDIStreamEvent.START_TRANSACTION,
                                      EDIStreamEvent.START_LOOP,
                                      EDIStreamEvent.START_SEGMENT,
                                      EDIStreamEvent.START_COMPOSITE,
                                      EDIStreamEvent.END_INTERCHANGE,
                                      EDIStreamEvent.END_GROUP,
                                      EDIStreamEvent.END_TRANSACTION,
                                      EDIStreamEvent.END_LOOP,
                                      EDIStreamEvent.END_SEGMENT,
                                      EDIStreamEvent.END_COMPOSITE));
    }

    @Test
    void testGetProperty() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        assertNull(reader.getProperty("NONE"), "Property was not null");
        assertThrows(IllegalArgumentException.class, () -> reader.getProperty(null));
    }

    @Test
    void testGetDelimitersX12() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '~');
        expected.put(Delimiters.DATA_ELEMENT, '*');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        expected.put(Delimiters.REPETITION, '^');
        expected.put(Delimiters.DECIMAL, '.');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            if (reader.next() == EDIStreamEvent.START_INTERCHANGE) {
                delimiters = reader.getDelimiters();
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @ParameterizedTest
    @CsvSource({
                 "Basic TRADACOMS, /TRADACOMS/order.edi",
                 "Basic TRADACOMS (extra MHD data), /TRADACOMS/order-extra-mhd-data.edi",
    })
    void testGetDelimitersTRADACOMS(String title, String resourceName) throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream(resourceName);
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '\'');
        expected.put(Delimiters.DATA_ELEMENT, '+');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        expected.put(Delimiters.RELEASE, '?');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            if (reader.next() == EDIStreamEvent.START_INTERCHANGE) {
                delimiters = reader.getDelimiters();
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testGetDelimitersEDIFACTA() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/EDIFACT/invoic_d93a_una.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '\'');
        expected.put(Delimiters.DATA_ELEMENT, '+');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        expected.put(Delimiters.RELEASE, '?');
        expected.put(Delimiters.DECIMAL, ',');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            if (reader.next() == EDIStreamEvent.START_INTERCHANGE) {
                delimiters = reader.getDelimiters();
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testGetDelimitersEDIFACTB() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/EDIFACT/invoic_d97b.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '\'');
        expected.put(Delimiters.DATA_ELEMENT, '+');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        // expected.put(Delimiters.REPETITION, '*'); Input is version 3. No repetition character until version 4
        expected.put(Delimiters.RELEASE, '?');
        expected.put(Delimiters.DECIMAL, '.');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            if (reader.next() == EDIStreamEvent.START_INTERCHANGE) {
                delimiters = reader.getDelimiters();
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testAlternateEncodingEDIFACT() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/EDIFACT/invoic_d97b.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        int matches = 0;

        while (reader.hasNext()) {
            switch (reader.next()) {
            case ELEMENT_DATA:
                Location location = reader.getLocation();
                if ("NAD".equals(location.getSegmentTag())
                        && location.getSegmentPosition() == 7
                        && location.getElementPosition() == 4) {
                    assertEquals("BÜTTNER WIDGET COMPANY", reader.getText());
                    matches++;
                }
                break;
            default:
                break;
            }
        }

        assertEquals(1, matches);
    }

    @Test
    void testGetDelimitersX12_WithISX_00401() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        ByteArrayInputStream stream = new ByteArrayInputStream((""
                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*U*00401*000000001*0*T*:~"
                + "ISX*\\~"
                + "GS*FA*ReceiverDept*SenderDept*20200711*010015*1*X*005010~"
                + "ST*997*0001*005010X230~"
                + "SE*2*0001~"
                + "GE*1*1~"
                + "IEA*1*000000001~").getBytes());

        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '~');
        expected.put(Delimiters.DATA_ELEMENT, '*');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        //expected.put(Delimiters.REPETITION, '*');
        //expected.put(Delimiters.RELEASE, '\\');
        expected.put(Delimiters.DECIMAL, '.');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;
        List<EDIStreamValidationError> errors = new ArrayList<>();

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            switch (reader.next()) {
            case START_GROUP:
                delimiters = reader.getDelimiters();
                break;
            case SEGMENT_ERROR:
            case ELEMENT_OCCURRENCE_ERROR:
            case ELEMENT_DATA_ERROR:
                errors.add(reader.getErrorType());
                break;
            default:
                break;
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertArrayEquals(new EDIStreamValidationError[] { EDIStreamValidationError.UNEXPECTED_SEGMENT },
                          errors.toArray(new EDIStreamValidationError[errors.size()]));
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testGetDelimitersX12_WithISX_00501() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        ByteArrayInputStream stream = new ByteArrayInputStream((""
                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*00501*000000001*0*T*:~"
                + "ISX*\\~"
                + "GS*FA*ReceiverDept*SenderDept*20200711*010015*1*X*005010~"
                + "ST*997*0001*005010X230~"
                + "SE*2*0001~"
                + "GE*1*1~"
                + "IEA*1*000000001~").getBytes());

        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '~');
        expected.put(Delimiters.DATA_ELEMENT, '*');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        expected.put(Delimiters.REPETITION, '^');
        //expected.put(Delimiters.RELEASE, '\\');
        expected.put(Delimiters.DECIMAL, '.');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;
        List<EDIStreamValidationError> errors = new ArrayList<>();

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            switch (reader.next()) {
            case START_GROUP:
                delimiters = reader.getDelimiters();
                break;
            case SEGMENT_ERROR:
            case ELEMENT_OCCURRENCE_ERROR:
            case ELEMENT_DATA_ERROR:
                errors.add(reader.getErrorType());
                break;
            default:
                break;
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertArrayEquals(new EDIStreamValidationError[] { EDIStreamValidationError.UNEXPECTED_SEGMENT },
                          errors.toArray(new EDIStreamValidationError[errors.size()]));
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testGetDelimitersX12_WithISX_00501_Invalid_Repitition() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        ByteArrayInputStream stream = new ByteArrayInputStream((""
                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*V*00501*000000001*0*T*:~"
                + "ISX*\\~"
                + "GS*FA*ReceiverDept*SenderDept*20200711*010015*1*X*005010~"
                + "ST*997*0001*005010X230~"
                + "SE*2*0001~"
                + "GE*1*1~"
                + "IEA*1*000000001~").getBytes());

        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '~');
        expected.put(Delimiters.DATA_ELEMENT, '*');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        expected.put(Delimiters.DECIMAL, '.');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;
        List<EDIStreamValidationError> errors = new ArrayList<>();

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            switch (reader.next()) {
            case START_GROUP:
                delimiters = reader.getDelimiters();
                break;
            case SEGMENT_ERROR:
            case ELEMENT_OCCURRENCE_ERROR:
            case ELEMENT_DATA_ERROR:
                errors.add(reader.getErrorType());
                break;
            default:
                break;
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertArrayEquals(new EDIStreamValidationError[] { EDIStreamValidationError.UNEXPECTED_SEGMENT },
                          errors.toArray(new EDIStreamValidationError[errors.size()]));
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testGetDelimitersX12_WithISX_00704() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        ByteArrayInputStream stream = new ByteArrayInputStream((""
                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*00704*000000001*0*T*:~"
                + "ISX*\\~"
                + "GS*FA*ReceiverDept*SenderDept*20200711*010015*1*X*005010~"
                + "ST*997*0001*005010X230~"
                + "SE*2*0001~"
                + "GE*1*1~"
                + "IEA*1*000000001~").getBytes());

        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '~');
        expected.put(Delimiters.DATA_ELEMENT, '*');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        expected.put(Delimiters.REPETITION, '^');
        expected.put(Delimiters.RELEASE, '\\');
        expected.put(Delimiters.DECIMAL, '.');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;
        List<EDIStreamValidationError> errors = new ArrayList<>();

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            switch (reader.next()) {
            case START_GROUP:
                delimiters = reader.getDelimiters();
                break;
            case SEGMENT_ERROR:
            case ELEMENT_OCCURRENCE_ERROR:
            case ELEMENT_DATA_ERROR:
                errors.add(reader.getErrorType());
                break;
            default:
                break;
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertEquals(0, errors.size(), "Unexpected errors");
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testGetDelimitersX12_WithISX_CharTooLong_00704() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        ByteArrayInputStream stream = new ByteArrayInputStream((""
                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*00704*000000001*0*T*:~"
                + "ISX*##~"
                + "GS*FA*ReceiverDept*SenderDept*20200711*010015*1*X*005010~"
                + "ST*997*0001*005010X230~"
                + "SE*2*0001~"
                + "GE*1*1~"
                + "IEA*1*000000001~").getBytes());

        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        Map<String, Character> expected = new HashMap<>(5);
        expected.put(Delimiters.SEGMENT, '~');
        expected.put(Delimiters.DATA_ELEMENT, '*');
        expected.put(Delimiters.COMPONENT_ELEMENT, ':');
        expected.put(Delimiters.REPETITION, '^');
        //expected.put(Delimiters.RELEASE, '\\');
        expected.put(Delimiters.DECIMAL, '.');

        Map<String, Character> delimiters = null;
        int delimiterExceptions = 0;
        List<EDIStreamValidationError> errors = new ArrayList<>();

        while (reader.hasNext()) {
            try {
                reader.getDelimiters();
            } catch (IllegalStateException e) {
                delimiterExceptions++;
            }

            switch (reader.next()) {
            case START_GROUP:
                delimiters = reader.getDelimiters();
                break;
            case SEGMENT_ERROR:
            case ELEMENT_OCCURRENCE_ERROR:
            case ELEMENT_DATA_ERROR:
                errors.add(reader.getErrorType());
                break;
            default:
                break;
            }
        }

        assertEquals(expected, delimiters, "Unexpected delimiters");
        assertArrayEquals(new EDIStreamValidationError[] { EDIStreamValidationError.DATA_ELEMENT_TOO_LONG },
                          errors.toArray(new EDIStreamValidationError[errors.size()]));
        assertEquals(1, delimiterExceptions, "Unexpected exceptions");
    }

    @Test
    void testX12_ReleaseCharacter() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        ByteArrayInputStream stream = new ByteArrayInputStream((""
                + "ISA*00*          *00*          *ZZ*ReceiverID     *ZZ*Sender         *200711*0100*^*00704*000000001*0*T*:~"
                + "ISX*\\~"
                + "GS*FA*Receiver\\*Dept*Sender\\*Dept*20200711*010015*1*X*005010~"
                + "ST*997*0001*005010X230~"
                + "SE*2*0001~"
                + "GE*1*1~"
                + "IEA*1*000000001~").getBytes());

        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        List<EDIStreamValidationError> errors = new ArrayList<>();
        String senderId = null;
        String receiverId = null;

        while (reader.hasNext()) {
            switch (reader.next()) {
            case ELEMENT_DATA:
                if ("GS".equals(reader.getLocation().getSegmentTag())) {
                    switch (reader.getLocation().getElementPosition()) {
                    case 2:
                        receiverId = reader.getText();
                        break;
                    case 3:
                        senderId = reader.getText();
                        break;
                    default:
                        break;
                    }
                }
                break;
            case SEGMENT_ERROR:
            case ELEMENT_OCCURRENCE_ERROR:
            case ELEMENT_DATA_ERROR:
                errors.add(reader.getErrorType());
                break;
            default:
                break;
            }
        }

        assertEquals(0, errors.size(), "Unexpected errors");
        assertEquals("Receiver*Dept", receiverId);
        assertEquals("Sender*Dept", senderId);
    }

    @Test
    void testNext() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);

        EDIStreamEvent event;

        do {
            try {
                event = reader.next();
                assertTrue(possible.contains(event), "Unknown event " + event);
            } catch (NoSuchElementException e) {
                event = null;
            }
        } while (event != null);
    }

    @Test
    void testNextTag() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        factory.setProperty(EDIInputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, "false");
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);

        int s = 0;
        EDIStreamEvent event;
        String tag = null;

        while (reader.hasNext()) {
            try {
                event = reader.nextTag();
            } catch (NoSuchElementException e) {
                break;
            }

            if (event != EDIStreamEvent.START_SEGMENT) {
                fail("Unexpected event: " + event);
            }

            tag = reader.getText();
            assertEquals(simple997tags[s++], tag, "Unexpected segment");
        }

        String last = simple997tags[simple997tags.length - 1];
        assertEquals(last, tag, "Unexpected last segment");
    }

    @Test
    void testHasNext() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        assertTrue(reader.hasNext(), "Does not have next after create");

        EDIStreamEvent event;

        while (reader.hasNext()) {
            event = reader.next();
            assertTrue(possible.contains(event), "Unknown event");
        }
    }

    @Test
    void testClose() throws Exception {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);
        reader.close();
        try {
            assertNotEquals(0, stream.available(), "Stream was closed");
        } catch (IOException e) {
            fail("IO Exception: " + e);
        }
        assertThrows(IllegalStateException.class, () -> reader.getEventType());
    }

    @Test
    void testGetEventType() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);

        EDIStreamEvent event;

        while (reader.hasNext()) {
            event = reader.next();
            assertEquals(event, reader.getEventType(), "Event not equal");
        }
    }

    @Test
    void testGetStandard() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);

        String standard = null;
        int events = 0;
        assertThrows(IllegalStateException.class, () -> reader.getStandard());

        while (reader.hasNext()) {
            if (reader.next() != EDIStreamEvent.END_INTERCHANGE) {
                standard = reader.getStandard();
                events++;
            }
        }

        assertEquals("X12", standard, "Unexpected version");
        assertTrue(events > 2, "Unexpected number of events");
    }

    @Test
    void testGetVersion() throws EDIStreamException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/simple997.edi");
        EDIStreamReader reader = factory.createEDIStreamReader(stream);

        String version[] = null;
        int events = 0;
        assertThrows(IllegalStateException.class, () -> reader.getVersion());

        while (reader.hasNext()) {
            if (reader.next() != EDIStreamEvent.END_INTERCHANGE) {
                version = reader.getVersion();
                events++;
            }
        }

        assertArrayEquals(new String[] { "00501" }, version, "Unexpected version");
        assertTrue(events > 2, "Unexpected number of events");
    }

    @Test
    void testSetSchema() throws EDIStreamException, EDISchemaException, IOException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        InputStream stream = getClass().getResourceAsStream("/x12/invalid997.edi");
        SchemaFactory schemaFactory = SchemaFactory.newFactory();
        Schema transaction = schemaFactory.createSchema(getClass().getResource("/x12/EDISchema997.xml"));
        EDIStreamReader reader = factory.createEDIStreamReader(stream);

        EDIStreamEvent event;
        int events = 0;
        String segment = null;
        Map<String, Set<EDIStreamValidationError>> errors = new HashMap<>(2);

        while (reader.hasNext()) {
            event = reader.next();

            if (event == EDIStreamEvent.START_INTERCHANGE) {
                String standard = reader.getStandard();
                String[] version = reader.getVersion();
                Schema control = SchemaUtils.getControlSchema(standard, version);
                reader.setControlSchema(control);
            } else {
                if (event == EDIStreamEvent.START_TRANSACTION) {
                    reader.setTransactionSchema(transaction);
                } else if (event == EDIStreamEvent.START_SEGMENT) {
                    segment = reader.getText();
                } else if (event == EDIStreamEvent.ELEMENT_DATA_ERROR) {
                    Location l = reader.getLocation();
                    String key = String.format(
                                               "%s%02d",
                                               segment,
                                               l.getElementPosition());

                    if (!errors.containsKey(key)) {
                        errors.put(key, new HashSet<EDIStreamValidationError>(2));
                    }

                    errors.get(key).add(reader.getErrorType());
                } else if (event == EDIStreamEvent.ELEMENT_OCCURRENCE_ERROR) {
                    fail("Unexpected error: " + event + " => " + reader.getErrorType() + ", " + reader.getText());
                } else if (event == EDIStreamEvent.SEGMENT_ERROR) {
                    fail("Unexpected error: " + event + " => " + reader.getErrorType() + ", " + reader.getText());
                }
            }
            events++;
        }

        assertTrue(errors.get("AK402").contains(EDIStreamValidationError.DATA_ELEMENT_TOO_LONG));
        assertTrue(errors.get("AK402").contains(EDIStreamValidationError.INVALID_CHARACTER_DATA));

    }

    @Test
    void testAddSchema() throws EDIStreamException, EDISchemaException, IOException {
        EDIInputFactory factory = EDIInputFactory.newFactory();
        factory.setProperty(EDIInputFactory.EDI_VALIDATE_CONTROL_STRUCTURE, "false");
        InputStream stream = getClass().getResourceAsStream("/x12/invalid997.edi");

        SchemaFactory schemaFactory = SchemaFactory.newFactory();
        Schema transaction = schemaFactory.createSchema(getClass().getResource("/x12/EDISchema997.xml"));
        EDIStreamReader reader = factory.createEDIStreamReader(stream);

        EDIStreamEvent event;
        int events = 0;
        String segment = null;
        Map<String, Set<EDIStreamValidationError>> errors = new HashMap<>(2);

        while (reader.hasNext()) {
            event = reader.next();
            events++;

            switch (event) {
            case START_INTERCHANGE: {
                String standard = reader.getStandard();
                String version[] = reader.getVersion();
                Schema control = SchemaUtils.getControlSchema(standard, version);
                IllegalStateException illegal = null;

                try {
                    reader.setTransactionSchema(control);
                } catch (IllegalStateException e) {
                    illegal = e;
                }
                assertNotNull(illegal);

                reader.setControlSchema(control);
                continue;
            }
            case START_SEGMENT: {
                segment = reader.getText();
                IllegalStateException illegal = null;

                try {
                    reader.setTransactionSchema(transaction);
                } catch (IllegalStateException e) {
                    illegal = e;
                }
                if ("ST".equals(segment)) {
                    assertNull(illegal);
                } else {
                    assertNotNull(illegal);
                }

                break;
            }
            case END_SEGMENT:
                segment = reader.getText();
                IllegalStateException illegal = null;

                try {
                    reader.setTransactionSchema(transaction);
                } catch (IllegalStateException e) {
                    illegal = e;
                }
                if ("ST".equals(segment)) {
                    assertNull(illegal);
                } else {
                    assertNotNull(illegal);
