import java.awt.Rectangle;
import java.text.DateFormat;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.Vector;

import processing.core.PApplet;
import processing.core.PFont;
import processing.core.PImage;
import corpus.shared.AnalyzedWords;
import corpus.shared.DataStore;
import corpus.shared.Document;
import corpus.shared.Utilities;
import corpus.shared.WordData;

/**
 * This applet displays the analyzed data from all the State of the Union
 * addresses delivered in the History of the United States as found in official
 * transcripts. Words are positioned based on their average location in the
 * document on the horizontal axis and according to a statistical measure of
 * significance on the vertical axis. Significance is determined by frequency
 * and difference in usage from the collection of addresses as a whole.
 * 
 * The data underneath the map of significant words shows trends in the length
 * of the speeches and the Flesch-Kincaid measure of language sophistication
 * which is meant to correlate with years of education in an American school (as
 * determined by sentence length and word length).
 * 
 * 
 * @author Brad Borevitz
 * @version 1.4, 01/17/07
 */
public class Display extends PApplet {

    /**
     * (note: UID for version 2.0 = 77771L.)
     */
    private static final long serialVersionUID = 77771L;

    /**
     * The directory on the server where the data files reside
     */
    static final String CorpusDirectory = "data"; // sotu

    /**
     * The URL of the server where the data files reside
     */
    String baseURL;

    /**
     * the filename of the corpus data file
     */
    static final String analyzedDataFile = "analyzedData";

    /**
     * the filename of the documents data file
     */
    static final String documentsDataFile = "documentsData";

    /**
     * should be "/"; keep for compatibility with stand-alone version
     */
    static final String filesep = System.getProperty("file.separator");

    /**
     * the corpus data object
     */
    AnalyzedWords aWords;

    /**
     * the document data object: a treeMap keyed by date
     */
    TreeMap documents;

    /**
     * date of current document as determined by mouse drag in date channel
     */
    Date mouseDate;

    Date lastDate;

    /**
     * int representing index of current document in temporal order (first is
     * 0); used for determining proper annotation and ornamentation of current
     * document
     */
    int currentDocument, lastDocument;

    float sizeScale;

    float xScale;

    float yScale;

    float wordSizeMin, wordSizeMax;

    // bounding boxes for nav and word cloud
    Rectangle cloudWindow, dataWindow, navBar, dataBar;

    int cloudMargin;

    DateFormat dateFormat, url;

    DecimalFormat floatF, floatF2, intF, intF2;

    PFont monoFont;

    List words = new Vector();

    List oldWords = new Vector();

    static final int docWordLimit = 40;

    int state = DRAW_BACKGROUND;

    LoadData loader;

    String query = "";

    static final float speed = 0.5f;

    int wordsToMove = 0;

    int UWcycles = 0;

    boolean stopped = false;

    final int bgColor = color(0); // black

    final int fgColor = color(255); // white

    final int contrastColor = color(255, 0, 0); // red

    final int transContrastColor = color(255, 0, 0, 102); // transparent red

    final int dimContrastColor = color(153, 0, 0); // dim red

    final int darkContrastColor = color(51, 0, 0); // dark red

    final int transFGColor = color(255, 204); // transparent white

    // font sizes
    float titleFont, authorFont, keyFont, dateFont, tipFont, dataFont,
            axisFont, msgFont, navFont;

    int cursor = 0;

    TreeMap distribution;

    PImage written, spoken, radio, tv, web, day, night;

    int uLimit = 25;

    String errMsg;

    Vector icons;

    Vector tips;

    TimeZone tz;

    public static final int DRAW_BACKGROUND = 0;

    public static final int LOAD_DATA = 1;

    public static final int WAITING = 2;

    public static final int GET_DOCUMENT = 3;

    public static final int ARRANGE_WORDS = 4;

    public static final int UNCOVER_WORDS = 5;

    public static final int NAVIGATE = 6;

    public static final int ROLL_OVER = 7;

    public static final int NOTHING = 8;

    public static final int OOPS = 9;

    // word drawing features bitmasks

    public static final int WORD = 1;

    public static final int HILITE = 2;

    public static final int DELTALINE = 4;

    public static final int DATA = 8;

    // document distribution data

    int WRITTEN = 1;

    int SPOKEN = 2;

    int RADIO = 4;

    int TV = 8;

    int WEB = 16;

    int DAY = 32;

    int NIGHT = 64;

    public void setup() {
        size(552, 528);
        if (online) { // online?
            baseURL = "http://" + getDocumentBase().getHost() + "/";
        } else {
            baseURL = "http://localhost/";
        }

        String[] fontRequests = { "Courier New Bold", "Courier New", "Courier" };
        monoFont = getFont(fontRequests, 32, true);

        // set inner/outer bounding boxes based on screen size
        cloudMargin = (int) (0.0125f * width);
        cloudWindow = new Rectangle(2 * cloudMargin, 2 * cloudMargin, width
                - (4 * cloudMargin), (int) (0.75f * height) - (2 * cloudMargin));
        dataWindow = new Rectangle(cloudWindow.x, cloudWindow.y
                + cloudWindow.height + (2 * cloudMargin), cloudWindow.width,
                (int) (0.25f * height) - (4 * cloudMargin));
        navBar = new Rectangle(dataWindow.x, dataWindow.y, dataWindow.width,
                (int) (dataWindow.height * 0.75f));
        dataBar = new Rectangle(dataWindow.x, dataWindow.y + navBar.height,
                dataWindow.width, (int) (dataWindow.height * 0.25f));

        wordSizeMin = 0.025f * (float) height;
        wordSizeMax = 3f * wordSizeMin;

        // initialize some stuff
        tz = TimeZone.getTimeZone("UTC");
        dateFormat = DateFormat.getDateInstance(DateFormat.MEDIUM);
        dateFormat.setTimeZone(tz);
        url = new SimpleDateFormat("yyyyMMdd");
        url.setTimeZone(tz);
        floatF = new DecimalFormat("00.0");
        floatF2 = new DecimalFormat("###0.0");
        intF = new DecimalFormat("00,000");
        intF2 = new DecimalFormat("###");

        distribution = documentDist();

        written = loadImage("written.gif");
        spoken = loadImage("spoken.gif");
        radio = loadImage("radio.gif");
        tv = loadImage("tv.gif");
        web = loadImage("web.gif");
        day = loadImage("day.gif");
        night = loadImage("night.gif");

        icons = new Vector();
        tips = new Vector();

        titleFont = 0.06f * height;
        dataFont = 0.03f * cloudWindow.height;
        axisFont = 0.0375f * cloudWindow.height;
        msgFont = 0.075f * cloudWindow.height;
        navFont = 0.0525f * cloudWindow.height;
        tipFont = 0.03f * cloudWindow.height;

        authorFont = 0.16f * dataWindow.height;
        keyFont = 0.14f * dataWindow.height;
        dateFont = 0.15f * dataWindow.height;

        textFont(monoFont);
    }

