/********************************************************************************
 This file contains the variables and functions that form the select screens of
 the game. The parsing functions for the opponent.xml file.
 ********************************************************************************/

/**********************************************************************
 *****               Opponent & Group Specification               *****
 **********************************************************************/

/**************************************************
 * Stores meta information about groups.
 **************************************************/
function createNewGroup (title) {
	var newGroupObject = {title:title,
						  opponents:Array(4)};

	return newGroupObject;
}

/**********************************************************************
 *****                  Select Screen UI Elements                 *****
 **********************************************************************/

/* main select screen */
$selectTable = $("#select-table");
$selectBubbles = [$("#select-bubble-1"),
                  $("#select-bubble-2"),
                  $("#select-bubble-3"),
                  $("#select-bubble-4")];
$selectDialogues = [$("#select-dialogue-1"),
                    $("#select-dialogue-2"),
                    $("#select-dialogue-3"),
                    $("#select-dialogue-4")];
$selectAdvanceButtons = [$("#select-advance-button-1"),
                         $("#select-advance-button-2"),
                         $("#select-advance-button-3"),
                         $("#select-advance-button-4")];
$selectImages = [$("#select-image-1"),
                 $("#select-image-2"),
                 $("#select-image-3"),
                 $("#select-image-4")];
$selectLabels = [$("#select-name-label-1"),
                 $("#select-name-label-2"),
                 $("#select-name-label-3"),
                 $("#select-name-label-4")];
$selectButtons = [$("#select-slot-button-1"),
                  $("#select-slot-button-2"),
                  $("#select-slot-button-3"),
                  $("#select-slot-button-4")];
$selectMainButton = $("#main-select-button");
$selectRandomButtons = $("#select-random-button, #select-random-female-button, #select-random-male-button");
$selectRandomTableButton = $("#select-random-group-button");
$selectRemoveAllButton = $("#select-remove-all-button");

$selectSuggestions = [
    $("#opponent-suggestions-1"),
    $("#opponent-suggestions-2"),
    $("#opponent-suggestions-3"),
    $("#opponent-suggestions-4"),
];

$suggestionQuads = [
    [$("#opponent-suggestion-1-1"), $("#opponent-suggestion-1-2"), $("#opponent-suggestion-1-3"), $("#opponent-suggestion-1-4")],
    [$("#opponent-suggestion-2-1"), $("#opponent-suggestion-2-2"), $("#opponent-suggestion-2-3"), $("#opponent-suggestion-2-4")],
    [$("#opponent-suggestion-3-1"), $("#opponent-suggestion-3-2"), $("#opponent-suggestion-3-3"), $("#opponent-suggestion-3-4")],
    [$("#opponent-suggestion-4-1"), $("#opponent-suggestion-4-2"), $("#opponent-suggestion-4-3"), $("#opponent-suggestion-4-4")],
]

mainSelectDisplays = [
	new MainSelectScreenDisplay(1),
	new MainSelectScreenDisplay(2),
	new MainSelectScreenDisplay(3),
	new MainSelectScreenDisplay(4)
]


/* individual select screen */
$individualSelectTable = $("#individual-select-table");
$individualNameLabels = [$("#individual-name-label-1"), $("#individual-name-label-2"), $("#individual-name-label-3"), $("#individual-name-label-4")];
$individualPrefersLabels = [$("#individual-prefers-label-1"), $("#individual-prefers-label-2"), $("#individual-prefers-label-3"), $("#individual-prefers-label-4")];
$individualSexLabels = [$("#individual-sex-label-1"), $("#individual-sex-label-2"), $("#individual-sex-label-3"), $("#individual-sex-label-4")];
$individualHeightLabels = [$("#individual-height-label-1"), $("#individual-height-label-2"), $("#individual-height-label-3"), $("#individual-height-label-4")];
$individualSourceLabels = [$("#individual-source-label-1"), $("#individual-source-label-2"), $("#individual-source-label-3"), $("#individual-source-label-4")];
$individualWriterLabels = [$("#individual-writer-label-1"), $("#individual-writer-label-2"), $("#individual-writer-label-3"), $("#individual-writer-label-4")];
$individualArtistLabels = [$("#individual-artist-label-1"), $("#individual-artist-label-2"), $("#individual-artist-label-3"), $("#individual-artist-label-4")];
$individualCountBoxes = [$("#individual-counts-1"), $("#individual-counts-2"), $("#individual-counts-3"), $("#individual-counts-4")];
$individualLineCountLabels = [$("#individual-line-count-label-1"), $("#individual-line-count-label-2"), $("#individual-line-count-label-3"), $("#individual-line-count-label-4")];
$individualPoseCountLabels = [$("#individual-pose-count-label-1"), $("#individual-pose-count-label-2"), $("#individual-pose-count-label-3"), $("#individual-pose-count-label-4")];
$individualDescriptionLabels = [$("#individual-description-label-1"), $("#individual-description-label-2"), $("#individual-description-label-3"), $("#individual-description-label-4")];
$individualBadges = [$("#individual-badge-1"), $("#individual-badge-2"), $("#individual-badge-3"), $("#individual-badge-4")];
$individualStatuses = [$("#individual-status-1"), $("#individual-status-2"), $("#individual-status-3"), $("#individual-status-4")];
$individualLayers = [$("#individual-layer-1"), $("#individual-layer-2"), $("#individual-layer-3"), $("#individual-layer-4")];
$individualCostumeSelectors = [$("#individual-costume-select-1"), $("#individual-costume-select-2"), $("#individual-costume-select-3"), $("#individual-costume-select-4")];

$individualImages = [$("#individual-image-1"), $("#individual-image-2"), $("#individual-image-3"), $("#individual-image-4")];
$individualButtons = [$("#individual-button-1"), $("#individual-button-2"), $("#individual-button-3"), $("#individual-button-4")];

$individualPageIndicator = $("#individual-page-indicator");
$individualMaxPageIndicator = $("#individual-max-page-indicator");

$individualCreditsButton = $('#individual-credits-button');

