/*
 * Copyright (c) 1997, 2020 Oracle and/or its affiliates. All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0, which is available at
 * http://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

import java.util.*;
import java.io.*;
import jakarta.mail.*;
import jakarta.mail.search.*;
import jakarta.mail.internet.*;

/**
 * Program to manage the javamail@Sun.COM mailing list by keeping track
 * of which messages have been answered.  Message that have been answered
 * have the ANSWERED flag set.  Messages that are replies to other messages
 * have the FLAGGED flag set. <p>
 *
 * Note that this program operates on the log file directly and thus all
 * access to the log file using this program must be through the same
 * IMAP server so that the flags are handled consistently. <p>
 *
 * When run with no message number arguments it will list the unanswered
 * messages.  The -u flag will cause it to first update the mailbox to
 * account for any recent replies. <p>
 *
 * When run with message number arguments it will mark those messages as
 * answered.  This is useful for marking spam and other random messages
 * that will never be replied to. <p>
 *
 * The -a flag will cause it to mark all messages as answered.  Useful for
 * flushing the ever accumulating spam. <p>
 *
 * @author Bill Shannon
 */

public class answer {

    static String protocol = "imap";
    static String host = "anybodys.sfbay";
    static String user = "javamail";
    static String password = "1javamail1";
    static String mbox =
	"/net/anybodys.sfbay/export6/javamail/logs/javamail.log";
    static String url = null;
    static boolean verbose = false;
    static boolean update = false;
    static boolean markAll = false;

    public static void main(String argv[]) {
	int optind;

	for (optind = 0; optind < argv.length; optind++) {
	    if (argv[optind].equals("-T")) {
		protocol = argv[++optind];
	    } else if (argv[optind].equals("-H")) {
		host = argv[++optind];
	    } else if (argv[optind].equals("-U")) {
		user = argv[++optind];
	    } else if (argv[optind].equals("-P")) {
		password = argv[++optind];
	    } else if (argv[optind].equals("-v")) {
		verbose = true;
	    } else if (argv[optind].equals("-f")) {
		mbox = argv[++optind];
	    } else if (argv[optind].equals("-L")) {
		url = argv[++optind];
	    } else if (argv[optind].equals("-u")) {
		update = true;
	    } else if (argv[optind].equals("-a")) {
		markAll = true;
		update = true;
	    } else if (argv[optind].equals("--")) {
		optind++;
		break;
	    } else if (argv[optind].startsWith("-")) {
		System.out.println(
"Usage: answer [-L url] [-T protocol] [-H host] [-U user] [-P password]\n" +
"\t[-f mailbox] [-v] [-u] [-a] [msgno ...]");
		System.exit(1);
	    } else {
		break;
	    }
	}

        try {
	    // Get a Properties object
	    Properties props = System.getProperties();

	    // Get a Session object
	    Session session = Session.getDefaultInstance(props,
				    new TtyAuthenticator());

	    // Get a Store object
	    Store store = null;
	    if (url != null) {
		URLName urln = new URLName(url);
		store = session.getStore(urln);
		store.connect();
	    } else {
		if (protocol != null)		
		    store = session.getStore(protocol);
		else
		    store = session.getStore();

		// Connect
		if (host != null || user != null || password != null)
		    store.connect(host, user, password);
		else
		    store.connect();
	    }
	    

	    // Open the Folder

	    Folder folder = store.getDefaultFolder();
	    if (folder == null) {
	        System.out.println("No default folder");
	        System.exit(1);
	    }

	    folder = folder.getFolder(mbox);
	    if (folder == null) {
	        System.out.println("Invalid folder");
	        System.exit(1);
	    }

	    if (optind < argv.length || update)
		folder.open(Folder.READ_WRITE);
	    else
		folder.open(Folder.READ_ONLY);
	    int totalMessages = folder.getMessageCount();

	    if (totalMessages == 0) {
		System.out.println("Empty folder");
		folder.close(false);
		store.close();
		System.exit(1);
	    }

	    if (verbose) {
		int newMessages = folder.getNewMessageCount();
		pv("Total messages = " + totalMessages);
		pv("New messages = " + newMessages);
		pv("-------------------------------");
	    }

	    if (optind >= argv.length) {
		// get all messages that aren't answered or flagged
		Flags f = new Flags();
		f.add(Flags.Flag.ANSWERED);
		f.add(Flags.Flag.FLAGGED);

		// Use a suitable FetchProfile
		FetchProfile fp = new FetchProfile();
		fp.add(FetchProfile.Item.ENVELOPE);
		fp.add(FetchProfile.Item.FLAGS);

		Message[] msgs = folder.search(new FlagTerm(f, false));
		folder.fetch(msgs, fp);

		if (update) {
		    doUpdate(msgs);
		    // re-fetch messages
		    msgs = folder.search(new FlagTerm(f, false));
		    pv("");
		    pv("");
		}

		System.out.println("Unanswered messages:");
		for (int i = 0; i < msgs.length; i++) {
		    Message msg = msgs[i];
		    System.out.println("--------------------------");
		    System.out.println("MESSAGE #" +
			msg.getMessageNumber() + ":");
		    Question q = new Question(msg);
		    System.out.println(q);
		    if (q.isReply())
			System.out.println(
			    "A reply to a message we haven't seen");
		    if (markAll) {
			int msgno = msg.getMessageNumber();
			if (q.isReply()) {
			    pv("Flagging message #" + msgno);
			    msg.setFlag(Flags.Flag.FLAGGED, true);
			} else {
			    pv("Answering message #" + msgno);
			    msg.setFlag(Flags.Flag.ANSWERED, true);
			}
		    }
		}

	    } else {
		for (int i = optind; i < argv.length; i++) {
		    int msgno = Integer.parseInt(argv[i]);
		    Message msg = folder.getMessage(msgno);
		    Question q = new Question(msg);
		    if (q.isReply()) {
			pv("Flagging message #" + msgno);
			msg.setFlag(Flags.Flag.FLAGGED, true);
		    } else {
			pv("Answering message #" + msgno);
			msg.setFlag(Flags.Flag.ANSWERED, true);
		    }
		}
	    }

	    folder.close(false);
	    store.close();
	} catch (Exception ex) {
	    System.out.println("Oops, got exception! " + ex.getMessage());
	    ex.printStackTrace();
	    System.exit(1);
	}
	System.exit(0);
    }

