/*
 * Copyright (c) 1997, 2018 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.util.concurrent.atomic.*;
import java.io.*;
import javax.mail.*;
import javax.mail.event.*;
import javax.mail.internet.*;
import javax.activation.*;

import com.sun.mail.imap.*;

/**
 * Test program for IDLE support in IMAP provider.
 *
 * Run several threads that access the folder's connection
 * while another thread runs the IDLE command.  A timer
 * thread checks that each thread is making progress, to
 * detect deadlock.
 *
 * XXX - Should have another thread add messages to the folder
 *	 to test the new message notification works properly.
 */
public class testidle {

    public static Store store;
    public static Folder folder;
    public static boolean showStructure = false;
    public static boolean saveAttachments = false;
    public static int attnum = 0;
    public static int totalTime;

    public static final AtomicBoolean stop = new AtomicBoolean(false);
    public static final AtomicInteger folderProgress = new AtomicInteger();
    public static final AtomicInteger msgProgress = new AtomicInteger();

    public static void main(String argv[]) {
	if (argv.length != 5) {
	    System.out.println(
		"Usage: testidle <host> <user> <password> <mbox> <time>");
	    System.exit(1);
	}

        try {
	    Properties props = System.getProperties();

	    // Get a Session object
	    Session session = Session.getInstance(props, null);
	    // session.setDebug(true);

	    // Get a Store object
	    store = session.getStore("imap");

	    store.addStoreListener(new StoreListener() {
		public void notification(StoreEvent e) {
		    System.out.println("StoreEvent: type " +
			e.getMessageType() + ", message " +
			e.getMessage());
		}
	    });

	    // Connect
	    store.connect(argv[0], argv[1], argv[2]);

	    // Open a Folder
	    folder = store.getFolder(argv[3]);
	    if (folder == null || !folder.exists()) {
		System.out.println("Invalid folder");
		System.exit(1);
	    }

	    folder.open(Folder.READ_WRITE);

	    // Add messageCountListener to listen for new messages
	    folder.addMessageCountListener(new MessageCountAdapter() {
		public void messagesAdded(MessageCountEvent ev) {
		    Message[] msgs = ev.getMessages();
		    System.out.println("Got " + msgs.length + " new messages");

		    // Just dump out the new messages
		    for (int i = 0; i < msgs.length; i++) {
			try {
			    System.out.println("-----");
			    System.out.println("Message " +
				msgs[i].getMessageNumber() + ":");
			    msgs[i].writeTo(System.out);
			} catch (IOException ioex) { 
			    ioex.printStackTrace();	
			} catch (MessagingException mex) {
			    mex.printStackTrace();
			}
		    }
		}
	    });

	    totalTime = Integer.parseInt(argv[4]);

	    new Thread("timer") {
		public void run() {
		    timer();
		}
	    }.start();

	    new Thread("message reader") {
		public void run() {
		    readMessages();
		}
	    }.start();

	    new Thread("folder reader") {
		public void run() {
		    readFolder();
		}
	    }.start();

	    new Thread("store idle") {
		public void run() {
		    storeIdle();
		}
	    }.start();


	    /*
	     * make sure two threads running idle works properly
	    new Thread("idle") {
		public void run() {
		    try {
			doIdle();
		    } catch (MessagingException mex) { }
		}
	    }.start();
	     */

	    doIdle();

	    System.out.println("connected " + store.isConnected());

	} catch (Exception ex) {
	    ex.printStackTrace();
	}
    }

    /**
     * Run the idle command until told to stop.
     */
    public static void doIdle() throws MessagingException {
	boolean supportsIdle = false;
	try {
	    if (folder instanceof IMAPFolder) {
		IMAPFolder f = (IMAPFolder)folder;
		f.idle();
		supportsIdle = true;
	    }
	} catch (FolderClosedException fex) {
	    throw fex;
	} catch (MessagingException mex) {
	    supportsIdle = false;
	}
	while (!stop.get()) {
	    if (supportsIdle && folder instanceof IMAPFolder) {
		IMAPFolder f = (IMAPFolder)folder;
		f.idle();
		/*
		System.out.println("IDLE done in " +
					Thread.currentThread().getName());
		 */
	    } else {
		try {
		    Thread.sleep(1000); // sleep for 1000 milliseconds
		} catch (InterruptedException ex) { }

		// This is to force the IMAP server to send us
		// EXISTS notifications. 
		folder.getMessageCount();
	    }
	}
    }