/* group select screen */
$groupSelectTable = $("#group-select-table");
$groupSwitchTestingButton = $("#group-switch-testing-button");
$groupNameLabels = [$("#group-name-label-1"), $("#group-name-label-2"), $("#group-name-label-3"), $("#group-name-label-4")];
$groupPrefersLabels = [$("#group-prefers-label-1"), $("#group-prefers-label-2"), $("#group-prefers-label-3"), $("#group-prefers-label-4")];
$groupSexLabels = [$("#group-sex-label-1"), $("#group-sex-label-2"), $("#group-sex-label-3"), $("#group-sex-label-4")];
$groupHeightLabels = [$("#group-height-label-1"), $("#group-height-label-2"), $("#group-height-label-3"), $("#group-height-label-4")];
$groupSourceLabels = [$("#group-source-label-1"), $("#group-source-label-2"), $("#group-source-label-3"), $("#group-source-label-4")];
$groupWriterLabels = [$("#group-writer-label-1"), $("#group-writer-label-2"), $("#group-writer-label-3"), $("#group-writer-label-4")];
$groupArtistLabels = [$("#group-artist-label-1"), $("#group-artist-label-2"), $("#group-artist-label-3"), $("#group-artist-label-4")];
$groupCountBoxes = [$("#group-counts-1"), $("#group-counts-2"), $("#group-counts-3"), $("#group-counts-4")];
$groupLineCountLabels = [$("#group-line-count-label-1"), $("#group-line-count-label-2"), $("#group-line-count-label-3"), $("#group-line-count-label-4")];
$groupPoseCountLabels = [$("#group-pose-count-label-1"), $("#group-pose-count-label-2"), $("#group-pose-count-label-3"), $("#group-pose-count-label-4")];
$groupDescriptionLabels = [$("#group-description-label-1"), $("#group-description-label-2"), $("#group-description-label-3"), $("#group-description-label-4")];
$groupBadges = [$("#group-badge-1"), $("#group-badge-2"), $("#group-badge-3"), $("#group-badge-4")];
$groupStatuses = [$("#group-status-1"), $("#group-status-2"), $("#group-status-3"), $("#group-status-4")];
$groupLayers = [$("#group-layer-1"), $("#group-layer-2"), $("#group-layer-3"), $("#group-layer-4")];
$groupCostumeSelectors = [$("#group-costume-select-1"), $("#group-costume-select-2"), $("#group-costume-select-3"), $("#group-costume-select-4")];

$groupImages = [$("#group-image-1"), $("#group-image-2"), $("#group-image-3"), $("#group-image-4")];
$groupNameLabel = $("#group-name-label");
$groupButton = $("#group-button");

$groupPageIndicator = $("#group-page-indicator");
$groupMaxPageIndicator = $("#group-max-page-indicator");

$groupCreditsButton = $('#group-credits-button');

$searchName = $("#search-name");
$searchSource = $("#search-source");
$searchTag = $("#search-tag");
$tagList = $("#tagList");
$sourceList = $("#sourceList");
$searchGenderOptions = [$("#search-gender-1"), $("#search-gender-2"), $("#search-gender-3")];

$sortingOptionsItems = $(".sort-dropdown-options li");

$groupSearchGroupName = $("#group-search-group-name");
$groupSearchName = $("#group-search-name");
$groupSearchSource = $("#group-search-source");
$groupSearchTag = $("#group-search-tag");
$groupSearchGenderOptions = [$("#group-search-gender-1"), $("#group-search-gender-2"), $("#group-search-gender-3"), $("#group-search-gender-4")];

/**********************************************************************
 *****                  Select Screen Variables                   *****
 **********************************************************************/

/* hidden variables */
var mainSelectHidden = false;
var singleSelectHidden = false;
var groupSelectHidden = false;

/* opponent listing file */
var listingFile = "opponents/listing.xml";
var metaFile = "meta.xml";

/* opponent information storage */
var loadedOpponents = [];
var selectableOpponents = loadedOpponents;
var hiddenOpponents = [];
var loadedGroups = [[], []];
var selectableGroups = [loadedGroups[0], loadedGroups[1]];
var tagSet = {};
var sourceSet = {};

/* page variables */
var groupSelectScreen = 0;
var individualPage = 0;
var groupPage = [0, 0];
var chosenGender = -1;
var chosenGroupGender = -1;
var sortingMode = "Featured";
var sortingOptionsMap = {
    "Newest" : sortOpponentsByMultipleFields("-release"),
    "Oldest" : sortOpponentsByMultipleFields("release"),
    "Most Layers" : sortOpponentsByMultipleFields("-layers"),
    "Fewest Layers" : sortOpponentsByMultipleFields("layers"),
    "Name (A-Z)" : sortOpponentsByMultipleFields("first", "last"),
    "Name (Z-A)" : sortOpponentsByMultipleFields("-first", "-last"),
    "Targeted most by selected" : sortOpponentsByMostTargeted(),
};
var individualCreditsShown = false;
var groupCreditsShown = false;

/* consistence variables */
var selectedSlot = 0;
var shownIndividuals = Array(4);
var shownGroup = Array(4);
var shownSuggestions = [Array(4), Array(4), Array(4), Array(4)];
var randomLock = false;

/* Status icon tooltips */
var TESTING_STATUS_TOOLTIP = "This opponent is currently in testing.";
var OFFLINE_STATUS_TOOLTIP = "This opponent has been retired from the official version of the game.";
var INCOMPLETE_STATUS_TOOLTIP = "This opponent is incomplete and currently not in development.";

/**********************************************************************
 *****                    Start Up Functions                      *****
 **********************************************************************/

/************************************************************
 * Loads all of the content required to display the title
 * screen.
 ************************************************************/
function loadSelectScreen () {
    loadListingFile();

	updateSelectionVisuals();
}

/************************************************************
 * Loads and parses the main opponent listing file.
 ************************************************************/