    public void draw() {

        cursor(cursor);

        if (state > UNCOVER_WORDS && state < OOPS) {
            // if necessary change state
            if (navBar.contains(mouseX, mouseY) && mousePressed) {
                state = NAVIGATE;
            } else if (cloudWindow.contains(mouseX, mouseY)) {
                state = ROLL_OVER;
            } else {
                state = NOTHING;
            }
        }

        background(0);

        switch (state) {
        case DRAW_BACKGROUND:
            // put something on the screen before loading or anything else

            // drawTitle();
            drawMessage(" Loading Data ", msgFont);
            // drawFrame();

            state = LOAD_DATA;
            break;

        case LOAD_DATA:
            // hand off the data loading task to a thread
            loader = new LoadData("DefaultData");
            // loader.setPriority(Thread.NORM_PRIORITY);
            loader.start();

            state = WAITING;
            break;

        case WAITING:
            // TODO make some animation here

            // drawTitle();
            drawMessage(" Loading Data ", msgFont);
            // drawFrame();

            if (!loader.isAlive()) {
                if (aWords != null && documents != null) {
                    state = GET_DOCUMENT;
                } else {
                    // connection error
                    errMsg = "A connection error occurred.";
                    state = OOPS;
                }
            }
            break;

        case GET_DOCUMENT:

            cursor = ARROW;
            stopped = false;
            setWords();
            loadIcons();

            // drawTitle();
            drawNav();
            drawWords(WORD);
            // drawFrame();

            state = ARRANGE_WORDS;
            break;

        case ARRANGE_WORDS:
            // unused words fallout, reused words move

            if (wordsToMove > 0) {
                moveWords();
            } else {
                state = UNCOVER_WORDS;
                UWcycles = 0;
                uLimit = 25;
            }

            // drawTitle();
            drawNav();
            drawWords(WORD);
            // drawFrame();

            break;

        case UNCOVER_WORDS:
            // move words around to uncover them

            uncoverWords();
            if (wordsToMove <= 0 || UWcycles > 35) {
                state = NOTHING;
            }

            // drawTitle();
            drawNav();
            drawWords(WORD | DELTALINE);
            // drawFrame();

            break;

        case NAVIGATE:
            // user selects new document: redraw nav

            // continuously change date in order to get "drag" effect
            setMouseDate();
            drawNav();
            drawNavOver();
            drawMessage(" " + ((Document) documents.get(mouseDate)).getAuthor()
                    + " :: " + dateFormat.format(mouseDate) + " ", navFont);
            // drawFrame();
            break;

        case ROLL_OVER:
            // user rolls and clicks on words: redraw, animate rollovers

            // background(0);
            drawAxes();
            drawNav();
            drawWords(WORD);
            drawMouseOvers();
            // drawFrame();

            break;

        case NOTHING:

            if (!focused && stopped) {
                drawMessage("select an address", 0.075f * cloudWindow.height);
            }
            drawNav();
            if (cursor == HAND) {
                drawNavOver();
            }
            drawWords(WORD);

            // test for icon rollovers
            if (dataBar.contains(mouseX, mouseY)) {
                drawIconTips();
            }

            // drawFrame();
            break;

        default:
            background(0);
            drawMessage(errMsg, 0.075f * cloudWindow.height);
            break;
        }
    }

    public void mouseReleased() {
        if (navBar.contains(mouseX, mouseY) && state > WAITING) {
            setMouseDate();
            setScales();

            state = GET_DOCUMENT;
        }
    }

    public void mouseMoved() {
        if (navBar.contains(mouseX, mouseY) && state > UNCOVER_WORDS) {
            cursor = HAND;
        } else if (dataBar.contains(mouseX, mouseY) && state > UNCOVER_WORDS) {
            cursor = ARROW;
        }
    }

    public void mousePressed() {
        if (cloudWindow.contains(mouseX, mouseY) && state > WAITING) {
            if (!query.equals("")) {
                link(baseURL + "texts/" + url.format(mouseDate) + ".html?"
                        + query, "more");
            } else {
                link(timeLineUrl(), "more");
            }
        }
        if (dataBar.contains(mouseX, mouseY) && state > WAITING) {
            link(timeLineUrl(), "more");
        }
        if (navBar.contains(mouseX, mouseY)) {
            lastDate = mouseDate;
            lastDocument = currentDocument;
        }
    }

    public void keyPressed() {
        if (key == CODED && state >= ARRANGE_WORDS) {
            if (keyCode == RIGHT) {
                if (currentDocument < aWords.getDateList().size() - 1) {
                    lastDate = mouseDate;
                    lastDocument = currentDocument;
                    currentDocument++;
                    mouseDate = (Date) aWords.getDateList()
                            .get(currentDocument);
                    setScales();
                    state = GET_DOCUMENT;
                }
            } else if (keyCode == LEFT) {
                if (currentDocument > 0) {
                    lastDate = mouseDate;
                    lastDocument = currentDocument;
                    currentDocument--;
                    mouseDate = (Date) aWords.getDateList()
                            .get(currentDocument);
                    setScales();
                    state = GET_DOCUMENT;
                }
            } else if (keyCode == UP) {
                state = UNCOVER_WORDS;
                UWcycles = 0;
                uLimit = uLimit + 5;
            } else {
            }
            // do nothing
        }
    }

