/******************************* FLASH **************************************/

var allSketchpadVersions = new Object();
var allPoints = new Object();
var allRedoPoints = new Object();

// Should be renamed... this is the function called to pass a single line of points back from Flash
function saveSketch(id, pointsFromSketchpad) {
	//$(id).value += thepoints;
	
	if (!allPoints[id]) {
	    allPoints[id] = new Array();
	}
	
	var drawing = null;
	
	if (id == oCreate.left.pointsKey) {
	    drawing = oCreate.left;
	} else if (id == oCreate.right.pointsKey) {
	    drawing = oCreate.right;
	    
	} else if (id == oCreate.outcome.pointsKey) {
	    drawing = oCreate.outcome;
	}
	
	if (drawing && drawing.drawingnameElfocused) {
	    drawing.drawingnameEl.blur();
	}
	
	drawing.hideErrors(true);
	
	// Attempt to split points from sketchpad info
	var points;
	var sketchpadVersion;
	var splitPoints = pointsFromSketchpad.split('|');
	
	if (splitPoints.length == 1 || splitPoints.length == 2) {
	    if (splitPoints.length == 1) {
	        points = splitPoints[0];
	        
        } else if (splitPoints.length == 2) {
            sketchpadVersion = splitPoints[0];
            points = splitPoints[1];
            
            allSketchpadVersions[id] = sketchpadVersion;
        }

	    allRedoPoints[id] = new Array();
    	allPoints[id].push(points);
    	
    	if (!gLoggedIn) {
            startAjaxLogin(null, null, false, 'draw');
    	}
	}
}


function trashSketch(id) {
	//$(id).value = '';
	
	allPoints[id] = new Array();
	allRedoPoints[id] = new Array();
}


function undoLine(id) {
    allRedoPoints[id].push(allPoints[id].pop());
}


function redoLine(id) {
    allPoints[id].push(allRedoPoints[id].pop());
}


function sketchpadLoaded() {
    itemsToLoad--;

    if (itemsToLoad == 0) {
        begin();
    }
}


var oCreate;
function begin() {
    // Should be redefined/overwritten
}

// dang cheaters!
var cheater = function(drawingnameEl, inputName, value) {
    var parentFieldset = $D.getAncestorByTagName($(drawingnameEl), 'fieldset');
    var drawingNameEl = $D.getElementsByClassName('drawingNameInput', '', parentFieldset)[0];
    var cheaterInput = document.createElement('input');
    cheaterInput.name = drawingnameEl[0] + inputName + '[]';
    cheaterInput.value = value;
    
    $D.insertAfter(cheaterInput, drawingNameEl);
    
    $E.addListener($(cheaterInput), 'focus', function(e) {
        if(cheaterInput.value == value) { cheaterInput.value = ''; }
    });

    $E.addListener($(cheaterInput), 'blur', function(e) {
        if(cheaterInput.value == '') { cheaterInput.value = value; }
    });
}


// When the user returns focus to the drawing window, it is important to check to see if they have added any new colors to their pencil set
$E.on(window, 'focus', function () {
    var self = this;
    
    var callback = {
        success: function(o) {
            var parts = o.responseText.split('|');
            
            if (parts[0] == 'OK' && parts[1]) {
                if ($('Lflash1') && $('Rflash1') && $('Oflash1')) {
                    $('Lflash1').sendAvailableColors(parts[1], parts[2], parts[3]);
                    $('Rflash1').sendAvailableColors(parts[1], parts[2], parts[3]);
                    $('Oflash1').sendAvailableColors(parts[1], parts[2], parts[3]);
                }
            }
        },

        failure: function(o) {
            // For debugging:
            //alert('Name check failed: ' + o.status);
            // For production: Not really worth reporting... what can we do about it?
        },
        
        argument: [self],

        timeout: 5000,
        
        cache: false
    };
    
    $C.asyncRequest(
        'GET',
        '/ajax/checkcolors.php',
        callback,
        null
    );
});



/******************************* DRAWING ************************************/

function Drawing(side, n, pOutcome) {
    this.side = side;
    this.n = n;
    
    this.pOutcome = null;
    if (pOutcome) {
        this.pOutcome = pOutcome;
    }
    
    this.label = '';
    
    switch (this.side) {
        case 'A':
            this.label ='avatar drawing';
            break;
            
        case 'L':
            this.label = 'left drawing';
            break;
        
        case 'R':
            this.label = 'right drawing';
            break;
        
        case 'O':
            this.label = 'outcome';
            break;
    }
    
    this.drawingnameEl = $(side + 'drawingname' + n);
    this.drawingnameElfocused = false;
    this.drawingautoEl = $(side + 'drawingauto' + n);
    this.drawinglinkEl = $(side + 'drawinglink' + n);
    this.drawingimgEl = $(side + 'drawingimg' + n);
    this.drawingidEl = $(side + 'drawingid' + n);
    this.errorsEl = $(side + 'drawingerrors' + n);    
    this.indicatorEl = $(side + 'drawingindicate' + n);
    
    if (this.side != 'A') {
        //this.drawingpointsEl = $(side + 'drawingpoints' + n);
        this.flashBoxEl = $(side + 'flashBox' + n);
    	this.flashEl = $(side + 'flash' + n);
    	this.newDupeWarning = $(side + 'newDupeWarning' + n);
    	this.newDupeEl = $(side + 'newDupe' + n);    
        this.pointsKey = side + 'drawingname' + n;
    }
    
    this.errors = null;
    this.resetErrors();
    this.errorsShown = false;
    
    this.isNewDupe = false;
}