function loadListingFile () {
	/* clear the previous meta information */
	var outstandingLoads = 0;
	var opponentGroupMap = {};
	var opponentMap = {};
	var onComplete = function(opp, index) {
		if (opp) {
			if (opp.id in opponentMap) {
				loadedOpponents[opponentMap[opp.id]] = opp;
                opp.tags.forEach(function(tag) {
                    tagSet[tag] = true;
                });
                sourceSet[opp.source] = true;
			}
			if (opp.id in opponentGroupMap) {
				opponentGroupMap[opp.id].forEach(function(groupPos) {
					groupPos.group.opponents[groupPos.idx] = opp;
				});
			}
		}
		if (--outstandingLoads % 16 == 0) {
			updateSelectableOpponents();
			updateIndividualSelectScreen();
			updateSelectableGroups(0);
			updateSelectableGroups(1);
			updateGroupSelectScreen();
		}
        if (outstandingLoads == 0) {
            $tagList.append(Object.keys(tagSet).sort().map(function(tag) {
                return new Option(tag);
            }));
            $sourceList.append(Object.keys(sourceSet).sort().map(function(source) {
                return new Option(source);
            }));
        }
	}

	/* grab and parse the opponent listing file */
	$.ajax({
        type: "GET",
		url: listingFile,
		dataType: "text",
		success: function(xml) {
            var $xml = $(xml);
			var available = {};

            /* start by checking which characters will be loaded and available */
            $xml.find('individuals>opponent').each(function () {
                var oppStatus = $(this).attr('status');
                var id = $(this).text();
                if (oppStatus === undefined || oppStatus === 'testing' || includedOpponentStatuses[oppStatus]) {
                    available[id] = true;
                }
            });

			$xml.find('groups>group').each(function () {
				var title = $(this).attr('title');
				var opp1 = $(this).attr('opp1');
				var opp2 = $(this).attr('opp2');
				var opp3 = $(this).attr('opp3');
				var opp4 = $(this).attr('opp4');

                var ids = [opp1, opp2, opp3, opp4];
                if (!ids.every(function(id) { return available[id]; })) return;

				var newGroup = createNewGroup(title);
				ids.forEach(function(id, idx) {
					if (!(id in opponentGroupMap)) {
						opponentGroupMap[id] = [];
					}
					opponentGroupMap[id].push({ group: newGroup, idx: idx });
				});
				loadedGroups[$(this).attr('testing') ? 1 : 0].push(newGroup);
			});

            /* now actually load the characters */
            var oppDefaultIndex = 0; // keep track of an opponent's default placement

            $xml.find('individuals>opponent').each(function () {
                var oppStatus = $(this).attr('status');
                var id = $(this).text();
                var releaseNumber = $(this).attr('release');
                var doInclude = (oppStatus === undefined || includedOpponentStatuses[oppStatus]);

                if (available[id]) {
                    outstandingLoads++;
					if (doInclude) {
						opponentMap[id] = oppDefaultIndex++;
					}
                    loadOpponentMeta(id, oppStatus, releaseNumber, onComplete);
                }
            });

		}
	});
}

/************************************************************
 * Loads and parses the meta XML file of an opponent.
 ************************************************************/
function loadOpponentMeta (id, status, releaseNumber, onComplete) {
	/* grab and parse the opponent meta file */
    console.log("Loading metadata for \""+id+"\"");
	$.ajax({
        type: "GET",
		url: 'opponents/' + id + '/' + metaFile,
		dataType: "text",
		success: function(xml) {
            var $xml = $(xml);

			var opponent = new Opponent(id, $xml, status, releaseNumber);

			/* add the opponent to the list */
            onComplete(opponent);
		},
		error: function(err) {
			console.log("Failed reading \""+id+"\"");
			onComplete();
		}
	});
}

function updateStatusIcon(elem, status) {
    var icon_img = 'img/testing-badge.png';
    var tooltip = TESTING_STATUS_TOOLTIP;

    if(!status) {
        elem.removeAttr('title').removeAttr('data-original-title').hide();
        return;
    }

    if (status === 'offline') {
        icon_img = 'img/offline-badge.png';
        tooltip = OFFLINE_STATUS_TOOLTIP;
    } else if (status === 'incomplete') {
        icon_img = 'img/incomplete-badge.png';
        tooltip = INCOMPLETE_STATUS_TOOLTIP;
    }

    elem.attr({
        'src': icon_img,
        'title': tooltip,
        'data-original-title': tooltip,
    }).show().tooltip({
        'placement': 'left'
    });
}


/* Creates an <option> element in a jQuery object for an alternate costume.
 * `alt_costume` in this case has only `id` and `label` attributes.
 */
function getCostumeOption(alt_costume, selected_costume) {
    return $('<option>', {val: alt_costume.folder, text: 'Alternate Skin: '+alt_costume.label,
                          selected: alt_costume.folder == selected_costume})
}

/************************************************************
 * Loads opponents onto the individual select screen based
 * on the currently selected page.
 ************************************************************/
function updateIndividualSelectScreen () {
	/* safety wrap around */
	if (individualPage < 0) {
		/* wrap to last page */
		individualPage = Math.ceil(selectableOpponents.length/4)-1;
	}
	$individualPageIndicator.val(individualPage+1);

	/* keep track of how many opponents were on this screen */
	var empty = 0;

    /* create and load all of the individual opponents */
	for (var i = individualPage*4; i < (individualPage+1)*4; i++) {
		var index = i - individualPage*4;

		if (i in selectableOpponents) {
			shownIndividuals[index] = selectableOpponents[i];

			$individualNameLabels[index].html(selectableOpponents[i].first + " " + selectableOpponents[i].last);
			$individualPrefersLabels[index].html(selectableOpponents[i].label);
			$individualSexLabels[index].html(selectableOpponents[i].gender);
			$individualSourceLabels[index].html(selectableOpponents[i].source);
			$individualWriterLabels[index].html(wordWrapHtml(selectableOpponents[i].writer));
			$individualArtistLabels[index].html(wordWrapHtml(selectableOpponents[i].artist));
			$individualDescriptionLabels[index].html(selectableOpponents[i].description);

            if (EPILOGUE_BADGES_ENABLED && selectableOpponents[i].ending) {
                $individualBadges[index].show();
            }
            else {
                $individualBadges[index].hide();
            }

            updateStatusIcon($individualStatuses[index], selectableOpponents[i].status);

            $individualLayers[index].show();
            $individualLayers[index].attr("src", "img/layers" + selectableOpponents[i].layers + ".png");
			
			$individualImages[index].attr('src', selectableOpponents[i].selection_image);
			$individualImages[index].css('height', selectableOpponents[i].scale + '%');
			$individualImages[index].show();
			$individualButtons[index].html('Select Opponent');
			$individualButtons[index].attr('disabled', false);
			
			$individualCostumeSelectors[index].hide();
			if (ALT_COSTUMES_ENABLED) {
				if (
					(!FORCE_ALT_COSTUME && selectableOpponents[i].alternate_costumes.length > 0) ||
					(FORCE_ALT_COSTUME && selectableOpponents[i].alternate_costumes.length > 1)
				) {
					if (!FORCE_ALT_COSTUME) {
						$individualCostumeSelectors[index].empty().append($('<option>', {val: '', text: 'Default Skin'}));
					}
					
					selectableOpponents[i].alternate_costumes.forEach(function (alt) {
						$individualCostumeSelectors[index].append(getCostumeOption(alt, selectableOpponents[i].selected_costume));
					});
					$individualCostumeSelectors[index].show();
				}
			}
		} else {
			delete shownIndividuals[index];

			$individualNameLabels[index].html("");
			$individualPrefersLabels[index].html("");
			$individualSexLabels[index].html("");
			$individualSourceLabels[index].html("");
			$individualWriterLabels[index].html("");
			$individualArtistLabels[index].html("");
            $individualCountBoxes[index].css("visibility", "hidden");
			$individualDescriptionLabels[index].html("");
            $individualBadges[index].hide();
            $individualStatuses[index].hide();
            $individualLayers[index].hide();

			$individualImages[index].hide();
			$individualButtons[index].attr('disabled', true);

			$individualCostumeSelectors[index].hide();

			empty++;
		}
    }

	/* reload if the page is empty */
	if (empty == 4 && individualPage != 0) {
		individualPage = 0;
		updateIndividualSelectScreen();
	}
}

