﻿// Global variables

var g_deltaX = 0, g_deltaY = 0;                 // deltas of cursor from dragTip top left
var g_wordTileDragged = null;                   // tile that's getting dragged
var g_capitalizationTooltipShown = false;
var g_alternatesTooltipShown = false;

var g_wordListRollingSpeed = 0.0;
var g_wordList = null; // set to the wordList element, if present; set in pageLoadHandler
var g_wordListIntervalTimer = null;
var g_wordListIntervalTimerSet = false;
var g_wordListLeftMin = 0; // used for wordList left/right scrolling; set when words are laid out

// Constants

var OLD_MOVE_HANDLER = null, OLD_UP_HANDLER = null;    // used by handleMouseUp in IE4
if (document.onmousemove) {
    OLD_MOVE_HANDLER = document.onmousemove;
    OLD_UP_HANDLER = document.onmouseup;
}
var FAST_WORDLIST_ROLLING_SPEED = 20;
var SLOW_WORDLIST_ROLLING_SPEED = 7;
var ALTERNATE_INDEX_ROOT_FORM = -1;  // alternateIndex == -1 when the root form of a word is used
var TOOLTIPS_FOR_SMALL_SYMBOLS = { ".": "period", ",": "comma", "“": "left quote", "”": "right quote", "’": "apostrophe" };

function pageLoadHandler_dragdrop()
{
    g_divAlternatesMenu = document.getElementById( "alternatesMenu" );
    g_divAlternatesMenuTarget = document.getElementById( "alternatesMenuTarget" );
    g_alternatesMenuAffordance = document.getElementById( "alternatesMenuAffordance" );
    attachEventToElement( $("divPoemPicture"), "click", onClick_divPoemPicture );
    attachEventToElement( g_alternatesMenuAffordance, "click", alternatesMenuTarget_onClick );

    g_wordList = document.getElementById( "wordList" );        
    attachEventToElement( document.getElementById( "wordListContainer" ), "mousemove", onMouseMove_wordList );
    attachEventToElement( document, "mousemove", onMouseMove_document );
}


// handleMouseMove gets attached to the document when there's a drag in progress.
// divDragTip is the only thing that gets dragged

function handleMouseMove(e) {
    if ( !e ) e = window.event;   // Earlier IE event model
    
    var dragTip = document.getElementById( "divDragTip" );
    dragTip.style.left = ( e.clientX - g_deltaX ) + "px";
    dragTip.style.top = ( e.clientY - g_deltaY ) + "px";
    
    if (e.stopPropagation) { e.stopPropagation(); }
    else { e.cancelBubble = true; }
}

function handleMouseDown(e) {
    if ( !e ) e = window.event;   // Earlier IE event model
    if ( e.target ) { var wordTile = e.target; /* DOM */ }
    else { var wordTile = e.srcElement; /* IE */ }
    
    hideTooltip();
    hideAlternatesMenuAffordance();
    if ( wordTile.parentNode.id != "divPoemPicture" )
    {
        wordTile.style.backgroundColor = "#d9d9d9";
        if ( wordTile.getAttribute( "part-of-speech" ) != "universal" ) wordTile.style["color"] = "#9c9c9c";
    }
    
    // Capture mouse delta from top left corner
    var wordTileLeft = absLeft(wordTile);
    var wordTileTop = absTop(wordTile);
    g_deltaX = e.clientX - wordTileLeft;
    g_deltaY = e.clientY - wordTileTop;
    
    // Move drag tip over top of the current tile, pick up its word, and make the drag tip visible
    var dragTip = document.getElementById( "divDragTip" );
    dragTip.style.left = wordTileLeft + "px";
    dragTip.style.top = wordTileTop + "px";
    dragTip.innerHTML = wordTile.innerHTML;
    dragTip.style.visibility = "visible";
    
    
    // remember the word that's been picked up
    g_wordTileDragged = wordTile;

    // set up handlers to deal with the rest of the drag action
    
    if (document.addEventListener) {  // DOM level 2
        document.addEventListener("mousemove", handleMouseMove, true);
        document.addEventListener("mouseup", handleMouseUp, true);
    }
    else if (document.attachEvent) {  // IE 5-6
        dragTip.setCapture();
        dragTip.attachEvent("onmousemove", handleMouseMove);
        dragTip.attachEvent("onmouseup", handleMouseUp);
        dragTip.attachEvent("onlosecapture", handleMouseUp); // handle loss of capture as mouseup
    }
    else { // IE 4
        document.onmousemove = handleMouseMove;
        document.onmouseup = handleMouseUp;
    }

    // stop bubbling the event and prevent default action
    if ( e.stopPropagation ) { e.stopPropagation( ); }
    else { e.cancelBubble = true; }
    if ( e.preventDefault ) { e.preventDefault( ); }
    else { e.returnValue = false; }
    
    return false;
}