Drawing.prototype.init = function () {
    // Eliminates scope problems in timers/event handlers
    //var self = this;
    
    if (this.side != 'A') {
        $E.addListener(this.newDupeWarning, 'mousedown', function () {this.lookForDuplicateNewDrawings(this);}, this, true);
    }
    
    if (this.isNewDrawing()) {
        $E.addListener(this.drawingnameEl, 'blur', this.blurName, this, true);
        $E.addListener(this.drawingnameEl, 'focus', this.focusName, this, true);
        
        this.showClickToDraw();
        
        this.blurName();
    }
    
    if (this.side != 'O') {
        // This single line makes the autocomplete div permanently invisible in IE 6 as far as I can tell.
    	//$D.setStyle(this.drawingautoEl, 'opacity', '0.8');
    	
        this.setupAutocomplete();
    }
    
    if (this.side == 'O') {
        $E.addListener(this.drawingnameEl, 'keyup', function (e) {this.lookForDuplicateNewDrawings();}, this, true);
        $E.addListener(this.drawingnameEl, 'change', this.ajaxIsNameUnique, this, true);
    }
    
    // Errors written in by PHP... (but non-ajax save isn't really supported anymore)
    if(this.errorsEl.getElementsByTagName('ul')[0]) {
		this.showErrors();
	}
    
    //$E.addListener(this.flashBoxEl, 'mousedown', function () {this.hideErrors(true);}, this, true);    
    $E.addListener(this.drawingnameEl, 'focus', function () {this.hideErrors();}, this, true);
    $E.addListener(this.errorsEl, 'mousedown', function () {this.hideErrors();}, this, true);
}


Drawing.prototype.ajaxIsNameUnique = function () {
    var self = this;
    
    var callback = {
        success: function(o) {
            var parts = o.responseText.split("\t");
            
            if (parts && parts[0]) {
                self.resetErrors();
                self.addError("The drawing " + parts[0] + " already exists.");
                self.showErrors();
            }
        },

        failure: function(o) {
            // For debugging:
            //alert('Name check failed: ' + o.status);
            // For production: Not really worth reporting... what can we do about it?
        },
        
        argument: [self],

        timeout: 5000,
        
        cache: false
    };
    
    $C.asyncRequest(
        'GET',
        '/ajax/drawingnames_yui.php?name=' + self.getName().toLowerCase() + '&exact=1',
        callback,
        null
    );
}


Drawing.prototype.getName = function () {
    return this.drawingnameEl.value;
}


Drawing.prototype.setName = function (name) {
    this.drawingnameEl.value = name;
}

Drawing.prototype.drawingHasName = function () {
    return (this.drawingnameEl && this.getName() != '' && this.getName() != 'name this drawing' && this.getName() != 'search for a drawing');
}


Drawing.prototype.isNewDrawing = function () {
    return (!this.drawingidEl || !this.drawingidEl.value || this.drawingidEl.value < 1);
}


Drawing.prototype.userDrewSomething = function () {
    return (allPoints[this.pointsKey] && allPoints[this.pointsKey].length > 0);
}


Drawing.prototype.resetErrors = function () {
    this.errors = {input: new Array(), sketchpad: new Array()};
}


Drawing.prototype.addError = function (error, isSketchpadError) {
    if (isSketchpadError) {
        this.errors['sketchpad'].push(error);
        
    } else {
        this.errors['input'].push(error);
    }
}


Drawing.prototype.hasErrors = function () {
    return (this.errors['input'].length > 0 || this.errors['sketchpad'].length > 0);
}


Drawing.prototype.updateErrorHTML = function () {
    var HTML = '';
    
    if (this.hasErrors()) {
        HTML += '<ul>';
        
        if (this.errors['sketchpad'].length > 0) {
            HTML += '<li>' + this.errors['sketchpad'].join('</li><li>') + '</li>';
        }
        
        if (this.errors['input'].length > 0) {
            HTML += '<li>' + this.errors['input'].join('</li><li>') + '</li>';
        }
        
        HTML += '</ul>';
    }
    
    this.errorsEl.innerHTML = HTML;
}


