// Copyright 2007 Type Supply / Tal Leming.
// Not for redistribution.



// Various Caches and Whatnot
//
// URL stub
var lsBaseURL = "http://lettersetter.net/cgi-bin/ls?foundry=typesupply"
// Default image URL stub
var lsLocalImageBaseURL = "http://static.typesupply.com/lettersetter/"
// The arrays below are setup as dicts keyed by
// layout name. This allows this code to handle
// more than one layout at a time.
//
// A cache of default URLs. These will only be
// used for reference.
var lsDefaultURLs = new Array();
// Initial states for the layout. This will be compared
// against the user input. LetterSetter will only be called
// when the input does not match the initial states.
var lsInitialText = new Array();
var lsInitialFeatureStates = new Array();
// All text boxes in the layout.
var lsTextBoxes = new Array();
// All created controls will be mapped to the feature
// tags they control.
var lsControlReference = new Array();
// A record of the last called URL. This will
// make sure that LetterSetter is not being asked
// to render what it just rendered.
var lsLastCalledURL = new Array();
// The images will be loaded from LetterSetter in
// an image that is not in the DOM. Once the image
// has been loaded, it will be inserted into the DOM.
// To handle the waiting, special objects are created
// and stored here.
var lsActiveImageLoaders = new Array();
// The image loader will timeout after 30 seconds.
var lsImageLoaderTimOutAfter = 30 * 1000;

// Instructions
var lsInstructionText = [
	"Set your own text by typing in the field above. To try out the OpenType features, click on the options to the right.",
	"Preview generated by LetterSetter."
	]

//
// Interface Maker
//
// This function reads the JSON layout data
// and creates the interface. It does not insert
// the interface into the DOM. It returns the
// element and the caller must insert it.
function makeLetterSetterInterface(layoutData) {
	// Store state data for the layout.
	layoutName = layoutData["layoutName"];
	lsInitialText[layoutName] = layoutData["text"];
	lsInitialFeatureStates[layoutName] = new Array();
	for (var featureTag in layoutData["featureStates"]) {
		lsInitialFeatureStates[layoutName][featureTag] = layoutData["featureStates"][featureTag];
	}
	lsTextBoxes[layoutName] = layoutData["textBoxes"].slice();
	lsControlReference[layoutName] = new Array();

	// An array for storing all connections that
	// should be made with MochiKit.Signal.connect
	// after the element is added to the document.
	var connections = new Array();

	// Action to be called after control changes.
	var updateAction = "updateLetterSetterImage('" + layoutName + "')";

	// A container div to contain everything.
	var lsContainer = DIV({"class" : "lsContainer"});

	// Add the image.
	var imageURL = lsLocalImageBaseURL + layoutName + ".png";
	lsDefaultURLs[layoutName] = lsBaseURL + "&layout=" + layoutName;
	lsLastCalledURL[layoutName] = lsDefaultURLs[layoutName];
	var lsImage = IMG({"class" : "lsImage", "src" : imageURL, "id" : "ls_image_" + layoutName})
	lsContainer.appendChild(lsImage);

	// A div to hold the controls.
	var interfaceContainer = DIV({"class" : "lsInterfaceContainer"});

	// A div to contain the input.
	var inputContainer = DIV({"class" : "lsInputContainer"});
	// Add the input field.
	var textInputID = "ls_textInput_" + layoutName;
	var textInput = INPUT({"class" : "lsTextInput", "id" : textInputID});
	textInput.value = layoutData["text"];
	inputContainer.appendChild(textInput);
	textInput.layoutName = layoutName; // XXX Arbitrary attribute! This is needed by the change function.
	connections[connections.length] = [textInputID, "onchange", "updateImage"];
	// Add the progress indicator.
	var progressIndicator = IMG({"class" : "lsProgressIndicator", "id" : "ls_progressIndicator_" + layoutName,
			"src" : "http://static.typesupply.com/img/ls_progress.gif"});
	progressIndicator.style.display = "none";
	inputContainer.appendChild(progressIndicator);
	// Add the instruction text.
	for (var pIndex in lsInstructionText) {
		var paragraph = P({"class" : "lsInstructions"}, lsInstructionText[pIndex])
		inputContainer.appendChild(paragraph);
	}
	// Add the input container to the main container.
	interfaceContainer.appendChild(inputContainer);

	// A div to contain all the feature groups.
	var featureContainer = DIV({"class" : "lsFeaturesContainer"});
	// Add the top header.
	featureContainer.appendChild(H3({"class" : "lsFeaturesTitle"}, "OpenType Features"))
	// Add the groups.
	var groups = layoutData["groups"];
	for (var groupIndex in groups) {
		var group = groups[groupIndex];
		var groupName = group[0];
		var groupData = group[1];
		// Add a header containing the name and give
		// it a toggle action to show/hide the group.
		var groupHead = H3({"class" : "lsFeatureGroupHead closed", "id" : groupData.headID}, groupName);
		groupHead.style.cursor = "pointer"
		featureContainer.appendChild(groupHead);
		groupHead.linkedGroup = groupData.groupID; // XXX Arbitrary attribute! This is needed by the toggle function.
		connections[connections.length] = [groupData.headID, "onclick", "toggleGroup", groupData.groupID];
		// A group to hold all members.
		var controlGroup = DIV({"id" : groupData.groupID, "class" : "lsFeatureGroup"});
		controlGroup.style.display = "none";
		// Add the members.
		var members = groupData["members"];
		for (var memberIndex in members) {
			var member = members[memberIndex];
			// The control.
			// This is a bit of hackery to make the radios work in IE.
			// To do that, the type has to be assigned when the control is created.
			if (member.radioGroup == null) {
				var control = INPUT({"id" : member.elementID, "type" : "checkbox"});
				control.className = "lsFeatureCheckBox";
			} else {
				var control = INPUT({"id" : member.elementID, "type" : "radio", "name" : "ls_" + layoutName + "_" + member.radioGroup});
				control.className = "lsFeatureRadio";
			}
			// Turn the control on.
			if (member.checked == true) {
				control.checked = "checked";
			}
			// Add the control to the group.
			controlGroup.appendChild(control);
			control.layoutName = layoutName; // XXX Arbitrary attribute! This is needed by the click function.
			connections[connections.length] = [member.elementID, "onclick", "updateImage", groupData.groupID];
			// Add the control text.
			controlGroup.appendChild(SPAN({"class" : "lsFeatureText"}, member.name))
			controlGroup.appendChild(BR());
			// Store references to the features affected by this control.
			lsControlReference[layoutName][member.elementID] = new Array();
			lsControlReference[layoutName][member.elementID]["featuresOn"] = member.featuresOn.slice();
			lsControlReference[layoutName][member.elementID]["featuresOff"] = member.featuresOff.slice();
		}
		// Add the group to the features conatiner.
		featureContainer.appendChild(controlGroup);
	}
	// Add the features to the main container.
	interfaceContainer.appendChild(featureContainer);
	// Return the main container.
	lsContainer.appendChild(interfaceContainer);
	return [lsContainer, connections];
}