function handleMouseUp(e) {
    if ( !e ) e = window.event;   // Earlier IE event model

    var dragTip = document.getElementById("divDragTip");
    
    // stop capturing mouse events
    if (document.removeEventListener) {   // DOM event model
        document.removeEventListener("mouseup", handleMouseUp, true);
        document.removeEventListener("mousemove", handleMouseMove, true);
    }
    else if (document.detachEvent) {   // IE 5-6 event model
        dragTip.detachEvent("onlosecapture", handleMouseUp);
        dragTip.detachEvent("onmouseup", handleMouseUp);
        dragTip.detachEvent("onmousemove", handleMouseMove);
        dragTip.releaseCapture();
    }
    else {   // IE 4 event model
        document.onmouseup = OLD_UP_HANDLER;
        document.onmousemove = OLD_MOVE_HANDLER;
    }
        
    // stop bubbling the event
    if ( e.stopPropagation ) { e.stopPropagation( ); }
    else { e.cancelBubble = true; }

    var dropTarget = document.getElementById("divPoemPicture");
    
    if ( window.pageXOffset )
    {
        var scrolledX = e.clientX + window.pageXOffset;
        var scrolledY = e.clientY + window.pageYOffset;
    }
    else
    {
        var scrolledX = e.clientX + document.documentElement.scrollLeft;
        var scrolledY = e.clientY + document.documentElement.scrollTop;
    }
    
    if ( isOverDropTarget( scrolledX, scrolledY, dropTarget ) )
    {
        if ( g_wordTileDragged.parentNode != dropTarget )
        {
            // dragged from list to picture (i.e. adding to poem)
            
            // add the word to the in-memory representation (g_poem)
            var wordObject = new Object();
            var rootForm = g_wordTileDragged.innerHTML;
            wordObject.top = ySnap();
            wordObject.left = xSnap( wordObject.top );
            wordObject.alternateIndex = ALTERNATE_INDEX_ROOT_FORM;
            wordObject.capitalization = "none";
            wordObject.partOfSpeech = g_wordTileDragged.getAttribute( "part-of-speech" );
            wordObject.poemWordIndex = g_wordTileDragged.getAttribute( "poem-word-index" );
            // if this word is already in the poem, prepend an underscore and a three digit unique index
            if ( g_poem.wordsInPoem[rootForm] )
            {
                var firstUnusedIndex = -1;
                while ( g_poem.wordsInPoem["_" + threeDigitString(++firstUnusedIndex) + rootForm] );
                rootForm = "_" + threeDigitString(firstUnusedIndex) + rootForm;
            }
            g_poem.wordsInPoem[rootForm] = wordObject;

            // add to on-screen poem
            var addedWordTile = addWordTileToPicturePoem( dropTarget, rootForm, handleMouseDown, true );  
            
            updateWordPoolsRenderingForWord( g_wordTileDragged.innerHTML, true );  // update what's enabled

            if ( setUpAlternatesMenu( addedWordTile ) ) // true if menu has alternates
            {
                showAlternatesMenuAffordance( addedWordTile );
                if ( !g_alternatesTooltipShown )
                {
                    if ( !g_capitalizationTooltipShown && !g_poem.alternates[g_wordTileDragged.innerHTML] )
                    {
                        showTooltip( "You can capitalize this word. Click here to try it!", 
                            g_alternatesMenuAffordance.offsetLeft + dropTarget.offsetLeft + 4, 
                            g_alternatesMenuAffordance.offsetTop + dropTarget.offsetTop );
                        g_capitalizationTooltipShown = true;
                    }
                    else
                    {
                        if ( g_poem.alternates[g_wordTileDragged.innerHTML] )
                        {
                            if ( g_capitalizationTooltipShown )
                            {
                                showTooltip( "Or change words to other forms, like <i>" + 
                                        g_poem.alternates[g_wordTileDragged.innerHTML][0] + "</i>. Try it out!",  
                                    g_alternatesMenuAffordance.offsetLeft + dropTarget.offsetLeft + 4, 
                                    g_alternatesMenuAffordance.offsetTop + dropTarget.offsetTop );
                            }
                            else
                            {
                                showTooltip( "You can capitalize this word or change it to another form like <i>" + 
                                        g_poem.alternates[g_wordTileDragged.innerHTML][0] + "</i>. Click here to try it!",  
                                    g_alternatesMenuAffordance.offsetLeft + dropTarget.offsetLeft + 4, 
                                    g_alternatesMenuAffordance.offsetTop + dropTarget.offsetTop );
                            }
                            g_alternatesTooltipShown = true;
                        }
                        else
                        {
                            hideTooltip();
                        }
                    }
                }
                else
                {
                    hideTooltip();
                }
            }
            
            showMessageOnPicture( false );
            stopRollingAnimation();
        }
        else
        { 
            // dragging from drop target to drop target (i.e. moving word in poem)
            g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )].top = ySnap();
            g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )].left = xSnap( g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )].top );
            
            var deltaX = g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )].left - g_wordTileDragged.offsetLeft;
            var deltaY = g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )].top - g_wordTileDragged.offsetTop;
            var outlineDiv = g_wordTileDragged.previousSibling;
            for ( var quadrant = 0; quadrant < 4; quadrant++ )
            {
                outlineDiv.style.left = outlineDiv.offsetLeft + deltaX + "px";
                outlineDiv.style.top = outlineDiv.offsetTop + deltaY + "px";
                outlineDiv = outlineDiv.previousSibling;
            }
            
            g_wordTileDragged.style.left = g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )].left + "px";
            g_wordTileDragged.style.top = g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )].top + "px";
            setUpAlternatesMenu( g_wordTileDragged );
            showAlternatesMenuAffordance( g_wordTileDragged );
        }
    } 
    else // mouse is not over drop target
    {
        // was this word part of the poem? if so, remove word from poem picture and put it back in the list
        if (g_wordTileDragged.parentNode == dropTarget) 
        {
            for ( var quadrant = 0; quadrant < 4; quadrant++ ) g_wordTileDragged.parentNode.removeChild(g_wordTileDragged.previousSibling);
            var newWordTile = dropTarget.removeChild(g_wordTileDragged);
            updateWordPoolsRenderingForWord( g_wordTileDragged.getAttribute( "root-form" ), false );
            delete g_poem.wordsInPoem[g_wordTileDragged.getAttribute( "root-form" )];
        } else {
            g_wordTileDragged.style.color = "black";
        }
    }

    // reset state to not dragging
    dragTip.style.visibility = "hidden";
    g_wordTileDragged = null;

    // ySnap returns the y-coordinate (CSS top) that puts the dragTip onto dropTarget and
    // snapping to a specific line
    function ySnap()
    {
        var destTop = dragTip.offsetTop - absTop( dropTarget );
        destTop = Math.round( destTop / dragTip.offsetHeight ) * dragTip.offsetHeight;

        if ( destTop < 0 ) 
            destTop = 0
        else if ( destTop + dragTip.offsetHeight > HEIGHT_OF_PICTURE_POEM_IN_PX )
            destTop = HEIGHT_OF_PICTURE_POEM_IN_PX - dragTip.offsetHeight;
        
        return destTop;
    }
    
    // xSnap returns the x-coordinate (CSS left) that puts the dragTip onto dropTarget
    // and properly spaced from a tile immediately to its left. Takes the y-coordinate of
    // the top of the tile that has been or will be placed.
    function xSnap( topOfThisTile )
    {
        var destLeft = -1;
        
        // snap against right edge of other tiles

        var MAX_DISTANCE_FOR_SNAPPING = 20;
        var PADDING_BETWEEN_TILES = 5;
        var distanceToClosestTile = MAX_DISTANCE_FOR_SNAPPING + 1;
        var closestTile = null;
        
        // loop through all tiles, finding one with same y coordinate closer than MAX_DISTANCE_FOR_SNAPPING
        var divsInDropTarget = dropTarget.getElementsByTagName( "div" );
        try
        {
            for ( var wordTileOnPicture in divsInDropTarget )
            {
                if ( divsInDropTarget[wordTileOnPicture].offsetTop == topOfThisTile )
                {
                    if ( ( divsInDropTarget[wordTileOnPicture].className == "WordOnPicture" ) && 
                         ( divsInDropTarget[wordTileOnPicture].getAttribute( "root-form" ) != g_wordTileDragged.getAttribute( "root-form" ) ) )
                    {
                        var distanceToThisTile = Math.abs( dragTip.offsetLeft - divsInDropTarget[wordTileOnPicture].offsetLeft - divsInDropTarget[wordTileOnPicture].offsetWidth );
                        if ( distanceToThisTile < distanceToClosestTile )
                        {
                            closestTile = divsInDropTarget[wordTileOnPicture];
                            distanceToClosestTile = distanceToThisTile;
                        }
                    }
                }
            }
            if ( closestTile != null )
            {
                destLeft = closestTile.offsetLeft + closestTile.offsetWidth - absLeft( dropTarget ) + 1;
                // add a space before the new word, if there's supposed to be one
                if ( ( ",.!?)”".indexOf( dragTip.innerHTML.substring( 0, 1 ) ) == -1 ) && ( "“(".indexOf( closestTile.innerHTML.substr( -1 ) ) == -1 ) )
                    destLeft += PADDING_BETWEEN_TILES;
            }
        }
        
        catch (err)
        {
            destLeft = dragTip.offsetLeft - absLeft( dropTarget );
        }        
        
        // snap into picture

        if ( destLeft == -1 ) destLeft = dragTip.offsetLeft - absLeft( dropTarget );

        if ( destLeft < 0 ) 
            destLeft = 0
        else if ( destLeft + dragTip.offsetWidth > WIDTH_OF_PICTURE_POEM_IN_PX )
            destLeft = WIDTH_OF_PICTURE_POEM_IN_PX - dragTip.offsetWidth;
        
        return destLeft;
    }
    
}