Drawing.prototype.showErrors = function () {
    this.updateErrorHTML();
    
    // Next line makes the flash disappear when errors are showing in Firefox
 	//$D.setStyle(this.errorsEl, 'opacity', '0.7');
 	
 	// Is there any way to not have to reset all these manually like this?
 	$D.setStyle(this.errorsEl, 'width', '160px');
 	$D.setStyle(this.errorsEl, 'height', 'auto');
 	$D.setStyle(this.errorsEl, 'left', '-1999px');
    $D.setStyle(this.errorsEl, 'display', 'block');
    var newHeight = this.errorsEl.offsetHeight;
    $D.setStyle(this.errorsEl, 'height', '0');
    $D.setStyle(this.errorsEl, 'left', '-4px');
 	
 	var show = new $M(this.errorsEl, {height: {from: 0, to: newHeight}}, 0.2, $Ease.easeNone);
	show.animate();
        
    this.errorsShown = true;
}


Drawing.prototype.hideErrors = function (isSketchpad) {
    if (this.errorsShown) {
        if (isSketchpad) {
            this.errors['sketchpad'] = new Array();

        } else {
            this.errors['input'] = new Array();
            this.errors['sketchpad'] = new Array();
        }
        
        if (this.hasErrors()) {
            this.updateErrorHTML();
            
        } else {
            var that = this;
            var hide = new $M(this.errorsEl, {height: {to: 0}}, 0.1, $Ease.easeNone);
        	hide.onComplete.subscribe(function() {$D.setStyle(that.errorsEl, 'display', 'none');});
        	hide.animate();

        	this.errorsShown = false;
        }
    }
}


Drawing.prototype.validate = function () {
    this.resetErrors();
    
    // Should either be an existing drawing or a new drawing with points
    if (!this.isNewDupe && this.isNewDrawing()) {
        if (!this.userDrewSomething()) {
            this.addError('You need to draw something.', true);
        }
    }
    
    // HTML input limits length for us.  We'll simply crop anything any jerk sends us that is too long.
    
    // Validate name
    if (!this.drawingHasName()) {
        this.addError('Enter a drawing name.');
    } else if (!this.getName().match(/^[ A-Za-z0-9]+$/)) {
        this.addError('Drawing names must be letters, numbers, and spaces only.');
    }
    
    return !this.hasErrors();
}