    /**
     * Monitor progress of threads and tell them to stop
     * when their time is up.
     */
    public static void timer() {
	long tend = System.currentTimeMillis() + totalTime;
	int fpcnt = folderProgress.get();
	int mpcnt = msgProgress.get();
	while (System.currentTimeMillis() < tend) {
	    try {
		Thread.sleep(1000);
	    } catch (InterruptedException ex) { }
	    int nfpcnt = folderProgress.get();
	    int nmpcnt = msgProgress.get();
	    if (!(nfpcnt > fpcnt && nmpcnt > mpcnt)) {
		System.out.println("THREAD STUCK?");
		System.out.printf("fpcnt %d nfpcnt %d\n", fpcnt, nfpcnt);
		System.out.printf("mpcnt %d nmpcnt %d\n", mpcnt, nmpcnt);
	    }
	    fpcnt = nfpcnt;
	    mpcnt = nmpcnt;
	}
	System.out.println("STOPPING");
	stop.set(true);
	try {
	    folder.getUnreadMessageCount();	// force IDLE to terminate
	} catch (MessagingException mex) { }
    }

    /**
     * Read all the messages in the folder and access all their content.
     */
    public static void readMessages() {
	try {
	    while (!stop.get()) {
		int cnt = folder.getMessageCount();
		for (int i = 1; i <= cnt; i++) {
		    //System.out.println("dump " + i);
		    Message msg = folder.getMessage(i);
		    dumpEnvelope(msg);
		    msgProgress.incrementAndGet();
		    try {
			Thread.sleep(25);
		    } catch (InterruptedException ex) { }
		}
	    }
	    System.out.println("messages DONE");
	} catch (Exception ex) {
	    System.out.println(ex);
	}
    }

    /**
     * Perform folder commands to compete with the message
     * access commands.
     */
    public static void readFolder() {
	try {
	    while (!stop.get()) {
		int cnt = folder.getUnreadMessageCount();
		store.isConnected();	// poke the store too
		folderProgress.incrementAndGet();
		try {
		    Thread.sleep(100);
		} catch (InterruptedException ex) { }
	    }
	    System.out.println("folder DONE");
	} catch (MessagingException mex) {
	    System.out.println(mex);
	}
    }

    /**
     * Run the idle command until told to stop.
     */
    public static void storeIdle() {
	boolean supportsIdle = false;
	try {
	    if (store instanceof IMAPStore) {
		IMAPStore s = (IMAPStore)store;
		s.idle();
		supportsIdle = true;
	    }
	} catch (MessagingException mex) {
	    supportsIdle = false;
	}
	try {
	    while (!stop.get()) {
		if (supportsIdle && store instanceof IMAPStore) {
		    IMAPStore s = (IMAPStore)store;
		    s.idle();
		    /*
		    */
		    System.out.println("IDLE done in " +
					    Thread.currentThread().getName());
		     /*
		     */
		} else {
		    try {
			Thread.sleep(1000); // sleep for 1000 milliseconds
		    } catch (InterruptedException ex) { }
		}
	    }
	} catch (MessagingException mex) {
	    System.out.println(mex);
	}
    }