function onMouseOver_poolWordTile( e )
{
    if ( !e ) e = window.event;
    if ( e.target ) { var wordTile = e.target; /* DOM */ }
    else { var wordTile = e.srcElement; /* IE */ }

    wordTile.style.backgroundColor = "#eeeeee";
}

function onMouseOut_poolWordTile( e )
{
    if ( !e ) e = window.event;
    if ( e.target ) { var wordTile = e.target; /* DOM */ }
    else { var wordTile = e.srcElement; /* IE */ }

    wordTile.style.backgroundColor = "#d9d9d9";
}


function isOverDropTarget(iX, iY, dropTarget) {
    var iX1 = absLeft(dropTarget);
    var iX2 = iX1 + WIDTH_OF_PICTURE_POEM_IN_PX;
    var iY1 = absTop(dropTarget);
    var iY2 = iY1 + dropTarget.offsetHeight;

    return (iX >= iX1 && iX <= iX2 && iY >= iY1 && iY <= iY2);
}

function updateWordPoolsRenderingForWord( word, addingToPoem )
{
    // TODO: Fix how universal words are handled in the data structure, since we're indexing by text of the word :P
    if ( g_poem.wordsInPoem[word].partOfSpeech != "universal" )
    {
        if ( addingToPoem )
        {
            var poolElement = document.getElementById( "divPool" + g_poem.wordsInPoem[word].partOfSpeech + 
                g_poem.wordsInPoem[word].poemWordIndex );
            if ( poolElement ) 
            {
                poolElement.style.color = "#9c9c9c";
                detachEventFromElement( poolElement, "mousedown", handleMouseDown, true );
                detachEventFromElement( poolElement, "mouseover", onMouseOver_poolWordTile );
                detachEventFromElement( poolElement, "mouseout", onMouseOut_poolWordTile );
            }
            
        } else {

            var poolElement = document.getElementById( "divPool" + g_poem.wordsInPoem[word].partOfSpeech +
                g_poem.wordsInPoem[word].poemWordIndex );
            if ( poolElement ) 
            {
                poolElement.style.color = "black";
                attachEventToElement ( poolElement, "mousedown", handleMouseDown, true );
                attachEventToElement( poolElement, "mouseover", onMouseOver_poolWordTile );
                attachEventToElement( poolElement, "mouseout", onMouseOut_poolWordTile );
            }

        }
    }
}


