/**
* @fileoverview This file contains internal definitions used in DsClient
*
* @author anders.rejdebrant@spark-vision.com (Anders Rejdebrant)
*/
goog.provide('spv.ds.impl.SessionState');
goog.require('spv.ds.ImageSerieInfo');
goog.require('spv.ds.UrlSettings');
goog.require('spv.ds.impl.Bump');
goog.require('spv.ds.impl.BumpSerie');
/**
* Manages all client side session data and provides functions for lookup and
* mutation of that data.
*
* The only client side session data not handled by this class is the session id
* used by the current implementation of the server protocol. Management of
* session id the responsibility of the protocol adaptor.
*
* @struct
* @constructor
* @param {spv.ds.impl.SessionInitData} init_data
*/
spv.ds.impl.SessionState = function(init_data)
{
/**
* @private
* @type {spv.ds.impl.SessionInitData}
*/
this._init_data = init_data;
/**
* @private
* @type {null|string}
*/
this._category_id = null;
/**
* @private
* @type {Object.<string, string>}
*/
this._category_short_texts = {};
/**
* @private
* @type {Object.<string, string>}
*/
this._category_medium_texts = {};
/**
* @private
* @type {Object.<string, string>}
*/
this._category_long_texts = {};
/**
* @private
* @type {Object.<string, InternalTextMapCollection>}
*/
this._all_texts = convertToInternalTextMapCollections(init_data);
/**
* @private
* @type {null|string}
*/
this._model_id = null;
/**
* @private
* @type {null|spv.ds.SessionStateUpdate}
*/
this._state_update = null;
/**
* @private
* @type {spv.ds.impl.AuxData}
*/
this._aux_data = new spv.ds.impl.AuxData();
};
/**
* Test if category and model has been set, throw on failure
*/
spv.ds.impl.SessionState.prototype.assertHasLoadedConfig = function()
{
this.getCategoryId();
this.getModelId();
};
/**
* Get the auxiliary data that came with the last bop.
*
* @return {spv.ds.impl.AuxData}
*/
spv.ds.impl.SessionState.prototype.getAuxiliaryData = function()
{
return this._aux_data;
};
/**
* Get the image serie information for the current configuration
*
* @return {Array.<spv.ds.ImageSerieInfo>}
*/
spv.ds.impl.SessionState.prototype.getImageSerieInfos = function()
{
var bump = getBump(this._init_data, this.getCategoryId());
var model_series = spv.ds.impl.SessionState.getBumpSeries(bump, this.getModelId());
/** @type {Array.<spv.ds.ImageSerieInfo>} */
var image_serie_infos = [];
for( var i=0; i < model_series.length; ++i )
image_serie_infos.push( convertToImageSerieInfo( model_series[i] ) );
return image_serie_infos;
};
/**
* Get the root menues from bump data
*
* @return {Array.<spv.ds.MenuItem>}
*/
spv.ds.impl.SessionState.prototype.getRootMenuItems = function()
{
var bump = getBump(this._init_data, this.getCategoryId());
return bump.menus;
};
/**
* Update the client side session state
*
* @param {spv.ds.SessionStateUpdate} state_update
* @param {spv.ds.impl.AuxData} aux_data
*/
spv.ds.impl.SessionState.prototype.update = function(state_update, aux_data)
{
this.setCategoryId(state_update);
this.setModelId(state_update);
this._state_update = state_update;
this._aux_data = aux_data;
this.updateTexts( state_update );
};
/**
* Updates the text database.
*
* @param {spv.ds.SessionStateUpdate} state_update
*/
spv.ds.impl.SessionState.prototype.updateTexts = function( state_update )
{
for( var i=0, ii=state_update.menu_items.length; i<ii; ++i )
{
var menu_item = state_update.menu_items[i];
var id = menu_item.id;
var texts = this._all_texts[ menu_item.category ];
texts.short_texts[ id ] = menu_item.short_text;
texts.medium_texts[ id ] = menu_item.medium_text;
texts.long_texts[ id ] = menu_item.long_text;
}
};
/**
* Get the last session state update
*
* @return {spv.ds.SessionStateUpdate}
*/
spv.ds.impl.SessionState.prototype.getUpdate = function()
{
if (this._state_update)
{
return this._state_update;
}
throw new Error('SessionState.prototype.getUpdate failed, no state found.');
};
/**
* Get the category id for the current configuration or throw if no config
*
* @private
* @return {string}
*/
spv.ds.impl.SessionState.prototype.getCategoryId = function()
{
if (this._category_id)
{
return this._category_id;
}
throw new Error('category_id is not set, no configuration loaded');
};
/**
* Get 'short_text' text for item_id or empty string on failure
*
* @param {string} item_id
* @return {string}
*/
spv.ds.impl.SessionState.prototype.getShortText = function(item_id)
{
var text = this._category_short_texts[item_id];
if (text)
{
return text;
}
return '';
};
/**
* Get 'medium_text' text for item_id or empty string on failure
*
* @param {string} item_id
* @return {string}
*/
spv.ds.impl.SessionState.prototype.getMediumText = function(item_id)
{
var text = this._category_medium_texts[item_id];
if (text)
{
return text;
}
return '';
};
/**
* Get 'long_text' text for item_id or empty string on failure
*
* @param {string} item_id
* @return {string}
*/
spv.ds.impl.SessionState.prototype.getLongText = function(item_id)
{
var text = this._category_long_texts[item_id];
if (text)
{
return text;
}
return '';
};
/**
* Get base urls needed for most GET operations on images etc.
*
* @return {spv.ds.UrlSettings}
*/
spv.ds.impl.SessionState.prototype.getUrlSettings = function()
{
var cat_ids = getCategoryIds(this._init_data);
if (cat_ids.length < 1)
{
throw new Error('No categories found in session init data.');
}
var category_id = cat_ids[0];
var bump = getBump(this._init_data, category_id);
var result = new spv.ds.UrlSettings(
bump.gui_url,
bump.icon_url,
bump.composed_url,
bump.readmore_content_url,
bump.summary_content_url);
return result;
};
/**
* Sets the current category id
*
* @private
* @param {spv.ds.SessionStateUpdate} state_update
*/
spv.ds.impl.SessionState.prototype.setCategoryId = function(state_update)
{
var cat_id = state_update.category;
if (!cat_id)
{
throw new Error('The category id is invalid: ' + cat_id);
}
var cat_ids = getCategoryIds(this._init_data);
if (!arrayContains(cat_ids, cat_id))
{
throw new Error('The category id is missing in init data: ' + cat_id);
}
this._category_id = cat_id;
this.rebindTextLookupObjects();
};
/**
* Updates the text lookup object references for category texts.
* Intended as a speculative performance optimization as well as a
* simplification of text lookup code.
*
* @private
*/
spv.ds.impl.SessionState.prototype.rebindTextLookupObjects = function()
{
var cat_id = this.getCategoryId();
var cat_texts = this._all_texts[cat_id];
if (!cat_texts)
{
cat_texts = new InternalTextMapCollection({}, {}, {});
}
this._category_short_texts = cat_texts.short_texts;
this._category_medium_texts = cat_texts.medium_texts;
this._category_long_texts = cat_texts.long_texts;
};
/**
* Get the model id for the current configuration or throw if no config
*
* @private
* @return {string}
*/
spv.ds.impl.SessionState.prototype.getModelId = function()
{
if (this._model_id)
{
return this._model_id;
}
throw new Error('model_id is not set, no configuration loaded');
};
/**
* Sets the current model id
*
* @private
* @param {spv.ds.SessionStateUpdate} state_update
*/
spv.ds.impl.SessionState.prototype.setModelId = function(state_update)
{
var model_id = state_update.model_id;
if (!model_id)
{
throw new Error('The new model_id is invalid: ' + model_id);
}
this._model_id = model_id;
};
/* -----------------------------------------------------------------------------
* Private utility functions.
*
* This looks like a members of global scope but it is not as long as you
* compile with an outer function wrapper. /AR
*/
/**
* Lookup the BumpSerie for a model
*
* @private
* @param {spv.ds.impl.Bump} bump
* @param {string} model_id
* @return {Array.<spv.ds.impl.BumpSerie>}
*/
// TODO: Put all private functions into the SessionState class.
spv.ds.impl.SessionState.getBumpSeries = function(bump, model_id)
{
var bump_series = bump.series;
/** @type {Array.<spv.ds.impl.BumpSerie>} */
var series_of_model = [];
for (var i = 0; i < bump_series.length; i++)
{
var serie = bump_series[i];
if (model_id == serie.model_id)
{
series_of_model.push(serie);
}
}
if( series_of_model.length == 0 )
throw new Error('Could not find image serie for model_id: ' + model_id);
else
return series_of_model;
};
/**
* @param {spv.ds.impl.SessionInitData} sid
* @param {string} category_id
* @return {spv.ds.impl.Bump}
*/
var getBump = function(sid, category_id)
{
var bumps = sid.bumps;
for (var i = 0; i < bumps.length; i++)
{
var bump = bumps[i];
if (category_id == bump.category)
{
return bump;
}
}
throw new Error('No bump found for category: ' + category_id);
};
/**
* Convert internal representation of image serie to the exposed type
*
* @param {spv.ds.impl.BumpSerie} bump_serie
* @return {spv.ds.ImageSerieInfo}
*/
var convertToImageSerieInfo = function(bump_serie)
{
return new spv.ds.ImageSerieInfo(
bump_serie.id,
bump_serie.model_id,
bump_serie.index,
bump_serie.first_frame,
bump_serie.last_frame,
bump_serie.frames_order,
bump_serie.image_width,
bump_serie.image_output_width,
bump_serie.image_height,
bump_serie.image_output_height);
};
/**
* Convert init data to collection of faster text lookup objects
*
* @param {spv.ds.impl.SessionInitData} init_data
* @return {Object.<string, InternalTextMapCollection>}
*/
var convertToInternalTextMapCollections = function(init_data)
{
/**
* @type {Object.<string, InternalTextMapCollection>}
*/
var result = {};
var cat_ids = getCategoryIds(init_data);
for (var i = 0; i < cat_ids.length; i++)
{
var cat_id = cat_ids[i];
var bump = getBump(init_data, cat_id);
var text_dict = convertToInternalTextMapCollection(bump);
result[cat_id] = text_dict;
}
return result;
};
/**
* @param {spv.ds.impl.SessionInitData} sid
* @return {Array.<string>}
*/
var getCategoryIds = function(sid)
{
if (!sid)
{
throw new Error('Parameter sid (SessionInitData) is not defined');
}
var bumps = sid.bumps;
if (!bumps)
{
throw new Error('sid.bumps (Array.<Bump>) is not defined');
}
/** @type {Array.<string>} */
var result = [];
for (var i = 0; i < bumps.length; i++)
{
result.push(bumps[i].category);
}
return result;
};
/**
* @param {spv.ds.impl.Bump} bump
* @return {InternalTextMapCollection}
*/
var convertToInternalTextMapCollection = function(bump)
{
/**
* @type {Object.<string, string>}
*/
var short_texts = {};
/**
* @type {Object.<string, string>}
*/
var medium_texts = {};
/**
* @type {Object.<string, string>}
*/
var long_texts = {};
var bump_items = bump.items;
for (var i = 0; i < bump_items.length; i++)
{
var bump_item = bump_items[i];
var item_id = bump_item.id;
short_texts[item_id] = bump_item.short_text;
medium_texts[item_id] = bump_item.medium_text;
long_texts[item_id] = bump_item.long_text;
}
var bump_menus = bump.menus;
for (var j = 0; j < bump_menus.length; j++)
{
var bump_menu = bump_menus[j];
var menu_id = bump_menu.id;
short_texts[menu_id] = bump_menu.short_text;
medium_texts[menu_id] = bump_menu.medium_text;
}
var result = new InternalTextMapCollection(
short_texts,
medium_texts,
long_texts);
return result;
};
/**
* Helper for browser compatibility.
* TODO: test indexOf and see what closure compiler translates it too and if it
* augments the Array prototype if the function is missing. (run tests in IE6)
*
* @param {Array.<string>} arr
* @param {string} elem
*/
var arrayContains = function(arr, elem)
{
for (var i = 0; i < arr.length; i++)
{
if (arr[i] == elem)
{
return true;
}
}
return false;
};
/**
* Internal collection of text lookup objects
*
* @private
* @struct
* @constructor
* @param {Object.<string, string>} short_texts
* @param {Object.<string, string>} medium_texts
* @param {Object.<string, string>} long_texts
*/
var InternalTextMapCollection = function(
short_texts,
medium_texts,
long_texts)
{
/**
* @type {Object.<string, string>}
*/
this.short_texts = short_texts;
/**
* @type {Object.<string, string>}
*/
this.medium_texts = medium_texts;
/**
* @type {Object.<string, string>}
*/
this.long_texts = long_texts;
};