Drawing.prototype.setupAutocomplete = function() {
    // Eliminates scope problems in timers/event handlers
    var self = this;
    
    // Setup autocomplete data source
    // Apparently we need a separate data source object for each autocomplete 
    // or the self/this object that is used in the data source event callbacks
    // wouldn't refer back to the correct drawing/autocomplete
    var ds = new $W.DS_XHR('/ajax/drawingnames_yui.php', ["\n", "\t"], {responseType: $W.DS_XHR.TYPE_FLAT, maxCacheEntries: 0, scriptQueryParam: 'name'});
    
    // Setup autocomplete itself
    var auto = new $W.AutoComplete(self.drawingnameEl, self.drawingautoEl, ds, {queryDelay: 0.1, prehighlightClassName: 'selected', highlightClassName: 'selected', autoHighlight: false, allowBrowserAutocomplete: false});
    
    // User typed in the name field, and a request has been made for matching drawing names
    auto.dataRequestEvent.subscribe(function () {auto.drawingChosen = false; self.showIndicator();});
    
    /* Autocomplete data (suggested drawing name matches) have come back
     * auto.dataReturnEvent does not fire if you quickly tab away from the autocomplete
     * after you type something, whereas ds.getResultsEvent always fires!
     */
    var f = function (evt, args) {self.hideIndicator(); /*auto.setFooter('<p>x</p>');*/ auto.theData = args[3]; if (self.firstAutoItem(auto)) {
        self.updateImageFromAuto(auto.theData[0][0], auto.theData[0][1], auto.theData[0][2]);
        self.updateLinkFromAuto(auto.theData[0][0]);
    } else {self.showClickToDraw();}}
    ds.getCachedResultsEvent.subscribe(f);
    ds.getResultsEvent.subscribe(f);
    
    // Autocomplete data have come back and there are no suggested matching drawing names
    ds.dataErrorEvent.subscribe(function () {self.hideIndicator(); self.showClickToDraw();});
    
    //
    // YUI stupidly doesn't have an event for "nothing is highlighted anymore"
    // so we have to test for that by setting a timeout every time we mouse or
    // arrow out of an item
    //
    var timer;
    var settimeout = function () {
        // YUI also doesn't have a method for setting/unsetting the highlighted item in the autocomplete without digging in their private methods
        timer = setTimeout(function () {auto._toggleHighlight(auto._oCurItem); auto._oCurItem = null; (picker)();}, 200);
    };
    var cleartimeout = function () {
        clearTimeout(timer);
    };
    var picker = function () {if (!auto.drawingChosen) {if (self.firstAutoItem(auto)) {
        self.updateImageFromAuto(auto.theData[0][0], auto.theData[0][1], auto.theData[0][2]);
        self.updateLinkFromAuto(auto.theData[0][0]);
    } else {self.showClickToDraw();}}};
    
    // As the user mouses over drawing names, show the drawing to the user
    auto.itemMouseOverEvent.subscribe(function (evt, args) {
        (cleartimeout)();
        data = auto.getListItemData(args[1]);
        self.updateImageFromAuto(data[0], data[1], data[2]);
        self.updateLinkFromAuto(data[0]);
    });
    
    // As the user mouses off drawing names, we should maybe go back to draw tools?
    auto.itemMouseOutEvent.subscribe(function (evt, args) {(settimeout)();});
    
    // As the user arrows over drawing names, show the drawing to the user
    auto.itemArrowToEvent.subscribe(function (evt, args) {
        (cleartimeout)(); 
        data = auto.getListItemData(args[1]);
        self.updateImageFromAuto(data[0], data[1], data[2]);
        self.updateLinkFromAuto(data[0]);
    });
    
    // As the user arrows off drawing names, we should maybe go back to draw tools?
    auto.itemArrowFromEvent.subscribe(function (evt, args) {(settimeout)();});
    
    // Fix Safari bug where if you clicked off the autocomplete to collapse it, and then clicked right back into the input it would not give you any results
    auto.textboxKeyEvent.subscribe(function () {auto._bFocused = true;});
    
    // The user has selected one of the autocomplete options
    auto.itemSelectEvent.subscribe(function (evt, args) {
        auto.drawingChosen = true; 
        self.updateImageFromAuto(args[1].innerHTML, args[2][1], args[2][2]);
        self.updateLinkFromAuto(args[1].innerHTML);
    });
    
    /* If the user arrows out of or collapses the autocomplete menu, make sure we're showing them a drawing or the click to draw box as appropriate
     * ...itemArrowFrom we need to worry about if the user arrows up all the way out of the drop down choices back into the input, in which
     * case the containerCollapseEvent should be happening anyway, right?
     *
     * ...and for the containerCollapse event, if its not collapsing because a drawing was chosen then we need to check what is typed
     * in the input... and if it matches the top autocomplete item, show that image... otherwise show click-to-draw.
     *
     */
    auto.containerCollapseEvent.subscribe(picker);
}


Drawing.prototype.showIndicator = function () {
    // There was a strange bug with Safari when we were using display instead
    // of visibility... where the indicator wouldn't show up unless you also
    // did an alert() or updated the DOM somehow like by calling DEBUG()
    $D.setStyle(this.indicatorEl, 'visibility', 'visible');
}


Drawing.prototype.hideIndicator = function () {
    $D.setStyle(this.indicatorEl, 'visibility', 'hidden');
}


// Returns true if the drawing name in the input == the first item in / at the top of the list of the given autocomplete's matching choices
Drawing.prototype.firstAutoItem = function (autocomplete) {
    return (autocomplete.theData.length > 0 && autocomplete.theData[0][0].toString().toUpperCase() == this.getName().toString().toUpperCase());
}

Drawing.prototype.updateLinkFromAuto = function (drawing) {
    this.drawinglinkEl.href = '/outcomes/' + this.urlSafeName(drawing);
}

Drawing.prototype.updateImageFromAuto = function(drawing, id, flags) {
    if (!id) {
        id = 0;
    } else {
        this.lookForDuplicateNewDrawings();
    }
    
    if (flags && (flags & 32) > 0) {
        this.drawingimgEl.src = '/images/drawing-403.png';
    } else {
        this.drawingimgEl.src = '/images/drawings/' + drawing.charAt(0).toLowerCase().replace(/[0-9]/, '1') + '/' + drawing + '.png';
    }
    
    this.drawingimgEl.title = 'Open ' + drawing + ' in a new window.';
    this.drawingidEl.value = id;
    
	this.showDrawing();

	this.hideFlash();
}

Drawing.prototype.urlSafeName = function(name) {
    return name.replace(/\s+/g, '_');
}

Drawing.prototype.showFlash = function () {
    $D.setStyle(this.flashBoxEl, 'left', '0');
}


// If we use display: none to hide the flash the user will lose their drawing
// if they change the name of the drawing after they've started to draw
Drawing.prototype.hideFlash = function () {
    $D.setStyle(this.flashBoxEl, 'left', '-900em');
}


Drawing.prototype.showDrawing = function () {
    $D.setStyle(this.drawingimgEl, 'display', 'block');
}