    /**
     * Draw the words of the current document
     * 
     * @param features
     *            integer coded for features
     */
    public void drawWords(int features) {

        // iterate through old word array
        int contrast = (state == ROLL_OVER || (state >= GET_DOCUMENT && state <= UNCOVER_WORDS)) ? dimContrastColor
                : darkContrastColor;
        fill(contrast);
        for (int i = 0; i < oldWords.size(); i++) {
            ((AWord) oldWords.get(i)).draw();
        }

        // iterate through word array
        fill(fgColor);
        for (int i = 0; i < words.size(); i++) {
            AWord w = (AWord) words.get(i);
            if ((features & DELTALINE) == DELTALINE)
                w.deltaLine();
            if ((features & WORD) == WORD)
                w.draw();
            if ((features & HILITE) == HILITE)
                w.hilite();
            if ((features & DATA) == DATA)
                w.data();
        }
    }

    /**
     * draw navigation box
     */
    public void drawNav() {

        int docs = documents.size();
        float w = ((float) dataWindow.width) / docs;

        noStroke();

        Set docSet = documents.entrySet();
        Iterator doc = docSet.iterator();
        int i = 0;

        while (doc.hasNext()) {
            Map.Entry entry = (Map.Entry) doc.next();
            Document document = (Document) entry.getValue();

            // draw bar for number of words
            if (i == currentDocument) {
                fill(contrastColor);
            } else {
                fill(fgColor);
            }
            float h = 0.5f * dataWindow.height * document.getNumberOfWords()
                    / aWords.getCorpMaxNumberOfWords();
            rect(dataWindow.x + (w * i), (dataWindow.y - h)
                    + (0.5f * dataWindow.height), w, h);

            // draw date
            textSize(dateFont);
            float dateY = dataWindow.y + 0.5f * dataWindow.height
                    + (2 * cloudMargin) + textAscent() - 3;
            if (i == currentDocument) {
                textAlign(LEFT);
                // draw last date
                if (!lastDate.equals(mouseDate)) {

                    if (state == NAVIGATE) {
                        fill(dimContrastColor); // dim red
                    } else {
                        fill(contrastColor); // red
                    }
                    String date = dateFormat.format(lastDate);
                    text(
                            date,
                            dataWindow.x
                                    + (w * lastDocument)
                                    - (textWidth(date) * lastDocument / (float) (docs - 1)),
                            dateY);
                }
                // draw current date
                if (state == NAVIGATE) {
                    fill(transFGColor); // dim white
                } else {
                    fill(fgColor); // white
                }
                String date = dateFormat.format(mouseDate);
                text(date, dataWindow.x + (w * i)
                        - (textWidth(date) * i / (float) (docs - 1)), dateY);
            }

            // draw squares for Flesch
            if (i == currentDocument) {
                fill(fgColor);
            } else {
                fill(contrastColor);
            }
            h = 0.5f * (float) dataWindow.height * document.getFleschKincaid()
                    / (float) aWords.getCorpMaxFleschKincaid();
            rect(dataWindow.x + (w * i), (dataWindow.y - h)
                    + (0.5f * (float) dataWindow.height), w, w);

            // draw remaining data
            if (i == currentDocument) {
                textAlign(RIGHT);
                fill(fgColor);
                textSize(keyFont);
                String s = "Grade Level:   "
                        + align(floatF.format(document.getFleschKincaid()));
                float x = dataWindow.x + dataWindow.width;
                float y = dataWindow.y + dataWindow.height - 0.135f
                        * (float) dataWindow.height;
                text(s, x, y);
                // add key
                fill(contrastColor); // red
                rect(x - textWidth(s) - 2 * w, y - textAscent() / 2, w, w);
                fill(fgColor);
                s = "Words in Speech: "
                        + align(intF.format(document.getNumberOfWords()));
                x = dataWindow.x + dataWindow.width;
                y = dataWindow.y + dataWindow.height;
                text(s, x, y);
                // add key
                x = x - textWidth(s) - 2 * w;
                rect(x, y - textAscent(), w, textAscent());
                // add icons at bottom middle of dataWindow
                x = dataWindow.x + dataWindow.width / 2 - 48;
                y = dataWindow.y + dataWindow.height - 16;
                int dist = ((Integer) distribution.get(mouseDate)).intValue();
                if (is(dist, DAY)) {
                    image(day, x, y);
                } else if (is(dist, NIGHT)) {
                    image(night, x, y);
                }
                x = x + 16;
                if (is(dist, WRITTEN)) {
                    image(written, x, y);
                }
                x = x + 16;
                if (is(dist, SPOKEN)) {
                    image(spoken, x, y);
                }
                x = x + 16;
                if (is(dist, RADIO)) {
                    image(radio, x, y);
                }
                x = x + 16;
                if (is(dist, TV)) {
                    image(tv, x, y);
                }
                x = x + 16;
                if (is(dist, WEB)) {
                    image(web, x, y);
                }

                textAlign(LEFT);
                textSize(authorFont);
                text(document.getAuthor(), dataWindow.x, dataWindow.y
                        + dataWindow.height);
            }
            i++;
        }

    }

    /**
     * draw dot where mouse is and highlight the nav
     */
    public void drawNavOver() {
        if (!mousePressed) {
            fill(transContrastColor);
            stroke(fgColor);
            rect(navBar.x, navBar.y, navBar.width, navBar.height);
        }
        float y = dataWindow.y + 0.5f * dataWindow.height + cloudMargin;
        fill(fgColor);
        noStroke();
        smooth();
        if (mouseX < navBar.x + navBar.width - 2 * cloudMargin
                && mouseX > navBar.x - cloudMargin) {
            triangle(mouseX + cloudMargin, y + cloudMargin, mouseX + 2
                    * cloudMargin, y, mouseX + cloudMargin, y - cloudMargin);
        }
        if (mouseX > navBar.x + 2 * cloudMargin
                && mouseX < navBar.x + navBar.width + cloudMargin) {
            triangle(mouseX - cloudMargin, y + cloudMargin, mouseX - 2
                    * cloudMargin, y, mouseX - cloudMargin, y - cloudMargin);
        }
        noSmooth();
    }