// Given the current g_poem structure, draw its words on the organized view

function renderPoemWords()
{
    setWordListHeight();
    
    var COLUMN_WIDTH = 93;
    var UNIVERSALS_COLUMN_WIDTH = 64;
    var COLUMN_HEIGHT = document.getElementById( "wordList" ).offsetHeight;
    var COLUMN_VERTICAL_PADDING = 10;
    var WORD_HEIGHT = 18;
    var topOfWordElement = COLUMN_VERTICAL_PADDING;
    var leftOfWordElement = 15;
    var divWordList = document.getElementById( "wordList" );
    var divNodeCreated = null;
    var partsOfSpeech_PoemWordsOrder = [ "noun", "adjective", "adverb", "verb", "universal" ];
    var partOfSpeech = "";
    var thisColumnWidth = COLUMN_WIDTH;
    var thisPartOfSpeechUsesMoreThanOneColumn = false;
    
    g_wordList.style.left = "0px";
    
    for ( var i = 0 ; i < partsOfSpeech_PoemWordsOrder.length ; i++ )
    {
        partOfSpeech = partsOfSpeech_PoemWordsOrder[i];
        thisPartOfSpeechUsesMoreThanOneColumn = false;
        
        // don't put a section header at the bottom of a column        
        if ( ( topOfWordElement + ( WORD_HEIGHT * 3 ) + COLUMN_VERTICAL_PADDING ) > COLUMN_HEIGHT )
        {
            topOfWordElement = COLUMN_VERTICAL_PADDING;
            leftOfWordElement += COLUMN_WIDTH;
        }
        
        // add section header
        if ( topOfWordElement != COLUMN_VERTICAL_PADDING ) topOfWordElement += ( WORD_HEIGHT >> 1) ;
        divNodeCreated = createPoemWordElement ( partOfSpeech.toUpperCase() + ( partOfSpeech != "universal" ? "S" : "" ), 
                                                 divWordList, topOfWordElement, leftOfWordElement,
                                                 partOfSpeech + "Label", "PoemWordHeader" );        
        topOfWordElement += WORD_HEIGHT;

        if ( ( topOfWordElement + WORD_HEIGHT + COLUMN_VERTICAL_PADDING ) > COLUMN_HEIGHT )
        {
            topOfWordElement = COLUMN_VERTICAL_PADDING;
            leftOfWordElement += COLUMN_WIDTH;
        }

        // add all the words in the section
        for ( var j = 0 ; j < g_poem[partOfSpeech].length ; j++ )
        {
            divNodeCreated = createPoemWordElement( g_poem[partOfSpeech][j],
                                   divWordList,
                                   topOfWordElement, leftOfWordElement,
                                   "divPool" + partOfSpeech + j,
                                   "PoemWord",
                                   partOfSpeech,
                                   j
                                  );
            topOfWordElement += WORD_HEIGHT;
            if ( ( topOfWordElement + WORD_HEIGHT + COLUMN_VERTICAL_PADDING + 5 ) > COLUMN_HEIGHT ) // 5 is a cheat to make the bottom edge look cleaner
            {
                topOfWordElement = COLUMN_VERTICAL_PADDING;
                leftOfWordElement += thisColumnWidth;
                thisPartOfSpeechUsesMoreThanOneColumn = true;
                
                if ( i == 4 ) // code in this condition only works while universals are last group
                {
                    thisColumnWidth = UNIVERSALS_COLUMN_WIDTH; // 2nd & following universals columns are narrower
                }
            }
        }
        
        if ( !thisPartOfSpeechUsesMoreThanOneColumn )
        {
            topOfWordElement = COLUMN_VERTICAL_PADDING;
            leftOfWordElement += thisColumnWidth;
        }
    }
    
    if ( divNodeCreated != null )
    {
        divWordList.style.width = divNodeCreated.offsetLeft + UNIVERSALS_COLUMN_WIDTH + "px";
        g_wordListLeftMin = document.getElementById( "wordListContainer" ).offsetWidth - g_wordList.offsetWidth;
    }
    
    if ( !(getQueryStringArgs().PoemId) ) showMessageOnPicture( "DRAG-N-DROP YOUR WORDS HERE<br />TO CREATE A PICLIT" ); // bug fix where this shows up in case of bizarre timing

    function setWordListHeight()
    {
        if ( window.innerHeight )
        {
            var browserWindowFrameHeight = window.outerHeight - window.innerHeight;
        }
        else
        {
            var browserWindowFrameHeight = 130;
            if ( navigator.appVersion.indexOf( "MSIE 6" ) != -1 ) browserWindowFrameHeight = 140;
        }
        
        var maxSupportedWordListHeight = screen.height - browserWindowFrameHeight - ( document.getElementById( "divCreateBar" ).offsetHeight ) -
            HEIGHT_OF_PICTURE_POEM_IN_PX;
        if ( maxSupportedWordListHeight > 367 ) maxSupportedWordListHeight = 367; // 367px is the height that will fit all words without scrolling
        document.getElementById( "wordList" ).style.height = maxSupportedWordListHeight + "px";
        document.getElementById( "wordListContainer" ).style.height = maxSupportedWordListHeight + "px";
    }
}