//
// LetterSetter Caller
//
// This function compiles the LetterSetter URL based
// on the user input and starts the image loader.
function updateLetterSetterImage(layoutName) {
	var imageURL = lsBaseURL + "&layout=" + layoutName;
	var imageID = "ls_image_" + layoutName;

	// Only changes from the default settings
	// are sent in the url. So, a map of all
	// changes is created.
	var featureChanges = new Array();
	var haveStateChange = false;

	// Find text input changes.
	var textInput = getElement("ls_textInput_" + layoutName);
	var initialText = lsInitialText[layoutName];
	var text = textInput.value;
	if (text != initialText) {
		haveStateChange = true;
	}

	// Find feature state changes.
	var controlReference = lsControlReference[layoutName];
	var initialFeatureStates = lsInitialFeatureStates[layoutName];
	for (var controlID in controlReference) {
		var controlData = controlReference[controlID];
		var control = getElement(controlID);
		var controlState = control.checked;
		// On features.
		if (controlState == true) {
			for (var tagIndex in controlData.featuresOn) {
				var tag = controlData.featuresOn[tagIndex];
				if (initialFeatureStates[tag] != true) {
					featureChanges[tag] = true;
					haveStateChange = true;
				}
			}
		} else {
			for (var tagIndex in controlData.featuresOn) {
				var tag = controlData.featuresOn[tagIndex];
				if (initialFeatureStates[tag] != false) {
					featureChanges[tag] = false;
					haveStateChange = true;
				}
			}
		}
		// Off features.
		if (controlState == true) {
			for (var tagIndex in controlData.featuresOff) {
				var tag = controlData.featuresOff[tagIndex];
				if (initialFeatureStates[tag] != false) {
					featureChanges[tag] = false;
					haveStateChange = true;
				}
			}
		}
	}

	var needImage = false;
	if (haveStateChange == true) {
		// If a state change is present, a new image is needed.
		needImage = true;
	} else if (lsLastCalledURL != lsDefaultURLs[layoutName]) {
		// If not a state change and the default URL was not
		// the last URL called, a new image is needed.
		needImage = true;
	}

	// If changes were found, make a URL and
	// start the image loader.
	if (needImage == true) {
		// Compile the image URL.
		var textBoxes = lsTextBoxes[layoutName];
		// Loop through all text boxes in the layout
		// and add the necessary values to the URL.
		var keys = new Array();
		var values = new Array();
		for (i in textBoxes) {
			var textBoxID = textBoxes[i];
			var textBoxIndex = textBoxID.replace("tb", "");
			// Text input change.
			if (text != initialText) {
				keys[keys.length] = textBoxID;
				values[values.length] = text;
			}
			// Feature state changes.
			for (featureTag in featureChanges) {
				var key = "tf" + textBoxIndex
				if (featureChanges[featureTag] == true) {
					var value = featureTag + ".1";
				} else {
					var value = featureTag + ".0";
				}
				keys[keys.length] = key;
				values[values.length] = value;
			}
		}
		imageURL = imageURL + "&" + queryString(keys, values)
		// Only start the image loader if the compiled
		// URL does not match the last URL called.
		if (lsLastCalledURL[layoutName] != imageURL) {
			// Default images are always pulled locally.
			if (imageURL == lsDefaultURLs[layoutName]) {
				imageURL = lsLocalImageBaseURL + layoutName + ".png";
			}
			lsLastCalledURL[layoutName] = imageURL;
			new LetterSetterImageLoader(layoutName, imageURL);
		}
	}
}