    /**
     * Draw a boxed message in center of cloudWindow at specified size
     * 
     * @param message
     * @param size
     *            height of letters
     * 
     */
    public void drawMessage(String message, float size) {
        int x = cloudWindow.x + cloudWindow.width / 2;
        int y = cloudWindow.y + cloudWindow.height / 2;
        textAlign(CENTER);
        textFont(monoFont);
        textSize(size);
        fill(bgColor);
        stroke(fgColor);
        rect(x - (textWidth(message) / 2), y - size + textDescent(),
                textWidth(message), size);
        fill(fgColor);
        text(message, x, y + 1);
    }

    /**
     * Draw a message with the state in the upper left corner (debug tool)
     * 
     */
    public void drawState() {
        textAlign(LEFT);
        textSize(cloudWindow.height / 10f);
        fill(fgColor);
        noStroke();
        text(state + " " + focused + " " + words.size(), cloudWindow.x + 1,
                cloudWindow.y + textAscent() + 1);
    }

    /**
     * Draw the title of the app
     */
    public void drawTitle() {
        textAlign(LEFT);
        fill(contrastColor);
        textSize(titleFont);
        text("State of the Union", cloudWindow.x, 0.066667f * (float) height);
    }

    /**
     * Draw a frame around the word graph; should be last drawn.
     */
    public void drawFrame() {
        noFill();
        stroke(fgColor);
        rect(cloudWindow.x, cloudWindow.y, cloudWindow.width,
                cloudWindow.height);
    }

    /**
     * create mouseOvers for words: highlights and frequency data
     */
    public void drawMouseOvers() {
        // test through words array for mouseover
        boolean data = false;
        query = "";
        cursor = ARROW;
        for (int i = 0; i < words.size(); i++) {
            AWord w = (AWord) words.get(i);
            if (w.box.contains(mouseX, mouseY)) {
                cursor = CROSS;
                // compile query string
                query += "q=" + w.word + "&";
                // draw delta
                w.deltaLine();
                // hilite word
                w.hilite();
                // draw freq data only once
                if (!data) {
                    w.data();
                    data = true;
                }
            }
        }
    }

    /**
     * draw axes
     */
    public void drawAxes() {
        fill(fgColor);
        stroke(fgColor);
        textSize(axisFont);
        // x axis
        String xLabel = "average position";
        text(xLabel, cloudWindow.x + 2 * cloudMargin, textAscent());
        line(cloudWindow.x, textAscent() / 2, cloudWindow.x + cloudMargin,
                textAscent() / 2);
        line(cloudWindow.x + textWidth(xLabel) + 3 * cloudMargin,
                textAscent() / 2, cloudWindow.x + cloudWindow.width,
                textAscent() / 2);
        // y axis
        String yLabel = "relative frequency";
        pushMatrix();
        translate(cloudWindow.y + textDescent() - textAscent(), cloudWindow.x
                + 2 * cloudMargin);
        rotate(PI / 2);
        text(yLabel, 0, 0);
        // text(yLabel, cloudWindow.x, textAscent());
        popMatrix();
        line(cloudWindow.x - textAscent() / 2, cloudWindow.y, cloudWindow.x
                - textAscent() / 2, cloudWindow.y + cloudMargin);
        line(cloudWindow.x - textAscent() / 2, cloudWindow.y
                + textWidth(yLabel) + 3 * cloudMargin, cloudWindow.x
                - textAscent() / 2, cloudWindow.y + cloudWindow.height);
    }

    /**
     * set mousedate to a date based on position of the mouse
     */
    public void setMouseDate() {
        List dateList = aWords.getDateList();
        int idx = (int) ((float) (dateList.size() * (mouseX - navBar.x)) / (float) (navBar.width));
        if (idx < 0) {
            mouseDate = (Date) dateList.get(0);
            currentDocument = 0;
        } else if (idx > dateList.size() - 1) {
            mouseDate = (Date) dateList.get(dateList.size() - 1);
            currentDocument = dateList.size() - 1;
        } else {
            mouseDate = (Date) dateList.get(idx);
            currentDocument = idx;
        }
    }

    /**
     * test for mouse on icons and draw appropriate tool-tips
     */
    public void drawIconTips() {
        textSize(tipFont);

        for (int i = 0; i < icons.size(); i++) {
            if (((Rectangle) icons.get(i)).contains(mouseX, mouseY)) {
                String tip = (String) tips.get(i);
                float tipW = textWidth(tip);
                // draw background
                fill(transFGColor);
                stroke(fgColor);
                rect(mouseX - tipW / 2, mouseY - tipFont, tipW, tipFont);

                // draw tool-tip
                fill(bgColor);
                noStroke();
                textAlign(LEFT);
                text(tip, mouseX - tipW / 2, mouseY - textDescent());
            }
        }
    }

    /**
     * load icon locations into array
     */
    public void loadIcons() {
        icons.clear();
        tips.clear();

        int x = dataWindow.x + dataWindow.width / 2 - 48;
        int y = dataWindow.y + dataWindow.height - 16;

        int dist = ((Integer) distribution.get(mouseDate)).intValue();
        if (is(dist, DAY)) {
            icons.add(new Rectangle(x, y, 16, 16));
            tips.add("Given during the day.");
        } else if (is(dist, NIGHT)) {
            icons.add(new Rectangle(x, y, 16, 16));
            tips.add("Given during the evening.");
        }
        x = x + 16;
        if (is(dist, WRITTEN)) {
            icons.add(new Rectangle(x, y, 16, 16));
            tips.add("Delivered as written message.");
        }
        x = x + 16;
        if (is(dist, SPOKEN)) {
            icons.add(new Rectangle(x, y, 16, 16));
            tips.add("Delivered as spoken message.");
        }
        x = x + 16;
        if (is(dist, RADIO)) {
            icons.add(new Rectangle(x, y, 16, 16));
            tips.add("Broadcast on radio.");
        }
        x = x + 16;
        if (is(dist, TV)) {
            icons.add(new Rectangle(x, y, 16, 16));
            tips.add("Broadcast on TV.");
        }
        x = x + 16;
        if (is(dist, WEB)) {
            icons.add(new Rectangle(x, y, 16, 16));
            tips.add("Distributed via the internet.");
        }

    }