    /**
     * Dump contents of message part.
     * (Copied from msgshow.java)
     */
    public static void dumpPart(Part p) throws Exception {
	if (p instanceof Message)
	    dumpEnvelope((Message)p);

	/** Dump input stream .. 

	InputStream is = p.getInputStream();
	// If "is" is not already buffered, wrap a BufferedInputStream
	// around it.
	if (!(is instanceof BufferedInputStream))
	    is = new BufferedInputStream(is);
	int c;
	while ((c = is.read()) != -1)
	    System.out.write(c);

	**/

	String ct = p.getContentType();
	try {
	    pr("CONTENT-TYPE: " + (new ContentType(ct)).toString());
	} catch (ParseException pex) {
	    pr("BAD CONTENT-TYPE: " + ct);
	}
	String filename = p.getFileName();
	if (filename != null)
	    pr("FILENAME: " + filename);

	/*
	 * Using isMimeType to determine the content type avoids
	 * fetching the actual content data until we need it.
	 */
	if (p.isMimeType("text/plain")) {
	    pr("This is plain text");
	    pr("---------------------------");
	    if (!showStructure && !saveAttachments)
		System.out.println((String)p.getContent());
	} else if (p.isMimeType("multipart/*")) {
	    pr("This is a Multipart");
	    pr("---------------------------");
	    Multipart mp = (Multipart)p.getContent();
	    level++;
	    int count = mp.getCount();
	    for (int i = 0; i < count; i++)
		dumpPart(mp.getBodyPart(i));
	    level--;
	} else if (p.isMimeType("message/rfc822")) {
	    pr("This is a Nested Message");
	    pr("---------------------------");
	    level++;
	    dumpPart((Part)p.getContent());
	    level--;
	} else {
	    if (!showStructure && !saveAttachments) {
		/*
		 * If we actually want to see the data, and it's not a
		 * MIME type we know, fetch it and check its Java type.
		 */
		Object o = p.getContent();
		if (o instanceof String) {
		    pr("This is a string");
		    pr("---------------------------");
		    System.out.println((String)o);
		} else if (o instanceof InputStream) {
		    pr("This is just an input stream");
		    pr("---------------------------");
		    InputStream is = (InputStream)o;
		    int c;
		    while ((c = is.read()) != -1)
			System.out.write(c);
		} else {
		    pr("This is an unknown type");
		    pr("---------------------------");
		    pr(o.toString());
		}
	    } else {
		// just a separator
		pr("---------------------------");
	    }
	}

	/*
	 * If we're saving attachments, write out anything that
	 * looks like an attachment into an appropriately named
	 * file.  Don't overwrite existing files to prevent
	 * mistakes.
	 */
	if (saveAttachments && level != 0 && !p.isMimeType("multipart/*")) {
	    String disp = p.getDisposition();
	    // many mailers don't include a Content-Disposition
	    if (disp == null || disp.equalsIgnoreCase(Part.ATTACHMENT)) {
		if (filename == null)
		    filename = "Attachment" + attnum++;
		pr("Saving attachment to file " + filename);
		try {
		    File f = new File(filename);
		    if (f.exists())
			// XXX - could try a series of names
			throw new IOException("file exists");
		    ((MimeBodyPart)p).saveFile(f);
		} catch (IOException ex) {
		    pr("Failed to save attachment: " + ex);
		}
		pr("---------------------------");
	    }
	}
    }

    public static void dumpEnvelope(Message m) throws Exception {
	pr("This is the message envelope");
	pr("---------------------------");
	Address[] a;
	// FROM 
	if ((a = m.getFrom()) != null) {
	    for (int j = 0; j < a.length; j++)
		pr("FROM: " + a[j].toString());
	}

	// TO
	if ((a = m.getRecipients(Message.RecipientType.TO)) != null) {
	    for (int j = 0; j < a.length; j++) {
		pr("TO: " + a[j].toString());
		InternetAddress ia = (InternetAddress)a[j];
		if (ia.isGroup()) {
		    InternetAddress[] aa = ia.getGroup(false);
		    for (int k = 0; k < aa.length; k++)
			pr("  GROUP: " + aa[k].toString());
		}
	    }
	}

	// SUBJECT
	pr("SUBJECT: " + m.getSubject());

	// DATE
	Date d = m.getSentDate();
	pr("SendDate: " +
	    (d != null ? d.toString() : "UNKNOWN"));

	// FLAGS
	Flags flags = m.getFlags();
	StringBuffer sb = new StringBuffer();
	Flags.Flag[] sf = flags.getSystemFlags(); // get the system flags

	boolean first = true;
	for (int i = 0; i < sf.length; i++) {
	    String s;
	    Flags.Flag f = sf[i];
	    if (f == Flags.Flag.ANSWERED)
		s = "\\Answered";
	    else if (f == Flags.Flag.DELETED)
		s = "\\Deleted";
	    else if (f == Flags.Flag.DRAFT)
		s = "\\Draft";
	    else if (f == Flags.Flag.FLAGGED)
		s = "\\Flagged";
	    else if (f == Flags.Flag.RECENT)
		s = "\\Recent";
	    else if (f == Flags.Flag.SEEN)
		s = "\\Seen";
	    else
		continue;	// skip it
	    if (first)
		first = false;
	    else
		sb.append(' ');
	    sb.append(s);
	}

	String[] uf = flags.getUserFlags(); // get the user flag strings
	for (int i = 0; i < uf.length; i++) {
	    if (first)
		first = false;
	    else
		sb.append(' ');
	    sb.append(uf[i]);
	}
	pr("FLAGS: " + sb.toString());

	// X-MAILER
	String[] hdrs = m.getHeader("X-Mailer");
	if (hdrs != null)
	    pr("X-Mailer: " + hdrs[0]);
	else
	    pr("X-Mailer NOT available");
    }

    static String indentStr = "                                               ";
    static int level = 0;

    /**
     * Print a, possibly indented, string.
     */
    public static void pr(String s) {
	/*
	if (showStructure)
	    System.out.print(indentStr.substring(0, level * 2));
	System.out.println(s);
	*/
    }
}