/************************************************************
 * Loads opponents onto the group select screen based on the
 * currently selected page.
 ************************************************************/
function updateGroupSelectScreen () {
	/* safety wrap around */
  if (groupPage[groupSelectScreen] < 0) {
		/* wrap to last page */
		groupPage[groupSelectScreen] = (selectableGroups[groupSelectScreen].length)-1;
	} else if (groupPage[groupSelectScreen] > selectableGroups[groupSelectScreen].length-1) {
		/* wrap to the first page */
		groupPage[groupSelectScreen] = 0;
	}
	$groupPageIndicator.val(groupPage[groupSelectScreen]+1);
    $groupMaxPageIndicator.html("of "+selectableGroups[groupSelectScreen].length);

    /* create and load all of the individual opponents */
	$groupButton.attr('disabled', false);
	for (var i = 0; i < 4; i++) {
		var opponent = selectableGroups[groupSelectScreen].length > 0 ?
            selectableGroups[groupSelectScreen][groupPage[groupSelectScreen]].opponents[i] :
            undefined;

		if (opponent && typeof opponent == "object") {
			shownGroup[i] = opponent;

			$groupNameLabels[i].html(opponent.first + " " + opponent.last);
			$groupPrefersLabels[i].html(opponent.label);
			$groupSexLabels[i].html(opponent.gender);
			$groupSourceLabels[i].html(opponent.source);
			$groupWriterLabels[i].html(wordWrapHtml(opponent.writer));
			$groupArtistLabels[i].html(wordWrapHtml(opponent.artist));
			$groupDescriptionLabels[i].html(opponent.description);

            if (EPILOGUE_BADGES_ENABLED && opponent.ending) {
                $groupBadges[i].show();
            }
            else {
                $groupBadges[i].hide();
            }
			
			$groupCostumeSelectors[i].hide();
			if (ALT_COSTUMES_ENABLED) {
				if (
					(!FORCE_ALT_COSTUME && opponent.alternate_costumes.length > 0) ||
					(FORCE_ALT_COSTUME && opponent.alternate_costumes.length > 1)
				) {
					if (!FORCE_ALT_COSTUME) {
						$groupCostumeSelectors[i].empty().append($('<option>', {val: '', text: 'Default Skin'}));
					}
					opponent.alternate_costumes.forEach(function (alt) {
						$groupCostumeSelectors[i].append(getCostumeOption(alt, opponent.selected_costume));
					});
					$groupCostumeSelectors[i].show();
				}
			}

            updateStatusIcon($groupStatuses[i], opponent.status);

            $groupLayers[i].show();
            $groupLayers[i].attr("src", "img/layers" + opponent.layers + ".png");

			$groupImages[i].attr('src', opponent.selection_image);
			$groupImages[i].css('height', opponent.scale + '%');
			$groupImages[i].show();
		} else {
			delete shownGroup[i];

			$groupNameLabels[i].html("");
			$groupPrefersLabels[i].html("");
			$groupSexLabels[i].html("");
			$groupSourceLabels[i].html("");
			$groupWriterLabels[i].html("");
			$groupArtistLabels[i].html("");
			$groupDescriptionLabels[i].html("");
            $groupBadges[i].hide();
            $groupStatuses[i].hide();
            $groupLayers[i].hide();
			$groupImages[i].hide();
			$groupCostumeSelectors[i].hide();
			$groupButton.attr('disabled', true);
		}
    }
    if (selectableGroups[groupSelectScreen].length == 0) {
        $groupNameLabel.html("(No matches)");
    } else {
        $groupNameLabel.html(selectableGroups[groupSelectScreen][groupPage[groupSelectScreen]].title);
    }
}

/* Sets the suggested opponent to be displayed in a given slot and quadrant.
 * Arguments:
 * - opponent: the opponent object to display
 * - slot: the selection slot to load into
 * - quad: the quadrant of said selection slot to load into
 */
function updateSuggestionQuad(slot, quad, opponent) {
    var img_elem = $suggestionQuads[slot][quad].children('.opponent-suggestion-image');
    var label_elem = $suggestionQuads[slot][quad].children('.opponent-suggestion-label');
    var tooltip = null;

    if (opponent.status === 'testing') {
        tooltip = TESTING_STATUS_TOOLTIP;
    } else if (opponent.status === 'offline') {
        tooltip = OFFLINE_STATUS_TOOLTIP;
    } else if (opponent.status === 'incomplete') {
        tooltip = INCOMPLETE_STATUS_TOOLTIP;
    }

    shownSuggestions[slot][quad] = opponent.id;

    img_elem.attr({
        'title': tooltip,
        'data-original-title': tooltip,
        'src': opponent.selection_image
    }).tooltip();

    label_elem.text(opponent.label);
}

/* Sets the given selection screen slot to display 4 opponents from an array.
 * Arguments:
 * - slot: the main select screen slot to update (zero-indexed)
 * - suggestionsArray: the array to draw suggestions from
 * - startIndex: the index into suggestionsArray to begin drawing suggestions from
 */
function updateSuggestions(slot, suggestionsArray, startIndex) {
    for(var i=0;i<4;i++) {
        if (suggestionsArray[startIndex+i]) {
            updateSuggestionQuad(slot, i, suggestionsArray[startIndex+i]);
        }
    }
}


/**********************************************************************
 *****                   Interaction Functions                    *****
 **********************************************************************/

/************************************************************
 * Filters the list of selectable opponents based on those
 * already selected and performs search and sort logic.
 ************************************************************/