    /**
     * calculate the scaling factors for word size and position
     */
    public void setScales() {
        int sWidth = cloudWindow.width - cloudMargin * 2;
        int sHeight = cloudWindow.height - cloudMargin * 2;

        sizeScale = (wordSizeMax - wordSizeMin)
                / ((float) ((Document) documents.get(mouseDate))
                        .getMaxFreqAsPercent() - (float) ((Document) documents
                        .get(mouseDate)).getMinFreqAsPercent());

        // x axis is between 0 and 1: so just multiply by width
        xScale = (float) sWidth;

        yScale = (float) (sHeight)
                / (((Document) documents.get(mouseDate)).getMaxS() - ((Document) documents
                        .get(mouseDate)).getMinS());
    }

    /**
     * Get the words in the document (based on mouseDate) and populate word
     * array. setScales must be called prior to setWords.
     */
    public void setWords() {
        // get new list and convert to AWords List

        // reset icons array
        icons.clear();

        // save old words for comparison
        oldWords.clear();
        for (int i = 0; i < words.size(); i++) {
            oldWords.add(new AWord((AWord) words.get(i)));
        }

        List newWords = new Vector();
        Document d = (Document) documents.get(mouseDate);
        for (int i = 0; i < d.getWordList().size(); i++) {
            WordData wd = (WordData) d.getWordList().get(i);
            // enter words that appear more than thrice if length of speech >
            // 5000 words
            if (wd.getFreq() >= 4
                    || (d.getNumberOfWords() < 5000 && wd.getFreq() >= 3)
                    || (d.getNumberOfWords() < 3000 && wd.getFreq() >= 2)
                    || (d.getNumberOfWords() < 2000)
                    && wd.getS() >= d.getMaxS() * 0.75f) {
                newWords.add(new AWord(wd, d));
            }
        }
        // iterate through old list
        for (int i = 0; i < words.size(); i++) {
            AWord oldWord = (AWord) words.get(i);
            // check if old word is in new list
            int index = newWords.indexOf((AWord) words.get(i));
            if (index >= 0) {
                // if true set new values
                AWord newWord = (AWord) newWords.get(index);
                oldWord.newSize = newWord.newSize;
                oldWord.newBox = new Rectangle(newWord.newBox);
                oldWord.newx = newWord.newBox.x;
                oldWord.newy = newWord.newBox.y;
                // remove word from new list
                newWords.remove(oldWord);
            } else {
                // if false set values to drop out
                oldWord.newSize = 0f;
                oldWord.newBox = new Rectangle(oldWord.box);
                oldWord.newBox.y = cloudWindow.y + cloudWindow.height;
            }
        }
        // add remaining new words to words
        words.addAll(newWords);
        wordsToMove = words.size();
    }

    /**
     * move all the words to new locations
     */
    public void moveWords() {
        wordsToMove = 0;
        for (int i = 0; i < words.size(); i++) {
            AWord w = (AWord) words.get(i);
            // if the old and new position or size are different
            if (!(w.box.equals(w.newBox)) || !(w.size == w.newSize)) {
                // if not in document and at bottom of window delete, else move
                if ((cloudWindow.y + cloudWindow.height - w.box.y < cloudMargin || w.size < 3)
                        && w.newSize == 0f) {
                    words.remove(i);
                } else {
                    // if its close snap in
                    if (Math.abs(w.size - w.newSize) < 2
                            && Math.abs(w.box.x - w.newBox.x) < 2
                            && Math.abs(w.box.y - w.newBox.y) < 2) {
                        w.box.setBounds(w.newBox);
                        w.size = w.newSize;
                    } else {
                        wordsToMove++;
                        w.move();
                    }
                }
            }
        }
    }

    /**
     * move all the words so just enough so they don't overlap
     */
    public void uncoverWords() {
        UWcycles++;
        wordsToMove = 0;
        for (int i = 0; i < words.size(); i++) {
            AWord w = ((AWord) words.get(i));
            w.uncover();
            if (w.over > 0) {
                wordsToMove++;
            }
        }
    }

    /**
     * @return String containing URL of Wikipedia timeline based on decade of
     *         date the mouse is in
     */
    public String timeLineUrl() {
        Calendar c = Calendar.getInstance();
        c.setTime(mouseDate);
        int decade = 10 * floor(c.get(Calendar.YEAR) / 10f);
        String era;

        switch (decade) {
        case 1790:
        case 1800:
        case 1810:
            era = "1790-1819";
            break;
        case 1820:
        case 1830:
        case 1840:
        case 1850:
            era = "1820-1859";
            break;
        case 1860:
        case 1870:
        case 1880:
        case 1890:
            era = "1860-1899";
            break;
        case 1900:
        case 1910:
        case 1920:
            era = "1900-1929";
            break;
        case 1930:
        case 1940:
            era = "1930-1949";
            break;
        case 1950:
        case 1960:
            era = "1950-1969";
            break;
        case 1970:
        case 1980:
            era = "1970-1989";
            break;
        case 1990:
        case 2000:
            era = "1990-pres";
            break;
        default:
            era = "";
            break;
        }

        return baseURL + "timelines/Timeline_US_hist_" + era + ".html#"
                + decade + "s";
    }

    /**
     * @param s
     * @return String with "0" replaced by space
     */
    public String align(String s) {
        int i = 0;
        while (s.charAt(i) == '0')
            i++;
        return s.substring(0, i + 1).replace('0', ' ') + s.substring(i + 1);
    }

    /**
     * fill text to specified number of characters
     * 
     * @param num
     * @param s
     * @return String
     */
    public String fill(int num, String s) {
        while (s.length() < num) {
            s = " " + s;
        }
        return s;
    }

