/*
 * Copyright (c) 2009, 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.pop3;

import org.xbib.net.mail.test.test.ProtocolHandler;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.channels.Channels;
import java.nio.charset.StandardCharsets;
import java.util.StringTokenizer;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * Handle connection.
 *
 * @author sbo
 */
public class POP3Handler extends ProtocolHandler {

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

    /**
     * First test message.
     */
    private final String top1 =
            """
                    Mime-Version: 1.0\r
                    From: joe@example.com\r
                    To: bob@example.com\r
                    Subject: Example\r
                    Content-Type: text/plain\r
                    \r
                    """;
    private final String msg1 = top1 +
            "plain text\r\n";

    /**
     * Second test message.
     */
    private final String top2 =
            """
                    Mime-Version: 1.0\r
                    From: joe@example.com\r
                    To: bob@example.com\r
                    Subject: Multipart Example\r
                    Content-Type: multipart/mixed; boundary="xxx"\r
                    \r
                    """;
    private final String msg2 = top2 +
            "preamble\r\n" +
            "--xxx\r\n" +
            "\r\n" +
            "first part\r\n" +
            "\r\n" +
            "--xxx\r\n" +
            "\r\n" +
            "second part\r\n" +
            "\r\n" +
            "--xxx--\r\n";

    public POP3Handler() {
    }

    /**
     * Send greetings.
     *
     * @throws IOException unable to write to socket
     */
    @Override
    public void sendGreetings(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK POP3 CUSTOM");
    }

    /**
     * Send String to socket.
     *
     * @param str String to send
     * @throws IOException unable to write to socket
     */
    public void println(OutputStream outputStream, final String str) throws IOException {
        Channels.newChannel(outputStream).write(StandardCharsets.UTF_8.encode(str + "\r\n"));
        outputStream.flush();
    }

    /**
     * Handle command.
     *
     * @throws IOException unable to read/write to socket
     */
    @Override
    public void handleCommand(InputStream inputStream, OutputStream outputStream) throws IOException {
        String currentLine = readLine(inputStream);

        if (currentLine == null) {
            // probably just EOF because the socket was closed
            //LOGGER.severe("Current line is null!");
            this.exit();
            return;
        }
        final StringTokenizer st = new StringTokenizer(currentLine, " ");
        final String commandName = st.nextToken().toUpperCase();
        final String arg = st.hasMoreTokens() ? st.nextToken() : null;
        switch (commandName) {
            case "STAT" -> this.stat(outputStream);
            case "LIST" -> this.list(outputStream);
            case "RETR" -> this.retr(outputStream, arg);
            case "DELE" -> this.dele(outputStream);
            case "NOOP" -> this.noop(outputStream);
            case "RSET" -> this.rset(outputStream);
            case "QUIT" -> this.quit(outputStream);
            case "TOP" -> this.top(outputStream, arg);
            case "UIDL" -> this.uidl(outputStream);
            case "USER" -> this.user(outputStream);
            case "PASS" -> this.pass(outputStream);
            case "CAPA" -> this.capa(outputStream);
            case "AUTH" -> this.auth(outputStream);
            default -> {
                logger.log(Level.SEVERE, "ERROR command unknown: {0}", commandName);
                this.println(outputStream, "-ERR unknown command");
            }
        }
    }

    /**
     * STAT command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void stat(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK 2 " + (msg1.length() + msg2.length()));
    }

    /**
     * LIST command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void list(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK\r\n1 " + msg1.length() + "\r\n2 " + msg2.length() + "\r\n.");
    }

    /**
     * RETR command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void retr(OutputStream outputStream, String arg) throws IOException {
        String msg;
        if ("1".equals(arg))
            msg = msg1;
        else
            msg = msg2;
        this.println(outputStream, "+OK " + msg.length() + " octets");
        this.println(outputStream, msg + ".");
    }

    /**
     * DELE command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void dele(OutputStream outputStream) throws IOException {
        this.println(outputStream, "-ERR DELE not supported");
    }

    /**
     * NOOP command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void noop(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK");
    }

    /**
     * RSET command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void rset(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK");
    }

    /**
     * QUIT command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void quit(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK");
        this.exit();
    }

    /**
     * TOP command.
     * XXX - ignores number of lines argument
     *
     * @throws IOException unable to read/write to socket
     */
    public void top(OutputStream outputStream, String arg) throws IOException {
        String top;
        if ("1".equals(arg))
            top = top1;
        else
            top = top2;
        this.println(outputStream, "+OK " + top.length() + " octets");
        this.println(outputStream, top + ".");
    }

    /**
     * UIDL command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void uidl(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK\r\n1 1\r\n2 2\r\n.");
    }

    /**
     * USER command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void user(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK");
    }

    /**
     * PASS command.
     *
     * @throws IOException unable to read/write to socket
     */
    public void pass(OutputStream outputStream) throws IOException {
        this.println(outputStream, "+OK");
    }

    /**
     * CAPA command
     *
     * @throws IOException unable to write to socket
     */
    public void capa(OutputStream outputStream) throws IOException {
        this.println(outputStream, "-ERR CAPA not supported");
    }

    /**
     * AUTH command
     *
     * @throws IOException unable to write to socket
     */
    public void auth(OutputStream outputStream) throws IOException {
        this.println(outputStream, "-ERR AUTH not supported");
    }
}