// called specifically from viewpoem et al.
function dragdropRenderPoemOnPicture()
{
    var mdh = ( ( location.pathname.indexOf( "viewpoem" ) == -1 ) ? handleMouseDown : null );
    for ( var wordObjectIndex in g_poem.wordsInPoem )
    {
        addWordTileToPicturePoem( document.getElementById( "divPoemPicture" ), wordObjectIndex, mdh );
        updateWordPoolsRenderingForWord( wordObjectIndex, true );  // update what's enabled
    }
    if ( window.stopRollingAnimation ) stopRollingAnimation();
}

// serializePoem
// Puts the contents of g_poem into divWithPoemFields; does not post the form that contains it
// Hard-coded to assume the form fields are the first three <input> elements and appear in the order 
// 1. template ID, 2. text representation of peom, 3. JSON structured representation of poem
function serializePoem_dragdrop( idOfDivWithPoemFields ) 
{
    // The "pixel number" of a word assigns a number to each pixel in the picture. The first row of
    // pixels is 0-599, the next row is 600-1199, etc. This numbers the words in reading order.
    // Known issue: words on the exact same pixel won't show up correctly in this representation.
    
    var wordsInPoemIndexedByPixelNumber = {};  // ex: { 500: "sushi", 232: "yummy" }
    var listOfPixelNumbers = [];  // ex: [500, 232]
    var typeAndWordsJSON = "\"poemStyle\": \"dragdrop\", ";
    var textPoem = "";
    var pixelNumber = -1;

    var structuredPoemWordsInPoem = "";
        
    for (var wordInPoem in g_poem.wordsInPoem)
    {
        // harvest words and calculate pixelNumers for them
        pixelNumber = g_poem.wordsInPoem[wordInPoem].top * WIDTH_OF_PICTURE_POEM_IN_PX + 
            g_poem.wordsInPoem[wordInPoem].left;
        listOfPixelNumbers[listOfPixelNumbers.length] = pixelNumber;
        wordsInPoemIndexedByPixelNumber[pixelNumber] = wordInPoem;
        
        // populate JSON (structured) representation of the words
        structuredPoemWordsInPoem += "\"" + wordInPoem.JSONEncode() + "\": { "
        for (var memberOfWord in g_poem.wordsInPoem[wordInPoem])
        {
            if ( typeof(g_poem.wordsInPoem[wordInPoem][memberOfWord]) == "string" )
            {
                structuredPoemWordsInPoem += "\"" + memberOfWord + "\": \"" + g_poem.wordsInPoem[wordInPoem][memberOfWord] + "\", ";
            }
            else
            {
                structuredPoemWordsInPoem += "\"" + memberOfWord + "\": " + g_poem.wordsInPoem[wordInPoem][memberOfWord] + ", ";
            }
        }
        structuredPoemWordsInPoem = structuredPoemWordsInPoem.substring( 0, structuredPoemWordsInPoem.length - 2 ) + " }, ";
    }

    if ( pixelNumber == -1 ) {
        
        alert( "Drag-n-drop some words from the list onto the picture to create your PicLit." );
        return false; // don't save blank piclits
    
    } else {
    
        // write out wordsInPoem structure
        typeAndWordsJSON += "\"wordsInPoem\": { " + structuredPoemWordsInPoem.substring( 0, structuredPoemWordsInPoem.length - 2 ) + " }";
        
        // sort words by pixelNumbers and create the text representation from that sorted list
        function numberOrder( a, b ) { return a - b; }
        listOfPixelNumbers.sort( numberOrder );
        var arrayMax = listOfPixelNumbers.length - 1;
        var rowOfPreviousWord = Math.floor( ( listOfPixelNumbers[0] - 1) / 600 );
        var rowOfThisWord = -1;
        var previousWordAsItAppearsInPoem = wordAsItAppearsInPoem( wordsInPoemIndexedByPixelNumber[listOfPixelNumbers[0]] );
        textPoem = previousWordAsItAppearsInPoem;
        for ( var i = 1 ; i <= arrayMax ; i++ )
        {
            var thisWordAsItAppearsInPoem = wordAsItAppearsInPoem( wordsInPoemIndexedByPixelNumber[listOfPixelNumbers[i]] );
            rowOfThisWord = Math.floor( ( listOfPixelNumbers[i] - 1 ) / 600 );
            if ( rowOfThisWord > rowOfPreviousWord )
            {
                textPoem += "\n";
            }
            else if ( ( ",.!?)”".indexOf( thisWordAsItAppearsInPoem.substring( 0, 1 ) ) == -1 ) && 
                      ( "“(".indexOf( previousWordAsItAppearsInPoem.substr( -1 ) ) == -1 ) )
            {
                textPoem += " ";
            }
            textPoem += thisWordAsItAppearsInPoem;
            rowOfPreviousWord = rowOfThisWord;
            previousWordAsItAppearsInPoem = thisWordAsItAppearsInPoem;
        }
    }
    
    return serializePoem_common( typeAndWordsJSON, textPoem, document.getElementById( idOfDivWithPoemFields ),
        g_poem.wordsInPoem[wordsInPoemIndexedByPixelNumber[listOfPixelNumbers[0]]].left, 
        g_poem.wordsInPoem[wordsInPoemIndexedByPixelNumber[listOfPixelNumbers[0]]].top);
}

