/*
 * Copyright (c) 1997, 2023 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0, which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * This Source Code may also be made available under the following Secondary
 * Licenses when the conditions for such availability set forth in the
 * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
 * version 2 with the GNU Classpath Exception, which is available at
 * https://www.gnu.org/software/classpath/license.html.
 *
 * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
 */

package org.xbib.net.mail.test.util;

import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import org.junit.jupiter.params.provider.MethodSource;
import org.xbib.net.mail.util.UUDecoderStream;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

import org.junit.jupiter.params.ParameterizedTest;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.fail;

/**
 * Test uudecoder.
 *
 * @author Bill Shannon
 */
public class UUDecoderStreamTest {

    private static final Logger logger = Logger.getLogger(UUDecoderStreamTest.class.getName());

    public UUDecoderStreamTest() {
    }

    /**
     * Test the data in the test case.
     */
    @ParameterizedTest
    @MethodSource("data")
    void test(TestData t) {
        InputStream in = new UUDecoderStream(new ByteArrayInputStream(t.input),
                t.ignoreErrors, t.ignoreMissingBeginEnd);

        // two cases - either we're expecting an exception or we're not
        if (t.expectedException != null) {
            try {
                int c;
                while ((c = in.read()) >= 0) {
                    ;    // throw it away
                }
                // read all the data with no exception - fail
                fail("Didn't get expected exception: " + t.expectedException);
                logger.log(Level.INFO, "Test: " + t.name + " got no Exception, expected Exception: " +
                        t.expectedException);
            } catch (Exception ex) {
                assertEquals(ex.getClass().getName(), t.expectedException);
                if (!ex.getClass().getName().equals(t.expectedException)) {
                    logger.log(Level.INFO, "Test: " + t.name + " got Exception: " + ex + " expected Exception: " +
                            t.expectedException);
                }
            } finally {
                try {
                    in.close();
                } catch (IOException ioex) {
                    //
                }
            }
        } else {
            InputStream ein = new ByteArrayInputStream(t.expectedOutput);
            try {
                int c, ec;
                boolean gotError = false;
                while ((c = in.read()) >= 0) {
                    ec = ein.read();
                    if (ec < 0) {
                        logger.log(Level.INFO, "Test: " + t.name + " got char: " + c + " expected EOF");
                        gotError = true;
                        break;
                    }
                    assertFalse(ec < 0);
                    assertEquals(ec, c);
                    if (c != ec) {
                        logger.log(Level.INFO, "Test: " + t.name + " got char: " + c + " expected char: " + ec);
                        gotError = true;
                        break;
                    }
                }
                if (!gotError) {
                    ec = ein.read();
                    if (ec >= 0) {
                        logger.log(Level.INFO, "Test: " + t.name + " got EOF, expected char: " + ec);
                    }
                    assertFalse(ec >= 0);
                }
            } catch (Exception ex) {
                logger.log(Level.INFO, "Test: " + t.name + " got Exception: " + ex + " expected no Exception");
                fail("Got exception: " + ex);
            } finally {
                try {
                    in.close();
                } catch (IOException ioex) {
                    logger.log(Level.WARNING, ioex.getMessage(), ioex);
                }
                try {
                    ein.close();
                } catch (IOException ioex) {
                    logger.log(Level.WARNING, ioex.getMessage(), ioex);
                }
            }
        }
    }

    private static Stream<TestData> data() throws Exception {
        // XXX - gratuitous array requirement
        List<TestData> testData = new ArrayList<>();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(
                UUDecoderStreamTest.class.getResourceAsStream("uudata")));
        TestData t;
        while ((t = parse(bufferedReader)) != null) {
            testData.add(t);
        }
        return testData.stream();
    }

    /*
     * Parse the input, returning a test case.
     */
    private static TestData parse(BufferedReader in) throws Exception {
        String line;
        for (; ; ) {
            line = in.readLine();
            if (line == null)
                return null;
            if (line.isEmpty() || line.startsWith("#"))
                continue;

            if (!line.startsWith("TEST"))
                throw new Exception("Bad test data format");
            break;
        }

        TestData t = new TestData();
        int i = line.indexOf(' ');    // XXX - crude
        t.name = line.substring(i + 1);

        line = in.readLine();
        StringTokenizer st = new StringTokenizer(line);
        String tok = st.nextToken();
        if (!tok.equals("DATA"))
            throw new Exception("Bad test data format: " + line);
        while (st.hasMoreTokens()) {
            tok = st.nextToken();
            if (tok.equals("ignoreErrors"))
                t.ignoreErrors = true;
            else if (tok.equals("ignoreMissingBeginEnd"))
                t.ignoreMissingBeginEnd = true;
            else
                throw new Exception("Bad DATA option in line: " + line);
        }

        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        Writer os = new OutputStreamWriter(bos, StandardCharsets.US_ASCII);
        for (; ; ) {
            line = in.readLine();
            if (line.equals("EXPECT"))
                break;
            os.write(line);
            os.write("\n");
        }
        os.close();
        t.input = bos.toByteArray();

        bos = new ByteArrayOutputStream();
        os = new OutputStreamWriter(bos, StandardCharsets.US_ASCII);
        for (; ; ) {
            line = in.readLine();
            if (line.startsWith("EXCEPTION")) {
                i = line.indexOf(' ');    // XXX - crude
                t.expectedException = line.substring(i + 1);
            } else if (line.equals("END"))
                break;
            os.write(line);
            os.write("\n");
        }
        os.close();
        if (t.expectedException == null) {
            t.expectedOutput = bos.toByteArray();
        }
        return t;
    }

    private static class TestData {
        public String name;
        public boolean ignoreErrors;
        public boolean ignoreMissingBeginEnd;
        public byte[] input;
        public byte[] expectedOutput;
        public String expectedException;
    }
}