Drawing.prototype.hideDrawing = function () {
    $D.setStyle(this.drawingimgEl, 'display', 'none');
}


Drawing.prototype.sameAsOther = function () {
    /*this.isNewDupe = true;
    
    this.drawingimgEl.src = '/images/sameasother.png';
    
    this.showDrawing();*/
    
    this.newDupeWarning.innerHTML = '<p>We can only keep one of your drawings of <em><strong>' + this.getName() + '</strong></em>.  We will keep the drawing in the visible sketch pad, or:</p><p class="dupclick"><span class="top"></span>Click here to keep this drawing of <em><strong>' + this.getName() + '</strong></em>.<span class="bottom"></span></p>';
    
    $D.setStyle(this.newDupeWarning, 'display', 'block');
    
    this.hideFlash();
}


Drawing.prototype.differentFromOther = function () {
    //this.isNewDupe = false;
    
    $D.setStyle(this.newDupeWarning, 'display', 'none');
    
    if (this.isNewDrawing()) {
        this.showFlash();
    } else {
        this.showDrawing();
    }
}


Drawing.prototype.showClickToDraw = function () {
    if (this.drawingidEl) {
        this.drawingidEl.value = 0;
    }
    
    // For avatar at the moment you can't click to draw
    if (this.side == 'A') {
        this.drawingimgEl.src = '/images/avatars/anonymous.png';
        
    } else {
        this.showFlash();

    	if (!this.userDrewSomething()) {
    	    if (this.drawingHasName()) {
        	    this.flashEl.sendUpdateDrawHere(this.getName());
        	} else {
        	    this.flashEl.sendUpdateDrawHere('something');
        	}
    	}

        this.hideDrawing();

        this.lookForDuplicateNewDrawings();
    }
}


/*
Drawing.prototype.hideClickToDraw = function () {    
	this.flashEl.sendHideDrawHere();
	this.hideFlash();
}
*/


Drawing.prototype.lookForDuplicateNewDrawings = function (keeper) {
    if (this.pOutcome && this.drawingHasName()) {
        this.pOutcome.lookForDuplicateNewDrawings(keeper);
    }
}


Drawing.prototype.blurName = function () {
    this.drawingnameElfocused = false;
    $D.setStyle(this.drawingnameEl, 'color', '#999');
    if (!this.drawingHasName()) {
        if (this.side == 'A') {
            this.setName('search for a drawing');
            
        } else {
            this.setName('name this drawing');
        }
    }
}


Drawing.prototype.focusName = function () {
    this.drawingnameElfocused = true;
    $D.setStyle(this.drawingnameEl, 'color', '#39D');
    if (!this.drawingHasName()) {
        this.setName('');
    }
}


/*
Originally points were written to the text area as each line was drawn.  I believe we switched to this method where we wrote all points to the text area at once because it made undo/redo easier and also because Safari had a problem where the more lines you drew the longer the UI lag a user would experience as Safari wrote the latest bit of line text to the text area.  That bug is still noticable as a lag when all points are written to the textarea.  It is a weird thing.

Because of the UI lag in Safari when the points text is written to the text areas, and because non-AJAX save is not officially supported anymore anyway, we're going to just send the points variables straight over tha ajax stream without ever writing them to a form element (i.e. the text areas)
/*
Drawing.prototype.pointsToTextArea = function () {
    if (allPoints[this.pointsKey]) {
        this.drawingpointsEl.value = allSketchpadVersions[this.pointsKey] + '|' + allPoints[this.pointsKey].join('');
        
    } else {
        this.drawingpointsEl.value = '';
    }
}
*/

Drawing.prototype.getPointsString = function () {
    if (allPoints[this.pointsKey]) {
        return allSketchpadVersions[this.pointsKey] + '|' + allPoints[this.pointsKey].join('');
    } else {
        return '';
    }
}


/*
function inputScale() {
	var inputBaseFontSize = 20;
    var zap = this.value.length - 8;
    if ( zap > 10 ) zap = 10;
    if ( zap > 0 ) {
        this.style.fontSize = parseInt( inputBaseFontSize - zap ).toString() + 'px';
    } else {
        this.style.fontSize = inputBaseFontSize.toString() + 'px';
    }
    

    //if ( this.value.length >= 8 ) {
    //    this.style.fontSize = parseInt( inputBaseFontSize - 10 ).toString() + 'px';
    //} else {
    //    this.style.fontSize = inputBaseFontSize.toString() + 'px';
    //}
}
*/



/******************************* OUTCOME ************************************/

/* This little bit of magic will cause Safari and Firefox (untested in IE) to
 * pop up a dialog where the user can confirm that they really want to leave
 * the page... we could use it if we wanted to.
/*
window.onbeforeunload = function () {return 'You will lose anything you have drawn.';}
*/