    /**
     * get a requested font or use "Monospaced"
     * 
     * @param request
     * @param size
     * @param smooth
     * @return PFont
     */
    public PFont getFont(String[] request, int size, boolean smooth) {
        // boolean hasIt = false;
        String it = "";
        String[] fontList = PFont.list();
        for (int i = 0; i < fontList.length; i++) {
            for (int j = 0; j < request.length; j++) {
                if (fontList[i].equals(request[j])) {
                    it = request[j];
                    break;
                }
            }
        }
        if (!it.equals("")) {
            return createFont(it, size, smooth);
        } else {
            return createFont("Monospaced", size, smooth);
        }
    }

    /**
     * @author Brad Borevitz
     * 
     */
    public class AWord implements Comparable {
        // word intrinsic properties
        String word;

        int freq;

        float S;

        float position;

        float freqAsPercent;

        float corpFreqAsPecent;

        int df;

        float tfidf;

        // word derivative properties
        float size;

        // bounding box x,y are from top left corner to match everything else
        // note: newBox preserves the values of location based on actual data
        // even after the word is moved to uncover words, that way it can be
        // used
        // to traw the deltaLine. But it is synced up when new words are set.
        Rectangle box, newBox;

        // for arranging and uncovering
        float newSize;

        float newx, newy;

        float angle = 0; // Angle of movement

        int over; // Number of overlaps

        /**
         * Construct word object for WordData
         * 
         * @param wd
         *            WordData of new word
         * @param d
         *            Current Document
         */
        AWord(WordData wd, Document d) {
            // unpack the object
            word = wd.getWord();
            freq = wd.getFreq();
            S = wd.getS();
            position = wd.getPosition();
            freqAsPercent = wd.getFreqAsPercent();
            corpFreqAsPecent = wd.getCorpFreqAsPercent();
            df = wd.getDF();
            tfidf = wd.getTFIDF();

            // derive the font size and bounding box
            size = 0f;
            newSize = ((float) (wd.getFreqAsPercent()) - d
                    .getMinFreqAsPercent())
                    * sizeScale + wordSizeMin;

            // set font because calculations depend on size of text.
            textSize(newSize);

            // dimensions
            float w = textWidth(word);
            // h = size; so not necessary to calc

            float x = cloudWindow.x + cloudMargin + position * xScale;
            // shift x to account for gradual alignment to edges
            // (none on the left, the length of the word on the right)
            x = x - w * position;

            // calculate y by starting from inside bottom margin and subtracting
            // the scaled difference between the valuse of S and the minimum
            float y = ((cloudWindow.y + cloudWindow.height - cloudMargin) - (S - d
                    .getMinS())
                    * yScale);
            // shift y to account for gradual alignment to edges
            // (none on the top, the height of the word on the bottom)
            y = y - newSize * (d.getMaxS() - S) / (d.getMaxS() - d.getMinS());

            // create box
            box = new Rectangle((int) x, cloudWindow.y + cloudMargin, 0, 0);
            newBox = new Rectangle((int) x, (int) y, (int) w, (int) newSize);
            newx = x;
            newy = y;
        }

        /**
         * Construct empty word object
         */
        AWord() {
            word = "";
            freq = 0;
            S = 0;
            position = 0;
            size = 0;
            box = new Rectangle();
            newBox = box;
            newSize = 0;
            newx = 0;
            newy = 0;
            df = 0;
            tfidf = 0f;
        }

        AWord(AWord aword) {
            word = aword.word;
            freq = aword.freq;
            S = aword.S;
            position = aword.position;
            size = aword.size;
            box = new Rectangle(aword.box);
            newBox = new Rectangle(aword.newBox);
            newSize = aword.newSize;
            newx = aword.newx;
            newy = aword.newy;
            df = aword.df;
            tfidf = aword.tfidf;
        }

        /**
         * draw the word
         */
        public void draw() {
            if (!(size == 0)) {
                textAlign(LEFT);
                // fill(fgColor);
                noStroke();
                textSize(size);
                text(word, box.x, box.y + textAscent());
            }
        }

        /**
         * hilite the word
         */
        public void hilite() {
            fill(transContrastColor);
            stroke(fgColor);
            rect(box.x, box.y, box.width, box.height);
        }

        /**
         * draw a line from the center of the current location of the word to
         * the location according to the data
         */
        public void deltaLine() {
            smooth();
            noFill();
            stroke(fgColor);
            line(getCenterX(), getCenterY(), newBox.x + newBox.width / 2,
                    newBox.y + newBox.height / 2);
            fill(fgColor);
            ellipse(newBox.x + newBox.width / 2, newBox.y + newBox.height / 2,
                    cloudWindow.height * 0.0125f, cloudWindow.height * 0.0125f);
            noSmooth();
        }

        /**
         * draw the word's data
         */
        public void data() {

            textSize(dataFont);
            textLeading(dataFont);

            String data = "\""
                    + word
                    + "\"\nFrequency in text: "
                    + fill(5, Integer.toString(freq))
                    + "\nPer 10k in text:   "
                    + fill(5, intF2.format(freqAsPercent * 10000))
                    + "\nPer 10k in corpus: "
                    + fill(5, (((corpFreqAsPecent * 10000) >= 1) ? intF2
                            .format(corpFreqAsPecent * 10000) : "<1"))
                    + "\nDocument frequency:" + fill(5, Integer.toString(df))
                    + "\nRelative frequency:" + fill(5, intF2.format(S))
                    + "\nAverage position:  "
                    + fill(5, intF2.format(position * 100));

            int dataW = (int) (cloudWindow.width / 3f + cloudMargin);
            int dataH = (int) (7 * dataFont + cloudMargin);
            int dataX = (getCenterX() < cloudWindow.x + cloudWindow.width / 2) ? cloudWindow.x
                    + cloudMargin
                    : cloudWindow.x + cloudWindow.width - (dataW + cloudMargin);
            int dataY = (getCenterY() < cloudWindow.y + cloudWindow.height / 2) ? cloudWindow.y
                    + cloudWindow.height - (dataH + cloudMargin)
                    : cloudWindow.y + cloudMargin;

            // draw data background
            fill(transFGColor);
            stroke(fgColor);
            rect(dataX, dataY, dataW, dataH);

            // draw line
            smooth();
            if (getCenterY() < cloudWindow.y + cloudWindow.height / 2) {
                line(getCenterX(), box.y + box.height, dataX + dataW / 2, dataY);
            } else {
                line(getCenterX(), box.y, dataX + dataW / 2, dataY + dataH);
            }
            noSmooth();

            // draw data
            fill(bgColor);
            noStroke();
            textAlign(LEFT);
            dataX = dataX + cloudMargin / 2;
            dataY = dataY + cloudMargin / 2;
            text(data, dataX, dataY, dataW - cloudMargin / 2, dataH);
        }