function updateSelectableOpponents(autoclear) {
    var name = $searchName.val().toLowerCase();
    var source = $searchSource.val().toLowerCase();
    var tag = $searchTag.val().toLowerCase();

    // Array.prototype.filter automatically skips empty slots
    selectableOpponents = loadedOpponents.filter(function(opp) {
        // filter by name
        if (name
            && opp.label.toLowerCase().indexOf(name) < 0
            && opp.first.toLowerCase().indexOf(name) < 0
            && opp.last.toLowerCase().indexOf(name) < 0) {
            return false;
        }

        // filter by source
        if (source && opp.source.toLowerCase().indexOf(source) < 0) {
            return false;
        }

        // filter by tag
        if (tag) {
            if (!opp.tags || !opp.tags.some(function(t) {
                return t.toLowerCase() == tag;
            })) {
                return false;
            }
        }

        // filter by gender
        if ((chosenGender == 2 && opp.gender !== eGender.MALE)
            || (chosenGender == 3 && opp.gender !== eGender.FEMALE)) {
            return false;
        }

        /* hide selected opponents */
        if (players.some(function(p) { return p && p.id == opp.id; })) {
            return false;
        }
        
        return true;
    });

    // If a unique match was made, automatically clear the search so
    // another opponent can be found more quickly.
    if (autoclear && (name != null || source != null) && selectableOpponents.length == 0) {
        clearSearch();
        return;
    }

    /* sort opponents */
    // Since selectableOpponents is always reloaded here with featured order,
    // check if a different sorting mode is selected, and if yes, sort it.
    if (sortingOptionsMap.hasOwnProperty(sortingMode)) {
        selectableOpponents.sort(sortingOptionsMap[sortingMode]);
    }

    /* update max page indicator */
    $individualMaxPageIndicator.html("of "+Math.ceil(selectableOpponents.length/4));
}

/************************************************************
 * The player clicked on a suggested character button.
 ************************************************************/
function suggestionSelected(slot, quad) {
    var selectedID = shownSuggestions[slot-1][quad-1];

    if(!selectedID) {
        /* This shouldn't happen. */
        console.error("Could not find suggested opponent ID for slot " + slot + " and quad " + quad);
        return;
    }

    /* Find the character they selected. */
    for (var i=0; i<loadedOpponents.length; i++) {
        if (loadedOpponents[i].id === selectedID) {
            players[slot] = loadedOpponents[i];
            
        	updateSelectionVisuals();

            players[slot].loadBehaviour(slot, true);

            return;
        }
    }

    /* This shouldn't happen, either. */
    console.error("Could not find opponent with ID " + selectedID);
}

/************************************************************
 * The player clicked on an opponent slot.
 ************************************************************/
function selectOpponentSlot (slot) {
    if (!(slot in players)) {
        /* add a new opponent */
        selectedSlot = slot;

        /* Make sure the user doesn't have target-count sorting set if
         * the amount of loaded opponents drops to 0. */
        if (sortingMode === "Targeted most by selected") {
            var player_count = countLoadedOpponents();
            if (player_count <= 1) {
                setSortingMode("Featured");
            }
        }

		/* update the list of selectable opponents based on those that are already selected, search, and sort options */
		updateSelectableOpponents(true);

		/* reload selection screen */
		updateIndividualSelectScreen();
        updateIndividualCountStats();

        /* switch screens */
		screenTransition($selectScreen, $individualSelectScreen);
    } else {
        /* remove the opponent that's there */
        $selectImages[slot-1].off('load');
		
        delete players[slot];

        updateSelectionVisuals();
    }
}

/************************************************************
 * The player clicked on the Preset Tables or Testing Tables button.
 ************************************************************/
function clickedSelectGroupButton (screen) {
    switchSelectGroupScreen(screen)

	/* switch screens */
	screenTransition($selectScreen, $groupSelectScreen);
}

/************************************************************
 * The player clicked on the Preset Tables or Testing Tables
 * button from within the table select screen.
 ************************************************************/
function switchSelectGroupScreen (screen) {
    if (screen !== undefined) {
        groupSelectScreen = screen;
    } else {
        groupSelectScreen = 1 - groupSelectScreen;
    }
    if (groupSelectScreen == 1) {
        $groupSwitchTestingButton.html("Preset Tables");
    } else {
        $groupSwitchTestingButton.html("Testing Tables");
    }
    updateSelectableGroups(groupSelectScreen);
    updateGroupSelectScreen();
}

/************************************************************
 * Filters the list of selectable opponents based on those
 * already selected and performs search and sort logic.
 ************************************************************/
function updateSelectableGroups(screen) {
    var groupname = $groupSearchGroupName.val().toLowerCase();
    var name = $groupSearchName.val().toLowerCase();
    var source = $groupSearchSource.val().toLowerCase();
    var tag = $groupSearchTag.val().toLowerCase();

    // reset filters
    selectableGroups[screen] = loadedGroups[screen].filter(function(group) {
        if (!group.opponents.every(function(opp) { return opp; })) return false;

        if (groupname && group.title.toLowerCase().indexOf(groupname) < 0) return false;

        if (name && !group.opponents.some(function(opp) {
            return opp.label.toLowerCase().indexOf(name) >= 0
                || opp.first.toLowerCase().indexOf(name) >= 0
                || opp.last.toLowerCase().indexOf(name) >= 0;
        })) return false;

        if (source && !group.opponents.some(function(opp) {
            return opp.source.toLowerCase().indexOf(source) >= 0;
        })) return false;

        if (tag && !group.opponents.some(function(opp) {
            return opp.tags.some(function(t) {
                return t.toLowerCase() == tag;
            })
        })) return false;

        if ((chosenGroupGender == 2 || chosenGroupGender == 3)
            && !group.opponents.every(function(opp) {
                return opp.gender == (chosenGroupGender == 2 ? eGender.MALE : eGender.FEMALE);
            })) return false;

        if (chosenGroupGender == 4
            && !(group.opponents.some(function(opp) { return opp.gender == eGender.MALE; })
                 && group.opponents.some(function(opp) { return opp.gender == eGender.FEMALE; })))
            return false;

        return true;
    })
}

/************************************************************
 * Common function to selectGroup and clickedRandomGroupButton
 * to load the members of a group (preset table)
 ************************************************************/
function loadGroup (chosenGroup) {
	clickedRemoveAllButton();
	console.log(chosenGroup.title);

    /* load the group members */
	for (var i = 1; i < 5; i++) {
        var member = chosenGroup.opponents[i-1];
        if (member) {
            if (players.some(function(p, j) { return i != j && p == member; })) {
                member = member.clone();
            }
            member.loadBehaviour(i);
            players[i] = member;
        }
	}

	updateSelectionVisuals();
}