//
// IMPORTANT: each of these events is important to a different browser in
//            order to completely capture and stop the Backspace key from
//            causing the browser to do a BACK operation, and likewise the
//            Enter key doing a SUBMIT operation.  If we need to do something
//            with key capturing where this event redundancy would be a
//            problem we'll have to create new separate listeners to do that
//            stuff.
//
$E.addListener(document, 'keydown', outcomeCreateKeyHandler);
$E.addListener(document, 'keyup', outcomeCreateKeyHandler);
$E.addListener(document, 'keypress', outcomeCreateKeyHandler);

// This function is for cancelling BACKSPACE and RETURN/ENTER...
// Because I'm listening on keydown, keyup, and keypress to cover how various 
// [stupid] browsers work this handler might fire multiple times and thus is
// not currently suited for doing affirmative rather than cancellation actions
function outcomeCreateKeyHandler(e) {
    var target = $E.getTarget(e);
    var targetTag = target.nodeName.toUpperCase(); // I think upper case by default, but can't hurt
    var keycode = $E.getCharCode(e);
    
    // Stop BACKSPACE from doing a browser BACK command by accident
    if (keycode == 8 && targetTag != 'INPUT' && targetTag != 'TEXTAREA') {
        $E.stopEvent(e);
        return false;
    
    // Don't submit the form if the user hits enter in one of these inputs
    } else if (keycode == 13 && targetTag == 'INPUT' && $D.hasClass(target, 'drawingNameInput')) {
        $E.stopEvent(e);
        return false;
    }
}


function Outcome(n, action) {
    this.n = n;
    this.randomizing = false;
    this.saving = false;
    
    if (action == 'create') {
        this.formEl = $('createform' + n);
        
        this.left = new Drawing('L', n, this);
        this.right = new Drawing('R', n, this);
        this.outcome = new Drawing('O', n, this);

        this.left.init();
        this.right.init();
        this.outcome.init();

        this.outcomebuttonparagraphEl =  $('Obuttonparagraph' + n);
        this.outcomebuttonEl =  $('Obutton' + n);
        this.outcomesavingEl = $('Osaving' + n);
        this.randomizerImage = $('randomizerImage' + n);
        
        this.dupeKeeper = null;
        
        this.buttonText = this.outcomebuttonEl.value;
        
        $D.setStyle('autocompleteHelp', 'display', 'block');
        $E.addListener('autocompleteHelp', 'mousedown', this.hideHelp, this, true);
        $E.addListener(this.left.drawingnameEl, 'focus', this.hideHelp, this, true);
        $E.addListener(this.right.drawingnameEl, 'focus', this.hideHelp, this, true);
        
        
        // Image preloading
        var imagesToLoad = ['/images/indicator.gif'];
        
        var images = new Array(imagesToLoad.length);
        for (var i = 0; i < imagesToLoad.length; i++) {
            images[i] = new Image();
            images[i].src = imagesToLoad[i];
        }
        
        
        // Eliminates scope problems in timers/event handlers
        //var self = this;
        
        // Submit Form handler
        $E.addListener(this.formEl, 'submit', this.submitHandler, this, true);
        
        // dummy listener to swap out randomizer image
        $E.addListener(this.randomizerImage, 'click', this.ajaxRandomizeCreate, this, true);
        
        // Hide loading message, show form bits
        $D.setStyle('createloading' + n, 'display', 'none');
        $D.setStyle('createform' + n, 'display', 'block');
        
    } else {
        //this.favCountEl = $('favCount' + n);
    }
}


Outcome.prototype.hideHelp = function () {
    var hide = new $A('autocompleteHelp', { height: {to: 0}}, 0.1, $Ease.easeNone);
    hide.onComplete.subscribe(function() {$D.setStyle('autocompleteHelp', 'display', 'none');});
    hide.animate();
    
    $E.removeListener('autocompleteHelp', 'mousedown', this.hideHelp);
    $E.removeListener(this.left.drawingnameEl, 'focus', this.hideHelp);
    $E.removeListener(this.right.drawingnameEl, 'focus', this.hideHelp);
}