        /**
         * moves word from previous position towards the new one: interpolates
         * between old and new size and box based on speed parameter.
         */
        public void move() {
            size = size + speed * (newSize - size);
            box.x = (int) (box.x + Math.ceil(speed * (newBox.x - box.x)));
            box.y = (int) (box.y + Math.ceil(speed * (newBox.y - box.y)));
            // set font because calculations depend on size of text.
            textSize(size);
            box.width = (int) textWidth(word);
            box.height = (int) size;
            // snyc these 'casue they aren't elsewhere
            newx = newBox.x;
            newy = newBox.y;
        }

        /**
         * sync up all the location info with current
         */
        public void sync() {
            newx = box.x;
            newy = box.y;
            newBox.setBounds(box);
        }

        public float getCenterX() {
            return (float) (newx + box.width / 2f);
        }

        public float getCenterY() {
            return (float) (newy + box.height / 2f);
        }

        public void nudge(float a, float r) {
            newx = newx + cos(a) * r * 2;
            newy = newy + sin(a) * r * 2;
        }

        public void uncover() {
            // Constrain to cloudWindow
            if (newx < cloudWindow.x + cloudMargin) {
                newx = cloudWindow.x + cloudMargin;
            }
            if (newx + box.width > cloudWindow.x + cloudWindow.width
                    - cloudMargin) {
                newx = cloudWindow.x + cloudWindow.width - box.width
                        - cloudMargin;
            }
            if (newy < cloudWindow.y + cloudMargin) {
                newy = cloudWindow.y + cloudMargin;
            }
            if (newy + box.height > cloudWindow.y + cloudWindow.height
                    - cloudMargin) {
                newy = (cloudWindow.y + cloudWindow.height)
                        - (cloudMargin + box.height);
            }

            // Constrain from wandering too far from home
            int xLimit = uLimit * 2;
            int yLimit = uLimit;

            if (newx < newBox.x - xLimit) {
                newx = newBox.x - xLimit;
            }
            if (newx > newBox.x + xLimit) {
                newx = newBox.x + xLimit;
            }
            if (newy < newBox.y - yLimit) {
                newy = newBox.y - yLimit;
            }
            if (newy > newBox.y + yLimit) {
                newy = newBox.y + yLimit;
            }

            // Interpolate
            float tempx = newx - box.x;
            if (abs(tempx) > 0.1) {
                box.x += tempx / 4.0;
            }
            float tempy = newy - box.y;
            if (abs(tempy) > 0.1) {
                box.y += tempy / 4.0;
            }

            over = 0;

            for (int i = 0; i < words.size(); i++) {
                AWord w = (AWord) words.get(i);
                if (!this.equals(w)) { // if not the same
                    float dx = w.getCenterX() - getCenterX();
                    float dy = w.getCenterY() - getCenterY();

                    // If overlap
                    if (box.intersects(w.box)) {
                        float rA = atan2(dy, dx);
                        if (rA == 0) {
                            rA = random(1);
                        }
                        w.nudge(rA, speed);
                        nudge(rA + PI, speed);
                        over++; // Count the number of rects touching
                    }
                }
            }
            // Spin if touching another cell
            if (over > 0) {
                angle += over * ((29 - box.width) / 100);
            }
        }

        /**
         * required to do searches
         */
        public boolean equals(Object obj) {

            if (obj == this) {
                return true;
            } else if (!(obj instanceof AWord)) {
                return false;
            } else {
                AWord w = (AWord) obj;
                return word.equals(w.word);
            }
        }

        /**
         * required for completeness
         */
        public int hashCode() {
            // assert false : "hashCode not designed";
            return 42; // any arbitrary constant will do
        }

        /**
         * required to do searches and sorts; ignores case
         */
        public int compareTo(Object obj) {
            AWord w = (AWord) obj;
            // return word.compareTo(w.getWord());
            return word.compareToIgnoreCase(w.word);
        }
    }

    /**
     * Class to load data using a thread
     * 
     * @author Brad Borevitz
     * 
     */
    public class LoadData extends Thread {
        /**
         * @param str
         */
        public LoadData(String str) {
            super(str);
        }

        public void run() {
            if (online) { // online
                String inFile = baseURL + CorpusDirectory + "/"
                        + analyzedDataFile;

                println("getting remote data from" + inFile); // debug

                aWords = (AnalyzedWords) DataStore.readObjectFromURL(inFile);
                inFile = baseURL + CorpusDirectory + "/" + documentsDataFile;
                documents = (TreeMap) DataStore.readObjectFromURL(inFile);

                if (aWords != null && documents != null) {
                    // set date and get ready to display now that we have data
                    mouseDate = lastDate = (Date) aWords.getDateList().get(0);
                    currentDocument = 0;
                    setScales();
                } else {
                    state = OOPS;
                }
            } else {
                String inFile = System.getProperty("user.home") + filesep
                        + "Documents/workspace/sotu/data/Corpus" + filesep
                        + analyzedDataFile;

                println("getting local data from" + inFile); // debug

                aWords = (AnalyzedWords) DataStore.readObjectFromFile(inFile);

                inFile = System.getProperty("user.home") + filesep
                        + "Documents/workspace/sotu/data/Corpus" + filesep
                        + documentsDataFile;
                documents = (TreeMap) DataStore.readObjectFromFile(inFile);
                if (aWords != null && documents != null) {
                    // set date and get ready to display now that we have
                    // data
                    mouseDate = lastDate = (Date) aWords.getDateList().get(0);
                    currentDocument = 0;
                    setScales();
                } else {
                    state = OOPS;
                }
            }
        }
    }