//
// Image Loader
//
// This object locates the image to be replaced and
// switches its src. This gives the illusion of the
// image being updated live. This also starts and stops
// the progress indicator (actually it shows and hides
// the progress indicator) to let the user know that
// LetterSetter is working.
LetterSetterImageLoader = function(layoutName, imageURL) {
	this.layoutName = layoutName;
	this.imageID = "ls_image_" + layoutName;
	// If this layout is already loading an image
	// that loading should be cancelled.
	if (lsActiveImageLoaders[this.imageID] != null) {
		var otherLoader = lsActiveImageLoaders[this.imageID];
		otherLoader.isBeingOverridden();
	}
	// start the progress
	this.startProgress()
	// get the existing image
	var image = getElement(this.imageID);
	// connect the onload function
	this.signalIdentity = connect(image, "onload", finishedLoadingImage);
	// start the loading
	image.src = imageURL;
	// store a reference indicating that this layout is being loaded
	lsActiveImageLoaders[this.imageID] = this;
	// set the timeout timer
	this.timeoutID = setTimeout("imageLoadingTimedOut('" + this.imageID + "');", lsImageLoaderTimOutAfter)
}

// Show the progress indicator.
LetterSetterImageLoader.prototype.startProgress = function() {
	var imageID = "ls_progressIndicator_" + this.layoutName;
	var image = getElement(imageID);
	image.style.display = "block";
}

// Hide the progress indicator.
LetterSetterImageLoader.prototype.stopProgress = function() {
	var imageID = "ls_progressIndicator_" + this.layoutName;
	var image = getElement(imageID);
	image.style.display = "none";
}

// Shut down this object and remove references to it.
LetterSetterImageLoader.prototype.shutDown = function() {
	disconnect(this.signalIdentity);
	delete lsActiveImageLoaders[this.imageID];
	clearTimeout(this.timeoutID);
}

// This layout has a more recent render to display.
LetterSetterImageLoader.prototype.isBeingOverridden = function() {
	this.shutDown();
}

// Finished loading. Shut it down.
function finishedLoadingImage(event) {
	var imageID = event.src().id;
	var loader = lsActiveImageLoaders[imageID];
	loader.shutDown();
	loader.stopProgress();
}

// The loading has timed out. Shut it down.
function imageLoadingTimedOut(imageID){
	var loader = lsActiveImageLoaders[imageID];
	loader.shutDown();
	loader.stopProgress();
	// do something with the image URL?
	// fallback to default? or error message?
}