// called generically from compose_global et al.
function renderPoemOnPicture()
{
    dragdropRenderPoemOnPicture();
}

// addWordTileToPicturePoem
// Adds a div to the picture so it will render as part of the poem
// Assumes g_poem is initialized and contains the word to be added

function addWordTileToPicturePoem( picturePoemElement, rootForm, mouseDownHandler )
{
    var textOfTileToAdd = wordAsItAppearsInPoem( rootForm );

    // add word tile outline
    // *** tile mouseUp code depends on the outline being the 4 divs prior to the element being picked up ***
    var newWordTile = null;
    var outlineColor = outlineColorFromTextColor( g_poem.textColor );
    var offsetsArray = [ { suffix: "NW", xOffset: -1, yOffset: -1 },
                         { suffix: "SW", xOffset: -1, yOffset:  1 },
                         { suffix: "SE", xOffset:  1, yOffset:  1 },
                         { suffix: "NE", xOffset:  1, yOffset: -1 } ];
    
    for ( var quadrant = 0; quadrant < 4; quadrant++ )
    {
        newWordTile = document.createElement( "div" );
        newWordTile.innerHTML = textOfTileToAdd;
        newWordTile.className = "WordOnPicture";
        newWordTile.style.color = outlineColor;
        newWordTile.style.left = g_poem.wordsInPoem[rootForm].left + offsetsArray[quadrant].xOffset + "px";
        newWordTile.style.top = g_poem.wordsInPoem[rootForm].top + offsetsArray[quadrant].yOffset + "px";
        picturePoemElement.appendChild( newWordTile );
    }
    
    // add word tile proper    
    newWordTile = document.createElement( "div" );
    newWordTile.innerHTML = textOfTileToAdd;
    newWordTile.className = "WordOnPicture";
    newWordTile.style.color = g_poem.textColor;
    newWordTile.style.left = g_poem.wordsInPoem[rootForm].left + "px";
    newWordTile.style.top = g_poem.wordsInPoem[rootForm].top + "px";
    newWordTile.setAttribute( "part-of-speech", g_poem.wordsInPoem[rootForm].partOfSpeech );
    newWordTile.setAttribute( "poem-word-index", g_poem.wordsInPoem[rootForm].poemWordIndex );
    newWordTile.setAttribute( "suppress-context-menu", "true" );
    newWordTile.setAttribute( "root-form", rootForm );
    
    if ( mouseDownHandler ) 
    {
        attachEventToElement( newWordTile, "mousedown", mouseDownHandler, true );
        newWordTile.style["cursor"] = "pointer";
    }
    else
    {
        newWordTile.style["cursor"] = "text";
    }
    
    picturePoemElement.appendChild( newWordTile );
    
    return newWordTile;
}