    public void stop() {
        stopped = true;
    }

    public boolean is(int flag, int mask) {
        return ((flag & mask) == mask);
    }

    /**
     * load up doc dist data
     * 
     * @return TreeMap with speech data
     */
    public TreeMap documentDist() {
        TreeMap dist = new TreeMap();
        dist
                .put(Utilities.stringToDate("January 8, 1790"), new Integer(
                        SPOKEN));
        dist.put(Utilities.stringToDate("December 8, 1790"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("October 25, 1791"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("November 6, 1792"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 3, 1793"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("November 19, 1794"), new Integer(
                SPOKEN));
        dist.put(Utilities.stringToDate("December 8, 1795"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 7, 1796"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("November 22, 1797"), new Integer(
                SPOKEN));
        dist.put(Utilities.stringToDate("December 8, 1798"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 3, 1799"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("November 11, 1800"), new Integer(
                SPOKEN));
        dist.put(Utilities.stringToDate("December 8, 1801"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 15, 1802"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("October 17, 1803"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("November 8, 1804"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1805"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1806"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("October 27, 1807"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("November 8, 1808"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("November 29, 1809"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1810"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("November 5, 1811"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("November 4, 1812"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1813"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("September 20, 1814"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1815"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1816"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 12, 1817"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("November 16, 1818"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1819"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("November 14, 1820"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1821"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1822"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1823"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1824"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1825"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1826"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1827"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1828"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1829"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1830"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1831"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1832"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1833"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 1, 1834"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1835"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1836"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1837"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1838"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1839"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1840"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1841"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1842"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1843"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1844"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1845"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1846"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1847"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1848"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1849"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1850"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1851"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1852"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1853"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1854"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 31, 1855"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1856"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1857"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1858"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 19, 1859"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1860"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1861"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 1, 1862"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1863"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1864"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1865"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1866"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1867"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 9, 1868"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1869"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1870"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1871"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1872"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 1, 1873"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1874"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1875"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1876"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1877"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1878"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 1, 1879"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1880"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1881"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1882"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1883"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 1, 1884"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1885"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1886"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1887"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1888"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1889"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 1, 1890"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 9, 1891"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1892"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1897"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1898"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1899"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1900"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1901"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1902"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1903"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1904"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1905"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1906"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1907"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1908"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1909"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1910"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 5, 1911"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1912"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1913"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 8, 1914"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 7, 1915"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 5, 1916"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 4, 1917"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 2, 1918"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 2, 1919"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1920"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1921"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 8, 1922"),
                new Integer(SPOKEN));
        dist.put(Utilities.stringToDate("December 6, 1923"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("December 3, 1924"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1925"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 7, 1926"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1927"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 4, 1928"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 3, 1929"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 2, 1930"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 8, 1931"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("December 6, 1932"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("January 3, 1934"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 4, 1935"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 3, 1936"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 6, 1937"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 3, 1938"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 4, 1939"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 3, 1940"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 6, 1941"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 6, 1942"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 7, 1943"), new Integer(SPOKEN
                | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 11, 1944"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("January 6, 1945"), new Integer(WRITTEN
                | SPOKEN | DAY | RADIO));
        dist.put(Utilities.stringToDate("January 21, 1946"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("January 6, 1947"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 7, 1948"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 5, 1949"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 4, 1950"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 8, 1951"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 9, 1952"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 7, 1953"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("February 2, 1953"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("January 7, 1954"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 6, 1955"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 5, 1956"), new Integer(WRITTEN
                | SPOKEN | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 10, 1957"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 9, 1958"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 9, 1959"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 7, 1960"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 12, 1961"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 30, 1961"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("January 11, 1962"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 14, 1963"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 8, 1964"), new Integer(SPOKEN
                | DAY | TV | RADIO));
        dist.put(Utilities.stringToDate("January 4, 1965"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 12, 1966"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 10, 1967"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 17, 1968"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 14, 1969"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 22, 1970"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 22, 1971"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 20, 1972"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("February 2, 1973"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("January 30, 1974"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 15, 1975"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 19, 1976"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 12, 1977"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 19, 1978"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 25, 1979"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 21, 1980"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 16, 1981"), new Integer(
                WRITTEN));
        dist.put(Utilities.stringToDate("January 26, 1982"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 25, 1983"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 25, 1984"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("February 6, 1985"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("February 4, 1986"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 27, 1987"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 25, 1988"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("February 9, 1989"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 31, 1990"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 29, 1991"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 28, 1992"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("February 17, 1993"), new Integer(
                SPOKEN | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 25, 1994"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 24, 1995"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 23, 1996"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("February 4, 1997"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 27, 1998"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 19, 1999"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 27, 2000"), new Integer(SPOKEN
                | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("February 27, 2001"), new Integer(
                SPOKEN | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("September 20, 2001"), new Integer(
                SPOKEN | NIGHT | TV | RADIO));
        dist.put(Utilities.stringToDate("January 29, 2002"), new Integer(SPOKEN
                | NIGHT | WEB | TV | RADIO));
        dist.put(Utilities.stringToDate("January 28, 2003"), new Integer(SPOKEN
                | NIGHT | WEB | TV | RADIO));
        dist.put(Utilities.stringToDate("January 20, 2004"), new Integer(SPOKEN
                | NIGHT | WEB | TV | RADIO));
        dist.put(Utilities.stringToDate("February 2, 2005"), new Integer(SPOKEN
                | NIGHT | WEB | TV | RADIO));
        dist.put(Utilities.stringToDate("January 31, 2006"), new Integer(SPOKEN
                | NIGHT | WEB | TV | RADIO));
        dist.put(Utilities.stringToDate("January 23, 2007"), new Integer(SPOKEN
                | NIGHT | WEB | TV | RADIO));
        dist.put(Utilities.stringToDate("January 28, 2008"), new Integer(SPOKEN
                | NIGHT | WEB | TV | RADIO));
        return dist;
    }

    static public void main(String args[]) {
        PApplet.main(new String[] { "Display" });
    }
}
