/**
 * 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.camel.dataformat.beanio;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.component.mock.MockEndpoint;
import org.apache.camel.test.junit4.CamelTestSupport;
import org.beanio.InvalidRecordException;
import org.beanio.UnexpectedRecordException;
import org.beanio.UnidentifiedRecordException;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class BeanIODataFormatComplexTest extends CamelTestSupport {

    private static Locale defaultLocale;

    private final String recordData = "0001917A112345.678900           " + LS
            + "0002374A159303290.020           " + LS
            + "0015219B1SECURITY ONE           " + LS
            + "END OF SECTION 1                " + LS
            + "0076647A10.0000000001           " + LS
            + "0135515A1999999999999           " + LS
            + "2000815B1SECURITY TWO           " + LS
            + "2207122B1SECURITY THR           " + LS
            + "END OF FILE 000007              " + LS;

    private final String data = "0000000A1030808PRICE            " + LS
            + "0000000B1030808SECURITY         " + LS
            + "HEADER END                      " + LS
            + recordData;

    private final String unExpectedData = "0000000A1030808PRICE            " + LS
            + "0000000B1030808SECURITY         " + LS
            + "0000000B1030808SECURITY         " + LS
            + "HEADER END                      " + LS
            + recordData;

    private final String invalidData = "0000000A1030808PRICE            " + LS
            + "0000000B1030808SECURITY         EXTRA DATA" + LS
            + "0000000B1030808SECURITY         " + LS
            + "HEADER END                      " + LS
            + recordData;

    private final String unidentifiedData = "0000000A1030808PRICE            " + LS
            + "0000000C1030808SECURITY         " + LS
            + "0000000B1030808SECURITY         " + LS
            + "HEADER END                      " + LS
            + recordData;

    @BeforeClass
    public static void setLocale() {
        if (!Locale.getDefault().equals(Locale.ENGLISH)) {

            // the Locale used for the number formatting of the above data is
            // english which could be other than the default locale
            defaultLocale = Locale.getDefault();
            Locale.setDefault(Locale.ENGLISH);
        }
    }

    @AfterClass
    public static void resetLocale() {
        if (defaultLocale != null) {
            Locale.setDefault(defaultLocale);
        }
    }

    @Test
    public void testMarshal() throws Exception {
        MockEndpoint mock = getMockEndpoint("mock:beanio-marshal");
        mock.expectedBodiesReceived(data);

        template.sendBody("direct:marshal", createTestData(false));

        mock.assertIsSatisfied();
    }

    @Test
    public void testUnmarshal() throws Exception {
        context.setTracing(true);
        MockEndpoint mock = getMockEndpoint("mock:beanio-unmarshal");
        mock.expectedBodiesReceived(createTestData(false));

        template.sendBody("direct:unmarshal", data);

        mock.assertIsSatisfied();
    }

    @Test
    public void testUnmarshalUnexpected() throws Exception {
        Throwable ex = null;

        try {
            template.sendBody("direct:unmarshal", unExpectedData);
        } catch (Exception e) {
            ex = e.getCause();
        }

        assertIsInstanceOf(UnexpectedRecordException.class, ex);
    }

    @Test
    public void testUnmarshalInvalid() throws Exception {
        Throwable ex = null;

        try {
            template.sendBody("direct:unmarshal", invalidData);
        } catch (Exception e) {
            ex = e.getCause();
        }

        assertIsInstanceOf(InvalidRecordException.class, ex);
    }

    @Test
    public void testUnmarshalUnidentifiedIgnore() throws Exception {
        MockEndpoint mock = getMockEndpoint("mock:beanio-unmarshal");
        mock.expectedBodiesReceived(createTestData(false));
        template.sendBody("direct:unmarshal-forgiving", unidentifiedData);
        mock.assertIsSatisfied();
    }

    @Test
    public void testUnmarshalUnexpectedIgnore() throws Exception {
        MockEndpoint mock = getMockEndpoint("mock:beanio-unmarshal");
        mock.expectedBodiesReceived(createTestData(false));
        template.sendBody("direct:unmarshal-forgiving", unExpectedData);
        mock.assertIsSatisfied();
    }

    @Test
    public void testUnmarshalInvalidIgnore() throws Exception {
        context.setTracing(true);
        MockEndpoint mock = getMockEndpoint("mock:beanio-unmarshal");
        mock.expectedBodiesReceived(createTestData(true));
        template.sendBody("direct:unmarshal-forgiving", invalidData);
        mock.assertIsSatisfied();
    }

    @Test
    public void testUnmarshalUnidentified() throws Exception {
        Throwable ex = null;

        try {
            template.sendBody("direct:unmarshal", unidentifiedData);
        } catch (Exception e) {
            ex = e.getCause();
        }

        assertIsInstanceOf(UnidentifiedRecordException.class, ex);
    }

    private List<Object> createTestData(boolean skipB1header) throws ParseException {
        String source = "camel-beanio";
        List<Object> body = new ArrayList<>();

        Date date = new SimpleDateFormat("ddMMyy").parse("030808");
        Header hFirst = new Header("A1", date, "PRICE");
        Header hSecond = new Header("B1", date, "SECURITY");
        Separator headerEnd = new Separator("HEADER END");

        A1Record first = new A1Record("0001917", source, 12345.678900);
        A1Record second = new A1Record("0002374", source, 59303290.020);
        B1Record third = new B1Record("0015219", source, "SECURITY ONE");
        Separator sectionEnd = new Separator("END OF SECTION 1");
        A1Record fourth = new A1Record("0076647", source, 0.0000000001);
        A1Record fifth = new A1Record("0135515", source, 999999999999d);
        B1Record sixth = new B1Record("2000815", source, "SECURITY TWO");
        B1Record seventh = new B1Record("2207122", source, "SECURITY THR");

        body.add(hFirst);
        if (!skipB1header) {
            body.add(hSecond);
        }
        body.add(headerEnd);
        body.add(first);
        body.add(second);
        body.add(third);
        body.add(sectionEnd);
        body.add(fourth);
        body.add(fifth);
        body.add(sixth);
        body.add(seventh);

        Trailer trailer = new Trailer(7);
        body.add(trailer);

        return body;
    }

    @Override
    protected RouteBuilder createRouteBuilder() throws Exception {
        return new RouteBuilder() {
            @Override
            public void configure() throws Exception {
                BeanIODataFormat format = new BeanIODataFormat("org/apache/camel/dataformat/beanio/mappings.xml", "securityData");

                BeanIODataFormat forgivingFormat = new BeanIODataFormat("org/apache/camel/dataformat/beanio/mappings.xml", "securityData");
                forgivingFormat.setIgnoreInvalidRecords(true);
                forgivingFormat.setIgnoreUnexpectedRecords(true);
                forgivingFormat.setIgnoreUnidentifiedRecords(true);

                from("direct:unmarshal").unmarshal(format).split(simple("${body}")).to("mock:beanio-unmarshal");

                from("direct:unmarshal-forgiving").unmarshal(forgivingFormat).split(simple("${body}")).to("mock:beanio-unmarshal");

                from("direct:marshal").marshal(format).to("mock:beanio-marshal");
            }
        };
    }
}