function wordAsItAppearsInPoem( rootForm )
{
    // words that appear more than once in poem (universals) are prepended with an underscore followed by a three-digit index
    var rootFormText = rootForm;    // text of root form without prefix
    
    if ( rootForm.substring( 0, 1 ) == "_" )
    {
        rootFormText = rootForm.substring( 4 );
    }
    var wordFormInPoem = ( g_poem.wordsInPoem[rootForm].alternateIndex == ALTERNATE_INDEX_ROOT_FORM ? 
                           rootFormText : 
                           g_poem.alternates[rootFormText][g_poem.wordsInPoem[rootForm].alternateIndex] );
    switch ( g_poem.wordsInPoem[rootForm].capitalization )
    {
        case "initial":
            wordFormInPoem = wordFormInPoem.toInitialCap();
            break;
            
        case "all-caps":
            wordFormInPoem = wordFormInPoem.toUpperCase();
            break;
    }
    
    return wordFormInPoem;
}

function clearPoemPools()
{
    removeChildren( document.getElementById( "wordList" ), "div" );
}

function clearPoemPictureWords()
{
    removeChildren( document.getElementById( "divPoemPicture" ), ".WordOnPicture" );
}

function createPoemWordElement ( word, divToContainWord, newElementTop, newElementLeft, newElementId, newElementClass, partOfSpeech, poemWordIndex, newElementHref )
{
    var divWordToAdd = document.createElement("div");
    divWordToAdd.id = newElementId;
    divWordToAdd.className = newElementClass;
    divWordToAdd.style.top = newElementTop.toString() + "px";
    divWordToAdd.style.left = newElementLeft.toString() + "px";
    if ( "" != partOfSpeech ) divWordToAdd.setAttribute( "part-of-speech", partOfSpeech );
    if ( -1 != poemWordIndex ) divWordToAdd.setAttribute( "poem-word-index", String(poemWordIndex) );
    divWordToAdd.setAttribute( "root-form", word );

    if ( newElementHref ) 
    {
        divWordToAdd.innerHTML = "<a href=\"" + newElementHref + "\">" + word + "</a>";
    }
    else if ( partOfSpeech ) // only make it draggable if it's a part of speech
    {
        divWordToAdd.innerHTML = word;
        if ( TOOLTIPS_FOR_SMALL_SYMBOLS[word] ) divWordToAdd.title = TOOLTIPS_FOR_SMALL_SYMBOLS[word];
        attachEventToElement ( divWordToAdd, "mousedown", handleMouseDown, true );
        attachEventToElement( divWordToAdd, "mousemove", onMouseMove_wordList );
        attachEventToElement( divWordToAdd, "mouseover", onMouseOver_poolWordTile );
        attachEventToElement( divWordToAdd, "mouseout", onMouseOut_poolWordTile );
    }
    else
    {
        divWordToAdd.innerHTML = word;
    }
        
    divToContainWord.appendChild( divWordToAdd );
    if ( divWordToAdd.offsetWidth < 15 ) divWordToAdd.style.width = "15px";
    
    return divWordToAdd;
}