/************************************************************
 * The player clicked on the select random group slot.
 ************************************************************/
function clickedRandomGroupButton () {
	selectedSlot = 1;
	/* get a random number for the group listings */
	var randomGroupNumber = getRandomNumber(0, loadedGroups[0].length);
	var chosenGroup = loadedGroups[0][randomGroupNumber];

	loadGroup(chosenGroup);
}

/************************************************************
 * The player clicked on the all random button.
 ************************************************************/
function clickedRandomFillButton (predicate) {
	/* compose a copy of the loaded opponents list */
	var loadedOpponentsCopy = loadedOpponents.filter(function(opp) {
        // Filter out already selected characters
        return (!players.some(function(p) { return p && p.id == opp .id; })
                && (!predicate || predicate(opp)));
    });

	/* select random opponents */
	for (var i = 1; i < players.length; i++) {
		/* if slot is empty */
		if (!(i in players)) {
			/* select random opponent */
			var randomOpponent = getRandomNumber(0, loadedOpponentsCopy.length);

			/* load opponent */
			players[i] = loadedOpponentsCopy[randomOpponent];
			players[i].loadBehaviour(i);

			/* remove random opponent from copy list */
			loadedOpponentsCopy.splice(randomOpponent, 1);
		}
	}

	updateSelectionVisuals();
}

/************************************************************
 * The player clicked on the remove all button.
 ************************************************************/
function clickedRemoveAllButton ()
{
    for (var i = 1; i < 5; i++) {
        delete players[i];
        $selectImages[i-1].off('load');
    }
    updateSelectionVisuals();
}

/************************************************************
 * The player clicked on a change stats card button on the
 * individual select screen.
 ************************************************************/
function changeIndividualStats (target) {
    for (var i = 1; i < 5; i++) {
        for (var j = 1; j < 4; j++) {
            if (j != target) {
                $('#individual-stats-page-'+i+'-'+j).hide();
            }
            else {
                $('#individual-stats-page-'+i+'-'+j).show();
            }
        }
    }

    individualCreditsShown = (target == 2); // true when Credits button is clicked
}

/************************************************************
 * The player clicked the select opponent button on the
 * individual select screen.
 ************************************************************/
function selectIndividualOpponent (slot) {
    /* move the stored player into the selected slot and update visuals */
	players[selectedSlot] = shownIndividuals[slot-1];
	updateSelectionVisuals();
	players[selectedSlot].loadBehaviour(selectedSlot, true);

	/* switch screens */
	screenTransition($individualSelectScreen, $selectScreen);
}

/************************************************************
 * The player is changing the page on the individual screen.
 ************************************************************/
function changeIndividualPage (skip, page) {
    console.log("resigtered");
    if (skip) {
        if (page == -1) {
            /* go to first page */
            individualPage = 0;
        } else if (page == 1) {
            /* go to last page */
            individualPage = Math.ceil(selectableOpponents.length/4)-1;
        } else {
            /* go to selected page */
            individualPage = Number($individualPageIndicator.val()) - 1;
        }
    } else {
        individualPage += page;
    }

    updateIndividualSelectScreen();
    updateIndividualCountStats();
}

/************************************************************
 * The player clicked on a change stats card button on the
 * group select screen.
 ************************************************************/
function changeGroupStats (target) {
    for (var i = 1; i < 5; i++) {
        for (var j = 1; j < 4; j++) {
            if (j != target) {
                $('#group-stats-page-'+i+'-'+j).hide();
            }
            else {
                $('#group-stats-page-'+i+'-'+j).show();
            }
        }
    }

    groupCreditsShown = (target == 2); // true when Credits button is clicked
}

/************************************************************
 * The player clicked the select opponent button on the
 * group select screen.
 ************************************************************/
function selectGroup () {
	loadGroup(selectableGroups[groupSelectScreen][groupPage[groupSelectScreen]]);

    /* switch screens */
	screenTransition($groupSelectScreen, $selectScreen);
}

/************************************************************
 * The player is changing the page on the group screen.
 ************************************************************/
function changeGroupPage (skip, page) {
	if (skip) {
		if (page == -1) {
			/* go to first page */
      groupPage[groupSelectScreen] = 0;
		} else if (page == 1) {
			/* go to last page */
			groupPage[groupSelectScreen] = selectableGroups[groupSelectScreen].length-1;
		} else {
			/* go to selected page */
			groupPage[groupSelectScreen] = Number($groupPageIndicator.val()) - 1;
		}
	} else {
		groupPage[groupSelectScreen] += page;
	}
	updateGroupSelectScreen();
    updateGroupCountStats();
}

/************************************************************
 * The player clicked on the back button on the individual or
 * group select screen.
 ************************************************************/
function backToSelect () {
    /* switch screens */
	screenTransition($individualSelectScreen, $selectScreen);
	screenTransition($groupSelectScreen, $selectScreen);
}

/************************************************************
 * The player clicked on the start game button on the main
 * select screen.
 ************************************************************/
function advanceSelectScreen () {
    console.log("Starting game...");

    gameID = generateRandomID();

    if (USAGE_TRACKING) {
        var usage_tracking_report = {
            'date': (new Date()).toISOString(),
			'commit': VERSION_COMMIT,
            'type': 'start_game',
            'session': sessionID,
            'game': gameID,
            'userAgent': navigator.userAgent,
            'origin': getReportedOrigin(),
            'table': {},
			'tags': players[HUMAN_PLAYER].tags
        };

        for (let i=1;i<5;i++) {
            if (players[i]) {
                usage_tracking_report.table[i] = players[i].id;
            }
        }

        $.ajax({
            url: USAGE_TRACKING_ENDPOINT,
            method: 'POST',
            data: JSON.stringify(usage_tracking_report),
            contentType: 'application/json',
            error: function (jqXHR, status, err) {
                console.error("Could not send usage tracking report - error "+status+": "+err);
            },
        });
    }
    players.forEach(function(player) {
        player.preloadStageImages(0);
    });

	transcriptHistory = [];

    advanceToNextScreen($selectScreen);
}

/************************************************************
 * The player clicked on the back button on the main select
 * screen.
 ************************************************************/
function backSelectScreen () {
	screenTransition($selectScreen, $titleScreen);
}

/* The player selected an alternate costume for an opponent.
 * `slot` is the 1-based opponent slot affected.
 * `inGroup` is true if the affected opponent is on the group selection screen.
 */
