package relational.email;

import java.util.*;
import java.io.*;
import java.net.*;
import java.lang.reflect.*;
import java.rmi.*;
import relational.*;
import Tools.*;

/**
 * Extract email from the relational database and spit it out as a
 * Berkeley-style mail folder. Originally designed for debugging: by
 * parsing big mailboxes into and out of my relational databases, I
 * could run a <em>diff</em> to determine if the mail had been munged
 * during the parse.
 */
public class Extract {
	public static void main(String argv[]) {
		try {
			if (argv.length<2) {
				System.err.println("usage: java Extract <destfile> <local_filename|remote>");
				System.exit(-1);
			}

			Database db;
			String destfile = argv[0];
			FileOutputStream fos = new FileOutputStream(destfile);
			OutputStreamWriter osw = new OutputStreamWriter(fos);

			String localremote = argv[1];
			if (localremote.equals("remote")) {
				InetAddress thisHost = InetAddress.getLocalHost();
				db = (Database)
					Naming.lookup("//"+thisHost.getHostName()+"/RMIDatabase");
			} else {
				db = new InternalDatabase();
				Mailbox.importMail(db, localremote);
			}

			db.createIndex(Header.f_msg);
			db.createIndex(Header.f_name);
			db.createIndex(Body.f_msg);

			Where wAlways = new WhereAlways();
			ResultSet rs = getSortedMessages(db, wAlways, "X-Folder-Order",
				null, new NumericComparator());

			Iterator iter = rs.iterator();
			while (iter.hasNext()) {
				Row row = (Row) iter.next();
				Message m = (Message) Message.f_reference.get(row);

				// print this message out
				// step 1. get headers in sorted order
				ResultSet hrs = getSortedHeaders(db, m);

				// find and print the BSD From header
				Iterator hdrIter = hrs.iterator();
				while (hdrIter.hasNext()) {
					Row hdrRow = (Row) hdrIter.next();
					Header h = (Header) Header.f_reference.get(hdrRow);
					if (h.synthetic && h.name.equals("X-BSD-From")) {
						osw.write("From "+h.value+"\n");
						break;
					}
				}

				// now print all of the nonsynthetic headers in the
				// order of their original appearance
				hdrIter = hrs.iterator();
				while (hdrIter.hasNext()) {
					Row hdrRow = (Row) hdrIter.next();
					Header h = (Header) Header.f_reference.get(hdrRow);
					if (!h.synthetic) {
						osw.write(h.name+":"+h.whitespace+h.value+"\n");
					}
				}

				// step 2. print blank line to separate headers from
				// body. No, wait, don't do that -- that was stored
				// along with the rest of the body.
				// osw.write("\n");

				// step 3. print body
				try {
					Body b = getBody(db, m);
					if (!b.body.startsWith("\n")) {
						System.err.println("Warning: body of message does not start with the customary blank line to separate it from the headers.");
					}
					osw.write(b.body);
				} catch (RuntimeException ex) {
					System.err.println(ex.toString());
					osw.write(ex.toString()+"\n");
				}
			}
			osw.close();
			System.exit(0);
		} catch (Exception ex) {
			ex.printStackTrace();
		}
	}

	public static ResultSet getSortedMessages(Database db,
		Where whereClause, String sortHeader, String defaultValue,
		Comparator comp)
		throws RemoteException {
		// if a defaultValue is supplied, the results will include
		// messages that don't have a sortHeader defined.

		Header dummyHeader=null;
		if (defaultValue!=null) {
			dummyHeader = new Header(db);
			dummyHeader.name = sortHeader;
			dummyHeader.value = defaultValue;
			dummyHeader.synthetic = true;
			dummyHeader.order = 0;	// unknown
			dummyHeader.msg_fk = new Double(0.0);	// matches no Message
		}

		FromClause fc = FromClause.create(
			new String[] { "Message", "Header" },
			new Class[] { Message.class, Header.class });
		ColumnSpec cs = FromClause.createAnonymous(Message.class)
			.getNaturalColumnSpec();

		Select sel;
		if (defaultValue!=null) {
			FromClause hufc = FromClause.create("Header", Header.class);
			ColumnSpec hucs = ColumnSpec.create(hufc,
				new String[] { "Header" },
				new FieldDescriptor[] { Header.f_msg }
				);
			// this Select statement describes the messages that
			// have 'sortHeader' defined.
			Select selHeaderUndefined = new Select(hucs, hufc,
				new WhereEquals(Header.f_name, sortHeader));

			sel = new Select(cs, fc,
				new WhereOr(
					new WhereAnd(
						whereClause,
						new WhereAnd(
							new WhereEquals(Header.f_name, sortHeader),
							new WhereJoin(Message.f_primaryKey,
								Header.f_msg)
						)
					),
					new WhereAnd(
						new WhereAnd(
							whereClause,
							new WhereNot(
								new WhereIn(Message.f_primaryKey,
									selHeaderUndefined)
							)
						),
						new WhereConstant("Header", dummyHeader)
					)
				)
			);
		} else {
			sel = new Select(cs, fc,
				new WhereAnd(
					whereClause,
					new WhereAnd(
						new WhereEquals(Header.f_name, sortHeader),
						new WhereJoin(Message.f_primaryKey,
							Header.f_msg)
					)
				)
			);
		}

		sel.setOrderBy(new OrderByOne(Header.f_value, comp));

		return db.evaluateSelect(sel);
	}

	public static ResultSet getSortedHeaders(Database db, Message m) {

		Select sel = new Select(Header.class,
			Where.equals(Header.f_msg, m.primaryKey));
		sel.setOrderBy(new OrderByOne(Header.f_order, new NumericComparator()));
		return sel.evaluate(db);
	}

	public static Body getBody(Database db, Message m) {
		Select sel = new Select(Body.class,
			Where.equals(Body.f_msg, m.primaryKey));
		ResultSet rs = sel.evaluate(db);
		if (rs.size()!=1) {
			throw new RuntimeException("Database returned "+rs.size()
				+" results for message "+m.primaryKey);
		}
		Row row = (Row) rs.iterator().next();
		return (Body) Body.f_reference.get(row);
	}
}
