/*
 * 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 jakarta.activation.DataHandler;
import jakarta.mail.Folder;
import jakarta.mail.Message;
import jakarta.mail.Session;
import jakarta.mail.Store;
import jakarta.mail.internet.ContentType;
import jakarta.mail.internet.MimeMessage;
import jakarta.mail.internet.ParameterList;
import jakarta.mail.internet.ParseException;
import jakarta.mail.util.ByteArrayDataSource;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.Enumeration;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
 * Test parameter list parsing.
 *
 * XXX - this should be a JUnit parameterized test,
 * but I can't figure out how to run parameterized
 * tests under my ClassLoaderSuite.
 *
 * @author Bill Shannon
 */

class ParameterListDecode {
    static boolean gen_test_input = false;    // output good for input to -p
    static boolean test_mail = false;        // test using a mail server
    static int errors = 0;            // number of errors detected
    static Session session;
    static Store store;
    static Folder folder;

    static boolean junit;

    protected void testDecode(String paramData) throws Exception {
        junit = true;
        parse(new BufferedReader(new InputStreamReader(
                getClass().getResourceAsStream(paramData))));
    }

    /*
     * Parse the input in "mail" format, extracting the Content-Type
     * headers and testing them.  The parse is rather crude, but sufficient
     * to test against most existing UNIX mailboxes.
     */
    public static void parse(BufferedReader in) throws Exception {
        String header = "";

        for (; ; ) {
            String s = in.readLine();
            if (s != null && !s.isEmpty()) {
                char c = s.charAt(0);
                if (c == ' ' || c == '\t') {
                    // a continuation line, add it to the current header
                    header += '\n' + s;
                    continue;
                }
            }
            // "s" is the next header, "header" is the last complete header
            if (header.regionMatches(true, 0, "Content-Type: ", 0, 14)) {
                int i;
                String[] expect = null;
                if (s != null && s.startsWith("Expect: ")) {
                    try {
                        int nexpect = Integer.parseInt(s.substring(8));
                        expect = new String[nexpect];
                        for (i = 0; i < nexpect; i++)
                            expect[i] = decode(trim(in.readLine()));
                    } catch (NumberFormatException e) {
                        try {
                            if (s.substring(8, 17).equals("Exception")) {
                                expect = new String[1];
                                expect[0] = "Exception";
                            }
                        } catch (StringIndexOutOfBoundsException se) {
                            // ignore it
                        }
                    }
                }
                i = header.indexOf(':');
                try {
                    test(header.substring(0, i), header.substring(i + 2),
                            expect);
                } catch (StringIndexOutOfBoundsException e) {
                    // ignore
                }
            }
            if (s == null)
                return;        // EOF
            if (s.isEmpty()) {
                while ((s = in.readLine()) != null) {
                    if (s.startsWith("From "))
                        break;
                }
                if (s == null)
                    return;
            }
            header = s;
        }
    }

    /**
     * Like String.trim, but only the left side.
     */
    public static String trim(String s) {
        int i = 0;
        while (i < s.length() && s.charAt(i) <= ' ')
            i++;
        return s.substring(i);
    }

    /**
     * Decode Unicode escapes.
     */
    public static String decode(String s) {
        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            if (c == '\\' && s.charAt(i + 1) == 'u') {
                c = (char) Integer.parseInt(s.substring(i + 2, i + 6), 16);
                i += 5;
            }
            sb.append(c);
        }
        return sb.toString();
    }

    /**
     * Test the header's value to see if we can parse it as expected.
     */
    public static void test(String header, String value, String[] expect)
            throws Exception {
        PrintStream out = System.out;
        ByteArrayOutputStream bos = null;
        if (gen_test_input) {
            if (test_mail) {
                bos = new ByteArrayOutputStream();
                out = new PrintStream(bos);
            } else {
                out.println(header + ": " + value);
            }
        } else if (!junit)
            out.println("Test: " + value);

        try {
            ContentType ct = new ContentType(value);
            ParameterList pl = ct.getParameterList();
            if (gen_test_input)
                out.println("Expect: " + pl.size());
            else if (junit)
                assertEquals(expect.length, pl.size());
            else {
                out.println("Got " + pl.size() + " parameters:");
                if (expect != null && pl.size() != expect.length) {
                    out.println("Expected " + expect.length + " parameters");
                    errors++;
                }
            }
            Enumeration<String> e = pl.getNames();
            for (int i = 0; e.hasMoreElements(); i++) {
                String name = e.nextElement();
                String pvalue = pl.get(name);
                if (gen_test_input)
                    out.println("\t" + name + "=" + pvalue);    // XXX - newline
                else if (junit) {
                    if (i < expect.length)
                        assertEquals(expect[i], name + "=" + pvalue);
                } else {
                    out.println("\t[" + (i + 1) + "] Name: " + name +
                            "\t\tValue: " + pvalue);
                    if (expect != null && i < expect.length &&
                            !expect[i].equals(name + "=" + pvalue)) {
                        out.println("\tExpected:\t" + expect[i]);
                        errors++;
                    }
                }
            }
        } catch (ParseException e) {
            if (gen_test_input)
                out.println("Expect: Exception " + e);
            else if (junit)
                assertTrue(expect.length == 1 && expect[0].equals("Exception"));
            else {
                out.println("Got Exception: " + e);
                if (expect != null &&
                        (expect.length != 1 || !expect[0].equals("Exception"))) {
                    out.println("Expected " + expect.length + " parameters");
                    for (int i = 0; i < expect.length; i++)
                        out.println("\tExpected:\t" + expect[i]);
                    errors++;
                }
            }
        }
        if (gen_test_input && test_mail) {
            MimeMessage msg = new MimeMessage(session);
            byte[] buf = bos.toByteArray();
            msg.setDataHandler(new DataHandler(
                    new ByteArrayDataSource(buf, value)));
            msg.saveChanges();
            //msg.writeTo(System.out);
            folder.appendMessages(new Message[]{msg});
        }
    }

    /**
     * Test an individual message.
     */
    private static void testMessage(Message msg) throws Exception {
        String[] expect = null;

        BufferedReader in = new BufferedReader(
                new InputStreamReader(msg.getInputStream()));

        String s = in.readLine();
        if (s != null && s.startsWith("Expect: ")) {
            try {
                int nexpect = Integer.parseInt(s.substring(8));
                expect = new String[nexpect];
                for (int i = 0; i < nexpect; i++)
                    expect[i] = trim(in.readLine());
            } catch (NumberFormatException e) {
                try {
                    if (s.substring(8, 17).equals("Exception")) {
                        expect = new String[1];
                        expect[0] = "Exception";
                    }
                } catch (StringIndexOutOfBoundsException se) {
                    // ignore it
                }
            }
        }

        String ct = msg.getContentType();
        test("Content-Type: ", ct, expect);
    }
}