function altCostumeSelected(slot, inGroup) {
	var costumeSelector = (inGroup ? $groupCostumeSelectors[slot-1] : $individualCostumeSelectors[slot-1]);
	var selectImage = (inGroup ? $groupImages[slot-1] : $individualImages[slot-1]);
	var opponent = (inGroup ? selectableGroups[groupSelectScreen][groupPage[groupSelectScreen]].opponents[slot-1] : shownIndividuals[slot-1]);
	
	var selectedCostume = costumeSelector.val();
	
	var costumeDesc = undefined;
	if (selectedCostume.length > 0) {
		for (let i=0;i<opponent.alternate_costumes.length;i++) {
			if (opponent.alternate_costumes[i].folder === selectedCostume) {
				costumeDesc = opponent.alternate_costumes[i];
				break;
			}
		}
	}
	
    opponent.selectAlternateCostume(costumeDesc);
    selectImage.attr('src', opponent.selection_image);
}


/**********************************************************************
 *****                     Display Functions                      *****
 **********************************************************************/

/************************************************************
 * Displays all of the current players on the main select
 * screen.
 ************************************************************/
function updateSelectionVisuals () {
    /* update all opponents */
    for (var i = 1; i < players.length; i++) {
		mainSelectDisplays[i-1].update(players[i]);
    }

    /* Check to see if all opponents are loaded. */
    var filled = 0, loaded = 0;
    players.forEach(function(p, idx) {
        if (idx > 0) {
            filled++;
            if (p.isLoaded()) {
                loaded++;
            }
        }
    });

    /* if enough opponents are selected, and all those are loaded, then enable progression */
    $selectMainButton.attr('disabled', filled < 2 || loaded < filled);

    /* if all slots are taken, disable fill buttons */
    $selectRandomButtons.attr('disabled', filled >= 4);

    /* if no opponents are loaded, disable remove all button */
    $selectRemoveAllButton.attr('disabled', filled <= 0 || loaded < filled);

    /* Disable buttons while loading is going on */
    $selectRandomTableButton.attr('disabled', loaded < filled);
    $groupButton.attr('disabled', loaded < filled);

    /* Update suggestions images. */
    var current_player_count = countLoadedOpponents();

    if (current_player_count >= 3) {
        var suggested_opponents = loadedOpponents.filter(function(opp) {
            /* hide selected opponents */
            if (players.some(function(p) { return p && p.id == opp.id; })) {
                return false;
            }

            return true;
        });

        /* sort opponents */
        suggested_opponents.sort(sortOpponentsByMostTargeted());

        var suggestion_idx = 0;
        for (var i=1;i<players.length;i++) {
            if (players[i] === undefined) {
                updateSuggestions(i-1, suggested_opponents, suggestion_idx);
                $selectSuggestions[i-1].show();
                suggestion_idx += 4;
            } else {
                $selectSuggestions[i-1].hide();
            }
        }
    } else {
        for (var i=0;i<4;i++) {
            $selectSuggestions[i].hide();
        }
    }
}



/************************************************************
 * This is the callback for the group clicked rows, it
 * updates information on the group screen.
 ************************************************************/
function updateGroupScreen (playerObject) {
    /* find a spot to store this player */
    for (var i = 0; i < storedGroup.length; i++) {
        if (!storedGroup[i]) {
            storedGroup[i] = playerObject;
            $groupLabels[i+1].html(playerObject.label);
            break;
        }
    }

	/* enable the button */
	$groupButton.attr('disabled', false);
}

/************************************************************
 * Hides the table on the single selection screen.
 ************************************************************/
function hideSelectionTable() {
    mainSelectHidden = !mainSelectHidden;
    if (mainSelectHidden) {
        $selectTable.hide();
    }
    else {
        $selectTable.show();
    }
}

/************************************************************
 * Hides the table on the single selection screen.
 ************************************************************/
function hideSingleSelectionTable() {
    singleSelectHidden = !singleSelectHidden;
    if (singleSelectHidden) {
        $individualSelectTable.hide();
    }
    else {
        $individualSelectTable.show();
    }
}

/************************************************************
 * Hides the table on the single group screen.
 ************************************************************/
function hideGroupSelectionTable() {
    groupSelectHidden = !groupSelectHidden;
    if (groupSelectHidden) {
        $groupSelectTable.hide();
    }
    else {
        $groupSelectTable.show();
    }
}

function openSearchModal() {
    $searchModal.modal('show');
}

function closeSearchModal() {
    // perform the search and sort logic
    updateSelectableOpponents();

    // update
    updateIndividualSelectScreen();
    updateIndividualCountStats();
}

function clearSearch() {
    $searchName.val(null);
    $searchTag.val(null);
    $searchSource.val(null);
    closeSearchModal();
}

function changeSearchGender(gender) {
    chosenGender = gender;
    setActiveOption($searchGenderOptions, gender);
}

function openGroupSearchModal() {
    $groupSearchModal.modal('show');
}

function closeGroupSearchModal() {
    // perform the search and sort logic
    updateSelectableGroups(groupSelectScreen);

    // update
    updateGroupSelectScreen();
    updateGroupCountStats();
}

function clearGroupSearch() {
    $groupSearchName.val(null);
    $groupSearchGroupName.val(null);
    $groupSearchTag.val(null);
    $groupSearchSource.val(null);
    closeGroupSearchModal();
}

function changeGroupSearchGender(gender) {
    chosenGroupGender = gender;
    setActiveOption($groupSearchGenderOptions, gender);
}


/************************************************************
 * Sorting Functions
 ************************************************************/

/**
 * Callback for Arrays.sort to sort an array of objects by the given field.
 * Prefixing "-" to a field will cause the sort to be done in reverse.
 * Examples:
 *   // sorts myArr by each element's first name (A-Z)
 *   myArr.sort(sortOpponentsByField("first"));
 *   // sorts myArr by each element's last name (Z-A)
 *   myArr.sort(sortOpponentsByField("-last"));
 */
function sortOpponentsByField(field) {
    // check for prefix
    var order = 1; // 1 = forward, -1 = reversed
    if (field[0] === "-") {
        order = -1;
        field = field.substr(1);
    }

    return function(opp1, opp2) {
        var compare = 0;
        if (opp1[field] < opp2[field]) {
            compare = -1;
        }
        else if (opp1[field] > opp2[field]) {
            compare = 1;
        }
        return order * compare;
    }
}