Outcome.prototype.ajaxRandomizeCreate = function (event) {
    var d = new Date();
    var start = d.utimeUTC();
    
    var self = this;
	$E.stopEvent(event);
	
	function randomizeComplete(o, self) {
	    self.randomizing = false;
        
        if (o.responseText) {
            // should set newLeftDrawing and newRightDrawing
            eval(o.responseText);
            
            if (newLeftDrawing && newRightDrawing) {
                if (!self.left.isNewDrawing() || !self.left.userDrewSomething()) {
                    self.left.setName(newLeftDrawing.name);
                    self.left.updateImageFromAuto(newLeftDrawing.name, newLeftDrawing.id);
                    self.left.updateLinkFromAuto(newLeftDrawing.name);
                }

                if (!self.right.isNewDrawing() || !self.right.userDrewSomething()) {
                    self.right.setName(newRightDrawing.name);
                    self.right.updateImageFromAuto(newRightDrawing.name, newRightDrawing.id);
                    self.right.updateLinkFromAuto(newRightDrawing.name);
                }
            }
        }
        
		$D.removeClass(self.randomizerImage, 'randomizing');
	}
    
    var callback = {
        success: function(o) {
            setTimeoutFromMoment(function () {randomizeComplete(o, self);}, 300, start);
        },

        failure: function(o) {
            self.randomizing = false;
            
            // For debugging:
            //alert('Randomization failed: ' + o.status);
            // For production: Not really worth reporting... what can we do about it?
            
 			$D.removeClass(self.randomizerImage, 'randomizing');
        },
        
        argument: [self, start],

        timeout: 5000,
        
        cache: false
    };
    
    if (!this.randomizing) {
        this.randomizing = true;
        
		$D.addClass(this.randomizerImage, 'randomizing');

        $C.asyncRequest(
            'GET',
            '/ajax/randomizecreate_yui.php',
            callback,
            null
        );
    }
}


Outcome.prototype.lookForDuplicateNewDrawings = function (keeper) {
    this.left.isNewDupe = false;
    this.right.isNewDupe = false;
    this.outcome.isNewDupe = false;

    if (this.left.isNewDrawing() && this.left.drawingHasName()) {
        if (this.right.isNewDrawing() && this.left.getName().toUpperCase() == this.right.getName().toUpperCase()) {
            this.left.isNewDupe = true;
            this.right.isNewDupe = true;
        }
        
        if (this.outcome.isNewDrawing() && this.left.getName().toUpperCase() == this.outcome.getName().toUpperCase()) {
            this.left.isNewDupe = true;
            this.outcome.isNewDupe = true;
        }
    }
    
    if (this.right.isNewDrawing() && this.right.drawingHasName()) {
        if (this.outcome.isNewDrawing() && this.right.getName().toUpperCase() == this.outcome.getName().toUpperCase()) {
            this.right.isNewDupe = true;
            this.outcome.isNewDupe = true;
        }
    }
    
    if (keeper) {
        this.dupeKeeper = keeper;
    } else if (this.dupeKeeper && !this.dupeKeeper.isNewDupe) {
        this.dupeKeeper = null;
    }
    
    if (this.dupeKeeper) {
        this.dupeKeeper.isNewDupe = false;
    } else {
        if (this.left.isNewDupe) {
            this.left.isNewDupe = false;
        } else if (this.right.isNewDupe) {
            this.right.isNewDupe = false;
        }
    }
    
    if (this.left.isNewDupe) {
        this.left.sameAsOther();
    } else {
        this.left.differentFromOther();
    }

    if (this.right.isNewDupe) {
        this.right.sameAsOther();
    } else {
        this.right.differentFromOther();
    }

    if (this.outcome.isNewDupe) {
        this.outcome.sameAsOther();
    } else {
        this.outcome.differentFromOther();
    }
}


Outcome.prototype.submitHandler = function (e) {
    $E.stopEvent(e);
    
    if (this.saving) {
        return;
    }
    
    if (!gLoggedIn) {
        startAjaxLogin(null, null, false, 'draw');
        return;
	}
    
    this.showSaveIndicator();

    if (!this.left.validate()) {
        this.left.showErrors();
    }
    
    if (!this.right.validate()) {
        this.right.showErrors();
    }
    
    if (!this.outcome.validate()) {
        this.outcome.showErrors();
    }
    
    if (!this.left.hasErrors() && !this.right.hasErrors() && !this.outcome.hasErrors()) {
        this.left.newDupeEl.value = (this.left.isNewDupe) ? 1 : 0;
        
        this.right.newDupeEl.value = (this.right.isNewDupe) ? 1 : 0;
        
        this.outcome.newDupeEl.value = (this.outcome.isNewDupe) ? 1 : 0;
        
        /*
        this.left.pointsToTextArea();
        this.right.pointsToTextArea();
        this.outcome.pointsToTextArea();
        */
        
        this.ajaxSave();
    } else {
        this.hideSaveIndicator();
    }
}


Outcome.prototype.showSaveIndicator = function () {
    this.saving = true;
    this.outcomebuttonEl.disabled = true;
    this.outcomebuttonEl.setAttribute('disabled', 'disabled');
    this.outcomebuttonEl.value = 'Saving...';
    $D.setStyle(this.outcomesavingEl, 'display', 'block');
}


Outcome.prototype.hideSaveIndicator = function () {
    this.saving = false;
    this.outcomebuttonEl.disabled = false;
    this.outcomebuttonEl.removeAttribute('disabled');
    this.outcomebuttonEl.value = this.buttonText;
    $D.setStyle(this.outcomesavingEl, 'display', 'none');
}