function onMouseMove_wordList(e)
{
    if ( !g_wordListIntervalTimerSet )
    {
        g_wordListIntervalTimer = setInterval( displayFrame_WordList, 30 );
        g_wordListIntervalTimerSet = true;
    }
    
    if ( !e ) e = window.event;   // Earlier IE event model
    if ( e.target ) { var sourceElement = e.target; /* DOM */ }
    else { var sourceElement = e.srcElement; /* IE */ }

    // if event came from a word tile, map the x coordinate to the container
    var adjustedClientX = e.clientX - absLeft( document.getElementById( "wordListContainer" ) );
    if ( adjustedClientX < 70 ) 
        g_wordListRollingSpeed = FAST_WORDLIST_ROLLING_SPEED
    else if ( adjustedClientX < 140 )
        g_wordListRollingSpeed = SLOW_WORDLIST_ROLLING_SPEED
    else if ( adjustedClientX > 670 )
        g_wordListRollingSpeed = -FAST_WORDLIST_ROLLING_SPEED
    else if ( adjustedClientX > 600 )
        g_wordListRollingSpeed = -SLOW_WORDLIST_ROLLING_SPEED
    else g_wordListRollingSpeed = 0;
    
    // stop bubbling the event
    if ( e.stopPropagation ) { e.stopPropagation( ); }
    else { e.cancelBubble = true; }
}

function onMouseMove_document(e)
{
    // if mousemove wasn't trapped by wordlist, the mouse is outside the list, so stop animating
    g_wordListRollingSpeed = 0;
}

function displayFrame_WordList()
{
    if ( g_wordListRollingSpeed < 0 )
    {
        if ( g_wordList.offsetLeft > g_wordListLeftMin )
        {
            var targetLeft = g_wordList.offsetLeft + g_wordListRollingSpeed;
            if ( targetLeft < g_wordListLeftMin ) targetLeft = g_wordListLeftMin;
            g_wordList.style.left = targetLeft + "px";
        }
    }
    else if ( g_wordListRollingSpeed > 0 )
    {
        if ( g_wordList.offsetLeft < 0 )
        {
            var targetLeft = g_wordList.offsetLeft + g_wordListRollingSpeed;
            if ( targetLeft > 0 ) targetLeft = 0;
            g_wordList.style.left = targetLeft + "px";
        }
    }
    else
    {
        clearInterval( g_wordListIntervalTimer );
        g_wordListIntervalTimerSet = false;
    }
}

function onClick_divPoemPicture(e)
{
    if ( document.getElementById( "alternatesMenu" ) ) closeAlternatesMenu();
    if ( document.getElementById( "resizableTooltip" ) ) hideTooltip();
}