/**
 * Callback for Arrays.sort to sort an array of objects over multiple given fields.
 * Prefixing "-" to a field will cause the sort to be done in reverse.
 * This should allow more flexibility in the sorting order.
 * Example:
 *   // sorts myArr by each element's number of layers (low to high),
 *   // and for elements whose layers are equivalent, sort them by first name (Z-A)
 *   myArr.sort(sortOpponentsByMultipleFields("layers", "-first"));
 */
function sortOpponentsByMultipleFields() {
    var fields = arguments; // retrieve the args passed in
    return function(opp1, opp2) {
        var i = 0;
        var compare = 0;
        // if both elements have the same field, check the next ones
        while (compare === 0 && i < fields.length) {
            compare = sortOpponentsByField(fields[i])(opp1, opp2);
            i++;
        }
        return compare;
    }
}

/**
 * Special Callback for Arrays.sort to sort an array of opponents on
 * the total number of lines targeting them the currently selected
 * opponents have.
 */
function sortOpponentsByMostTargeted() {
	return function(opp1, opp2) {
		counts = [opp1, opp2].map(function(opp) {
			return players.reduce(function(sum, p) {
				if (p && p.targetedLines && opp.id in p.targetedLines) {
					sum += p.targetedLines[opp.id].count;
				}
				return sum;
			}, 0);
		});
		if (counts[0] > counts[1]) return -1;
		if (counts[0] < counts[1]) return 1;
		return 0;
	}
}

function setSortingMode(mode) {
    sortingMode = mode;
    $("#sort-dropdown-selection").html(sortingMode); // change the dropdown text to the selected option
    individualPage = 0; // reset the page number
}

/** Event handler for the sort dropdown options. Fires when user clicks on a dropdown item. */
$sortingOptionsItems.on("click", function(e) {
    setSortingMode($(this).find('a').html());
});

/************************************************************
 * Word wrapping Functions
 ************************************************************/

/**
 * Inserts a fixed-size HTML element with the specified text to allow the content
 * to be either word-wrapped (if the text is long and spaces are present)
 * or word-broken (if text is long and no spaces are present).
 */
function wordWrapHtml(text) {
    text = text || "&nbsp;";
    return "<table class=\"wrap-text\"><tr><td>" + text + "</td></tr></table>";
}

/************************************************************
 * Dynamic dialogue and image counting functions
 ************************************************************/

/** Event handler for the individual selection screen credits button. */
$individualCreditsButton.on('click', function(e) {
    updateIndividualCountStats()
});

/** Event handler for the group selection screen credits button. */
$groupCreditsButton.on('click', function(e) {
    updateGroupCountStats();
});

/**
 * Loads and displays the number of unique dialogue lines and the number of pose images
 * into the character's player object for those currently on the selection screen.
 * Only loads if the unique line count or image count is not known.
 */
function updateOpponentCountStats(opponentArr, uiElements) {
    opponentArr.forEach(function(opp, idx) {
        // load behaviour file if line/image count is not known
        if (opp && (opp.uniqueLineCount === undefined || opp.posesImageCount === undefined)) {
            uiElements.countBoxes[idx].css("visibility", "visible");

            // retrieve line and image counts
            if (DEBUG) {
                console.log("[LineImageCount] Fetching counts for " + opp.label + " in slot " + idx);
            }

            var countsPromise = new Promise(function (resolve, reject) {
                fetchCompressedURL(
                    opp.folder + 'behaviour.xml',
                    resolve, reject
                );
            });

            countsPromise.then(countLinesImages).then(function(response) {
                opp.uniqueLineCount = response.numUniqueLines;
                opp.posesImageCount = response.numPoses;

                // show line and image counts
                if (DEBUG) {
                    console.log("[LineImageCount] Loaded " + opp.label + " from behaviour: " +
                      opp.uniqueLineCount + " lines, " + opp.posesImageCount + " images");
                }
                uiElements.lineLabels[idx].html(opp.uniqueLineCount);
                uiElements.poseLabels[idx].html(opp.posesImageCount);
            });
        }
        else {
            // this character's counts were previously loaded
            if (opp) {
                if (DEBUG) {
                    console.log("[LineImageCount] Loaded previous count for " + opp.label + ": " +
                      opp.uniqueLineCount + " lines, " + opp.posesImageCount + " images)");
                }
                uiElements.countBoxes[idx].css("visibility", "visible");
                uiElements.lineLabels[idx].html(opp.uniqueLineCount);
                uiElements.poseLabels[idx].html(opp.posesImageCount);
            }
            else {
                // there is no character in the slot
                uiElements.countBoxes[idx].css("visibility", "hidden");
                uiElements.lineLabels[idx].html("");
                uiElements.poseLabels[idx].html("");
            }
        }
    });
}

/** Dialogue/image count update function for the individual selection screen. */
function updateIndividualCountStats() {
    if (individualCreditsShown) {
        var individualUIElements = {
            countBoxes : $individualCountBoxes,
            lineLabels : $individualLineCountLabels,
            poseLabels : $individualPoseCountLabels
        };
        updateOpponentCountStats(shownIndividuals, individualUIElements);
    }
}

/** Dialogue/image count update function for the group selection screen. */
function updateGroupCountStats() {
    if (groupCreditsShown) {
        var groupUIElements = {
            countBoxes : $groupCountBoxes,
            lineLabels : $groupLineCountLabels,
            poseLabels : $groupPoseCountLabels
        };
        updateOpponentCountStats(shownGroup, groupUIElements);
    }
}

/**
 * Callback to parse the number of lines of dialogue and number of images
 * given a character's behaviour XML. Returns the counts as an object with
 * properties numTotalLines, numUniqueLines, and numPoses.
 */
function countLinesImages(xml) {
    // parse all lines of dialogue and all images
	var numTotalLines = 0;
	var numUniqueDialogueLines = 0;
	var numUniqueUsedPoses = 0;
    var lines = {};
    var poses = {};
    $(xml).find('state').each(function(idx, data) {
		numTotalLines++;
		// count only unique lines of dialogue
		if (lines[data.textContent.trim()] === undefined) numUniqueDialogueLines++;
        lines[data.textContent.trim()] = 1;
		// count unique number of poses used in dialogue
		// note that this number may differ from actual image count if some images
		// are never used, or if images that don't exist are used in the dialogue
		if (poses[data.getAttribute("img")] === undefined) numUniqueUsedPoses++;
        poses[data.getAttribute("img")] = 1;
    });

    return {
        numTotalLines : numTotalLines,
        numUniqueLines : numUniqueDialogueLines,
        numPoses : numUniqueUsedPoses
    };
}