Outcome.prototype.ajaxSave = function () {
    var self = this;
    
    var callback = {
        success: function(o) {
            // OK|URL
            // ERROR|Lerrsjsarray|Rerrsjsarray|Oerrsjsarray
            // <!--MAINT|junk
            /*// <!--PRIVATELOGIN|junk*/
            var responseParts = o.responseText.split('|');
            
            // Success
            if (responseParts[0] == 'OK' && responseParts[1]) {
                // In case redirect fails
                self.outcomebuttonparagraphEl.innerHTML = '<strong>Saved!</strong> <a href="' + responseParts[1] + '">Click here if you are not redirected automagically...</a>';
                // Redirect
                window.location.href = responseParts[1];
            
            // Site down for maintenance
            } else if (responseParts[0] == '<!--MAINT') {
                window.location.href = '/';
            
            // User needs to log in
            } else if (responseParts[0] == 'LOGIN') {
                self.hideSaveIndicator();
                
                // If self.ajaxSave isn't wrapped in a lambda function, the "this" referenced above becomes the window object
                startAjaxLogin(function () {self.ajaxSave();}, null, false, 'draw');
                
            /*
            // This behaviour will change when we have AJAX logins rather than a site login page.
            } else if (responseParts[0] == '<!--PRIVATELOGIN') {
                self.outcome.resetErrors();
                self.outcome.addError('You are not logged in.  Open a new window and log in, then resubmit.');
                self.outcome.showErrors();
                self.hideSaveIndicator();
            */
            
            // Errors
            } else {
                // "message for drawing saved during back-fill error" function
                // This is hacky.
                function bf_hack(which, drawingName)
                {
                    which.setName(drawingName);
                    which.updateLinkFromAuto(drawingName);
                    which.updateImageFromAuto(drawingName, 0, 0);

                    var tt = new Tooltip(which.drawingnameEl, null, null, false, false);
                    tt.create('Your new drawing of "' + drawingName + '" has been saved.', 'success');
                    tt.show(null, 100);
                }
                var bf_hack_regex = new RegExp("^BF_HACK_DRAWING_SAVED:([ A-Za-z0-9]+)$");
                
                if (responseParts[1])
                {
                    // Hack for "drawing saved during back-fill error"
                    var bf_hack_matches = bf_hack_regex.exec(responseParts[1]);
                    if (bf_hack_matches)
                    {
                        bf_hack(self.left, bf_hack_matches[1]);
                    }
                    else
                    {
                        self.left.resetErrors();
                        self.left.errors['input'] = eval(responseParts[1]);
                        self.left.showErrors();
                    }
                }
                
                if (responseParts[2])
                {
                    // Hack for "drawing saved during back-fill error"
                    var bf_hack_matches = bf_hack_regex.exec(responseParts[2]);
                    if (bf_hack_matches)
                    {
                        bf_hack(self.right, bf_hack_matches[1]);
                    }
                    else
                    {
                        self.right.resetErrors();
                        self.right.errors['input'] = eval(responseParts[2]);
                        self.right.showErrors();
                    }
                }
                
                if (responseParts[3])
                {
                    self.outcome.resetErrors();
                    self.outcome.errors['input'] = eval(responseParts[3]);
                    self.outcome.showErrors();
                }
                
                self.hideSaveIndicator();
            }
        },

        failure: function(o) {
            switch (o.status) {
                case 0:
                    self.outcome.resetErrors();
                    self.outcome.addError('Save failed.  Try saving again?');
                    break;
                
                case -1:
                    self.outcome.resetErrors();
                    self.outcome.addError('Save timed out.  Try saving again?');
                    break;
                
                default:
                    self.outcome.resetErrors();
                    self.outcome.addError('Save failed: ' + o.status + '.  Try saving again?');
                    break;
            }
            
            self.outcome.showErrors();
            
            self.hideSaveIndicator();
        },
        
        argument: [self],

        timeout: 20000
    };
    
    // Get the data from the form
    var postVars = $C.setForm('createform' + self.n);
    
    // Because we are skipping using the textarea at all due to performance issues in Safari
    postVars += '&Ldrawingpoints[]=' + self.left.getPointsString();
    postVars += '&Rdrawingpoints[]=' + self.right.getPointsString();
    postVars += '&outcomepoints[]=' + self.outcome.getPointsString();
    
    // Form fields have [] in the names to forward accomodate multiple creations per page and fail back to non ajax form submissions
    // so we have to get rid of the encoding of the [] here (%5B%5D)
    //postVars = postVars.replace(/%5B%5D/g, '');
        
    $C.asyncRequest(
        'POST',
        '/new',
        callback,
        postVars + '&ajaxed=1'
    );
}