    /**
     * Update the mailbox to account for any replies.
     */
    static void doUpdate(Message[] msgs) throws MessagingException {
	// a hash table of all the unanswered questions
	Hashtable qt = new Hashtable();

	Address javamailAddress = new InternetAddress("javamail@Sun.COM");
	SearchTerm javamail = new OrTerm(
		new RecipientTerm(Message.RecipientType.TO, javamailAddress),
		new RecipientTerm(Message.RecipientType.CC, javamailAddress));

	for (int i = 0; i < msgs.length; i++) {
	    Message msg = msgs[i];
	    pv("--------------------------");
	    pv("MESSAGE #" + msg.getMessageNumber() + ":");
	    Question q = new Question(msg);
	    pv(q.toString());
	    Question q0;
	    if ((q0 = (Question)qt.get(q)) != null) {
		pv("Found in table");
		if (q.isReply()) {
		    Message qm = q0.getMessage();
		    if (!qm.isSet(Flags.Flag.ANSWERED)) {
			pv("Answered:");
			pv(q0.toString());
			qm.setFlag(Flags.Flag.ANSWERED, true);
		    }
		    pv("is reply, flagging it");
		    msg.setFlag(Flags.Flag.FLAGGED, true);
		} else {
		    // a second copy of a message that's not a reply?
		    // shouldn't happen.
		}
	    } else {
		pv("NOT found in table");
		if (q.isReply()) {
		    // a reply to a message we haven't seen?
		    pv("flagging, but no original msg");
		    msg.setFlag(Flags.Flag.FLAGGED, true);
		} else {
		    // an original question, put it in the table
		    qt.put(q, q);
		    // if the message looks like spam, flag it
		    if (!javamail.match(msg)) {
			pv("SPAM!!!");
			msg.setFlag(Flags.Flag.FLAGGED, true);
		    }
		}
	    }
	}
    }

    /**
     * Print verbose.
     */
    static void pv(String s) {
	if (verbose)
	    System.out.println(s);
    }
}

/**
 * This class represents a single "question" sent to javamail@Sun.COM,
 * or possibly a reply to such a question.
 */
class Question {
    Message	msg;
    Address	sender;
    Address[]	recipients;
    String	subject;
    Date	date;
    boolean	reply = false;

    Question(Message msg) throws MessagingException {
	this.msg = msg;
	sender = msg.getReplyTo()[0];    // XXX - assume only one
	subject = msg.getSubject();
	if (subject == null)
	    subject = "";
	else if (subject.regionMatches(true, 0, "Re: ", 0, 4)) {
	    subject = subject.substring(4);
	    reply = true;
	    recipients = msg.getRecipients(Message.RecipientType.TO);
	}
	subject = subject.trim();
	date = msg.getSentDate();
	if (date == null)
	    date = msg.getReceivedDate();
    }

    public boolean isReply() {
	return reply;
    }

    public Message getMessage() {
	return msg;
    }

    public int hashCode() {
	return subject.hashCode();
    }

    public boolean equals(Object obj) {
	if (!(obj instanceof Question))
	    return false;
	Question q = (Question)obj;
	if (this.reply == q.reply)
	    return this.sender.equals(q.sender) &&
		    this.subject.equals(q.subject);

	/*
	 * A reply is equal to a question if the sender of the question
	 * is one of the recipients of the reply.
	 */
	Question qq, qr;
	if (this.reply) {
	    qq = q;
	    qr = this;
	} else {
	    qq = this;
	    qr = q;
	}
	for (int i = 0; i < qr.recipients.length; i++) {
	    if (qq.sender.equals(qr.recipients[i]))
		return true;
	}
	return false;
    }

    public String toString() {
	return "Date: " + date + "\nFrom: " + sender + "\nSubject: " + subject;
    }
}
