ds/impl/IPPNovaAdaptor.js

/**
 *
 * @fileoverview This file contains the IPad Protocol Adaptor and its related
 *               utility function. There is quite a lot of code dedicated to
 *               data structure conversion, this is intentional and provides a
 *               layer of abstraction between the exported web service code and
 *               GUI code. It is unclear at the time of writing if the VIViD
 *               protocol will be implemented in this client or if the ipad
 *               protocol will be implemented in the stand alone server. This
 *               abstraction of the protocol implementation will allow for both
 *               options.
 *
 * TODO: add more assertions of protocol data conversions later.
 *
 * @author anders.rejdebrant@spark-vision.com (Anders Rejdebrant)
 */

goog.provide('spv.ds.impl.IPPNovaAdaptor');

goog.require('spv.ds.Config');
goog.require('spv.ds.ConfigLoadResult');
goog.require('spv.ds.DisplayPrice');
goog.require('spv.ds.FacebookShareData');
goog.require('spv.ds.FreshConfig');
goog.require('spv.ds.GuiPath');
goog.require('spv.ds.GuiPathItem');
goog.require('spv.ds.ImageParams');
goog.require('spv.ds.ImageResult');
goog.require('spv.ds.IntroPage');
goog.require('spv.ds.IntroPageConfig');
goog.require('spv.ds.IntroPageGroup');
goog.require('spv.ds.ItemResources');
goog.require('spv.ds.ItemSummary');
goog.require('spv.ds.ItemState');
goog.require('spv.ds.Loan');
goog.require('spv.ds.MenuItem');
goog.require('spv.ds.MenuNode');
goog.require('spv.ds.ReadMoreResource');
goog.require('spv.ds.SessionStateUpdate');
goog.require('spv.ds.Summary');
goog.require('spv.ds.PresentationStructure');
goog.require('spv.ds.impl.AuxData');
goog.require('spv.ds.impl.Bump');
goog.require('spv.ds.impl.BumpItem');
goog.require('spv.ds.impl.BumpSerie');
goog.require('spv.ds.impl.IPPNovaAdaptor');
goog.require('spv.ds.impl.IPPCommon');
goog.require('spv.ds.impl.SessionInitData');

goog.require('spv.ds.ipprot_nova.IpadClient');
goog.require('spv.ds.ipprot_nova.output.Bump');
goog.require('spv.ds.ipprot_nova.output.BumpItem');
goog.require('spv.ds.ipprot_nova.output.BumpMenu');
goog.require('spv.ds.ipprot_nova.output.BumpSerie');
goog.require('spv.ds.ipprot_nova.output.Category');
goog.require('spv.ds.ipprot_nova.output.ConfigBop');
goog.require('spv.ds.ipprot_nova.output.FacebookShareData');
goog.require('spv.ds.ipprot_nova.output.FreshConfig');
goog.require('spv.ds.ipprot_nova.output.GetConfigResult');
goog.require('spv.ds.ipprot_nova.output.GuiItem');
goog.require('spv.ds.ipprot_nova.output.GuiPath');
goog.require('spv.ds.ipprot_nova.output.MenuNode');
goog.require('spv.ds.ipprot_nova.output.OutBop');
goog.require('spv.ds.ipprot_nova.output.OutBopSummary');
goog.require('spv.ds.ipprot_nova.output.OutBopUpdate');
goog.require('spv.ds.ipprot_nova.output.PdfGenerationResult');
goog.require('spv.ds.ipprot_nova.output.PresentationStructure');
goog.require('spv.ds.ipprot_nova.output.PresentationStructureItemState');
goog.require('spv.ds.ipprot_nova.output.ReadMoreResourceDefinition');
goog.require('spv.ds.ipprot_nova.output.ReadMoreResources');
goog.require('spv.ds.ipprot_nova.output.SavedConfig');
goog.require('spv.ds.ipprot_nova.output.SavedUser');
goog.require('spv.ds.ipprot_nova.output.SessionInitData');
goog.require('spv.ds.ipprot_nova.output.UserConfigInfo');
goog.require('spv.ds.ipprot_nova.definition.Configuration');
goog.require('spv.ds.ipprot_nova.definition.Groups');
goog.require('spv.ds.ipprot_nova.definition.Image');
goog.require('spv.ds.ipprot_nova.definition.Root');




/**
 * Protocol adaptor for spv.ds.ipprot_nova.IpadClient
 *
 * Resposible for hiding protocol data types and interpreting reponse data to
 * failures in cases where errors are reported inside the normal result data.
 * eg. Result codes from load config operations.
 *
 * @constructor
 * @implements {spv.ds.impl.IDsProtocol}
 * @param {string} service_url
 * @param {boolean} use_jsonp
 */
spv.ds.impl.IPPNovaAdaptor = function(service_url, use_jsonp)
{
	/** @type {spv.ds.ipprot_nova.IpadClient} */
	this._impl = new spv.ds.ipprot_nova.IpadClient(service_url, use_jsonp);
};




/**
 * Fetches an image for a serialized configuration.
 *
 * @param {spv.ds.Config} conf
 * @param {spv.ds.ImageParams} img_params
 * @param {function(spv.ds.ImageResult)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.getConfigImage = function(
		conf,
		img_params,
		on_success,
		on_failure)
{
	var proto_success = function(image_url)
	{
		var result = new spv.ds.ImageResult(image_url);
		on_success(result);
	};
	this._impl.get_config_image(
			conf.data,
			conf.encoding,
			img_params.scaled_width,
			img_params.scaled_height,
			img_params.serie_name,
			img_params.frame,
			img_params.crop_x_ratio,
			img_params.crop_y_ratio,
			img_params.cropped_width,
			img_params.cropped_height,
			img_params.skip.join(';'),
			img_params.skip_groups.join(';'),
			img_params.only_use.join(';'),
			img_params.only_use_groups.join(';'),
			img_params.file_type,
			img_params.use_hd,
			img_params.keep_aspect_ratio,
			img_params.auto_crop,
			img_params.bg_color_hex,
			proto_success,
			on_failure);
};




/**
 * @param {function(spv.ds.IntroPage)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.getIntroPage = function(
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.definition.Root} proto_intro_page */
	var proto_success = function(proto_intro_page)
	{
		var intro_page = spv.ds.impl.IPPCommon.convertToIntroPage(proto_intro_page);
		on_success(intro_page);
	};
	this._impl.init_settings(proto_success, on_failure);
};




/**
 * @param {string} item_id
 * @param {string} model
 * @param {string} category
 * @param {function(Array.<spv.ds.ReadMoreResource>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.getReadMore = function(
	item_id,
	model,
	category,
	language,
	on_success,
	on_failure)
{
	this._impl.get_readmore(
		item_id,
		model,
		category,
		language,
		on_success_wrapper,
		on_failure );

	/**
	 * @param {spv.ds.ipprot_nova.output.ReadMoreResources} raw_result
	 */
	function on_success_wrapper( raw_result )
	{
		/** @type {Array<spv.ds.ReadMoreResource>} */
		var result = [];
		raw_result.resources.forEach( function(raw)
		{
			var resource = new spv.ds.ReadMoreResource();
			resource.path = raw.path;
			resource.resource_type = raw.resource_type;
			resource.media_tag = raw.media_tag;
			resource.media_group = raw.media_group;
			resource.media_index = raw.media_index;
			resource.attachment = raw.attachment;
			resource.file_size = raw.file_size;
			resource.attachment_link_text = raw.attachment_link_text;
			resource.width = raw.width;
			resource.height = raw.height;
			resource.bitrate_bitps = raw.bitrate_bitps;
			resource.start_time_ms = raw.start_time_ms;
			resource.auto_play = raw.auto_play;
			result.push( resource );
		});
		on_success( result );
	}
};



/**
 * @param {Array<string>} item_ids
 * @param {string} category
 * @param {string} model
 * @param {string} language
 * @param {function(Object<string,spv.ds.ItemResources>)} on_success
 * @param {function(Error)} on_failure
 **/
spv.ds.impl.IPPNovaAdaptor.prototype.getItemResources = function(
	item_ids,
	category,
	model,
	language,
	on_success,
	on_failure)
{
	this._impl.get_item_resources(
		category,
		model,
		language,
		item_ids,
		on_success_wrapper,
		on_failure)

	/** @param {spv.ds.ipprot_nova.output.GetItemResourcesResult} result */
	function on_success_wrapper( result )
	{
		var resources = {};
		for( var k in result.resources )
		{
			if( !result.resources.hasOwnProperty(k) ) continue;
			var r = new spv.ds.ItemResources();
			r.name = result.resources[k].name;
			r.short_text = result.resources[k].short_text;
			r.has_readmore = result.resources[k].has_readmore;
			r.icon = result.resources[k].icon;
			resources[k] = r;
		}
		on_success( resources );
	}
}





/**
 * @param {string} username
 * @param {function(Array.<spv.ds.SavedConfig>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.getUserConfigList = function(
	username,
	on_success,
	on_failure)
{
	/* TODO: output.SavedUser isn't actually serialized as the structure shows.
	   This is due to SavedUserConverter not registering the names it serializes,
	   so the api export has no way of knowing the names! :'( */

	/** @param {?} saved_user
	  * @suppress {missingProperties}
	  */
	var on_saved_user = function( saved_user )
	{
		/** @type {Array.<spv.ds.SavedConfig>} */
		var config_list = [];
		for( var i=0, ii = saved_user['user_configs'].length; i < ii; ++i )
		{
			var raw_saved_config = saved_user['user_configs'][i];
			/* Get default encoded config data. */
			var encoded_configs = raw_saved_config['config_data']['encoded_configs'];
			var config_data = [];
			for( var j=0, jj = encoded_configs.length; j < jj; ++j )
			{
				config_data.push( new spv.ds.Config(
					encoded_configs[j]['encoding'],
					encoded_configs[j]['config'] ));
			};


			config_list.push( new spv.ds.SavedConfig(
				raw_saved_config['config_name'],
				config_data) );
		}

		on_success( config_list );
	};

	this._impl.user_config_list( username, on_saved_user, on_failure );
};




/**
 * @param {string} session_id
 * @param {Array.<string>} group_ids
 * @param {function()} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionAddGuiGroupSubscriptions = function(
	session_id,
	group_ids,
	on_success,
	on_failure )
{
	this._impl.add_gui_group_subscriptions(
		session_id,
		group_ids,
		on_success,
		on_failure );
};




/**
 * @param {string} session_id
 * @param {function()} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionClearGuiGroupSubscriptions = function(
	session_id,
	on_success,
	on_failure )
{
	this._impl.clear_gui_group_subscriptions(
		session_id,
		on_success,
		on_failure );
};




/**
 * @param {string} session_id
 * @param {function(string)} on_success    Callback taking the generated pdf url
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGeneratePdf = function(
		session_id,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.PdfGenerationResult} pdf_result */
	var handle_protocol = function(pdf_result)
	{
		var pdf_url = spv.ds.impl.IPPCommon.extractPdfUrl( pdf_result );
		on_success(pdf_url);
	};
	this._impl.generate_pdf(
			session_id,
			'',
			false,
			false,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} encoding
 * @param {function(spv.ds.Config)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetConfig = function(
		session_id,
		encoding,
		on_success,
		on_failure)
{
	/** @param {string} conf_data */
	var on_protocol_success = function(conf_data)
	{
		var conf = new spv.ds.Config(encoding, conf_data);
		on_success(conf);
	};
	this._impl.get_config_as_string(
			session_id,
			encoding,
			on_protocol_success,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {function(Array.<string>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetConfigNames = function(
		session_id,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.GetConfigResult} result */
	var handle_protocol = function(result) {
	    var result_code = result.ResultCode;
	    if (result_code == 'SUCCESS') {
	        on_success(result.Output);
	        return;
	    }

	    throw new Error(result_code);
	};

	this._impl.get_configs(session_id, handle_protocol, on_failure);
};




/**
 * @param {string} session_id
 * @param {function(spv.ds.FacebookShareData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetFacebookShareData = function(
		session_id,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.FacebookShareData} proto_data */
	var handle_protocol = function(proto_data)
	{
		var share_data = spv.ds.impl.IPPCommon.convertToFacebookShareData(proto_data);
		on_success(share_data);
	};
	this._impl.get_facebook_share_data(
			session_id,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {number} limit
 * @param {number} scale_width
 * @param {number} scale_height
 * @param {function(Array.<spv.ds.FreshConfig>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetFreshConfigs = function(
		session_id,
		limit,
		scale_width,
		scale_height,
		on_success,
		on_failure)
{
	this.getFreshConfigs(
			limit,
			scale_width,
			scale_height,
			on_success,
			on_failure );
}




/**
 * @param {number} limit
 * @param {number} scale_width
 * @param {number} scale_height
 * @param {function(Array.<spv.ds.FreshConfig>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.getFreshConfigs = function(
		limit,
		scale_width,
		scale_height,
		on_success,
		on_failure)
{
	/** @param {Array.<spv.ds.ipprot_nova.output.FreshConfig>} proto_data */
	var handle_protocol = function(proto_data)
	{
		var fresh_configs = spv.ds.impl.IPPCommon.convertToFreshConfigs(proto_data);
		on_success(fresh_configs);
	};
	var ctime = 0;
	var meta_filter = 'MODEL;ENGINE;GEAR';
	this._impl.get_fresh_configs(
			ctime,
			limit,
			meta_filter,
			scale_width,
			scale_height,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {spv.ds.ImageParams} img_params
 * @param {function(spv.ds.ImageResult)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetImage = function(
		session_id,
		img_params,
		on_success,
		on_failure)
{
	/** @param {string} image_url */
	var handle_image_success = function(image_url)
	{
		var result = new spv.ds.ImageResult(image_url);
		on_success(result);
	};
	this._impl.get_current_config_image(
			session_id,
			img_params.scaled_width,
			img_params.scaled_height,
			img_params.serie_name,
			img_params.frame,
			img_params.crop_x_ratio,
			img_params.crop_y_ratio,
			img_params.cropped_width,
			img_params.cropped_height,
			img_params.skip.join(';'),
			img_params.skip_groups.join(';'),
			img_params.only_use.join(';'),
			img_params.only_use_groups.join(';'),
			img_params.file_type,
			img_params.use_hd,
			img_params.keep_aspect_ratio,
			img_params.auto_crop,
			img_params.bg_color_hex,
			handle_image_success,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {Array.<string>} item_ids
 * @param {string} language
 * @param {function(Array.<spv.ds.MenuItem>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetItemInfo = function(
		session_id,
		item_ids,
		language,
		on_success,
		on_failure)
{
	/** @param {Array.<spv.ds.ipprot_nova.output.OutBopUpdate>} obus */
	var on_proto_success = function(obus)
	{
		var result = spv.ds.impl.IPPCommon.convertToMenuItems(obus);
		on_success(result);
	};
	this._impl.get_item_info(
			session_id,
			item_ids.join(';'),
			language,
			on_proto_success,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {number} serie_index
 * @param {number} frame_index
 * @param {string} file_type
 * @param {boolean} separate_background
 * @param {boolean} separate_shadow
 * @param {boolean} all_frames
 * @param {boolean} use_hd
 * @param {number} image_width
 * @param {number} image_height
 * @param {function(string)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetMediaKit = function(
	session_id,
	serie_index,
	frame_index,
	file_type,
	separate_background,
	separate_shadow,
	all_frames,
	use_hd,
	image_width,
	image_height,
	on_success,
	on_failure)
{
	this._impl.get_media_kit(
		session_id,
		serie_index,
		frame_index,
		file_type,
		separate_background,
		separate_shadow,
		all_frames,
		use_hd,
		image_width,
		image_height,
		on_success,
		on_failure);
};




/**
 * @param {string} session_id
 * @param {string} item_id
 * @param {function(Array.<spv.ds.impl.GoalStateResolver>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetStateResolvers = function(
		session_id,
		item_id,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.OutResolvers} out_resolver */
	var on_protocol_success = function(out_resolver)
	{
		var goal_resolver = spv.ds.impl.IPPCommon.convertToGoalStateResolver(out_resolver);
		var result = [goal_resolver];
		on_success(result);
	};
	this._impl.get_state_resolvers(
			session_id,
			item_id,
			on_protocol_success,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {function(spv.ds.Summary)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetSummary = function(
		session_id,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.OutBop} out_bop */
	var handle_protocol = function(out_bop)
	{
		var s = spv.ds.impl.IPPCommon.extractSummary(out_bop);
		on_success(s);
	};
	this._impl.bip(
			session_id,
			'',
			'BTN_SUMMARY',
			'',
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} config_name
 * @param {spv.ds.ImageParams} img_params
 * @param {function(spv.ds.ImageResult)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetUserConfigImage = function(
		session_id,
		config_name,
		img_params,
		on_success,
		on_failure)
{
	var proto_success = function(image_url)
	{
		var result = new spv.ds.ImageResult(image_url);
		on_success(result);
	};
	this._impl.get_user_config_image(
			session_id,
			config_name,
			img_params.scaled_width,
			img_params.scaled_height,
			img_params.serie_name,
			img_params.frame,
			img_params.crop_x_ratio,
			img_params.crop_y_ratio,
			img_params.cropped_width,
			img_params.cropped_height,
			img_params.skip.join(';'),
			img_params.skip_groups.join(';'),
			img_params.only_use.join(';'),
			img_params.only_use_groups.join(';'),
			img_params.file_type,
			img_params.use_hd,
			img_params.keep_aspect_ratio,
			img_params.auto_crop,
			img_params.bg_color_hex,
			proto_success,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} filter_name
 * @param {spv.ds.PresentationStructureQuery} query
 * @param {function(spv.ds.PresentationStructure)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGetPresentationStructure = function(
		session_id,
		filter_name,
		query,
		on_success,
		on_failure )
{

	var q = new spv.ds.ipprot_nova.input.PresentationStructureQuery();

	if( query.options )
	{
		q.Options = new spv.ds.ipprot_nova.presentationfilter.FilterGroupOptions();
		q.Options.OnlyVisible = query.options.only_visible;
		q.Options.OnlySelected = query.options.only_selected;
		q.Options.ExcludeEmptyGroups = query.options.exclude_empty_groups;
	}

	if( query.path_regex )
	{
		q.PathRegex = query.path_regex;
	}

	this._impl.get_presentation_structure_v2(
			session_id,
			filter_name,
			q,
			my_on_success,
			on_failure );

	/** @param {spv.ds.ipprot_nova.output.PresentationStructure} raw */
	function my_on_success(raw)
	{
		var item_states = {};
		for( var prop in raw.items )
		{
			var raw_item = raw.items[prop];
			item_states[prop] = new spv.ds.ItemState(
				raw_item.visible,
				raw_item.selected );
		}

		var query = new spv.ds.PresentationStructureQuery();
		if( raw.query )
		{
			if( raw.query.Options )
			{
				query.options = new spv.ds.PresentationStructureQueryOptions();
				query.options.only_visible = raw.query.Options.OnlyVisible;
				query.options.only_selected = raw.query.Options.OnlySelected;
				query.options.exclude_empty_groups = raw.query.Options.ExcludeEmptyGroups;
			}
			if( raw.query.PathRegex )
			{
				query.path_regex = raw.query.PathRegex['Pattern'];
			}
		}

		var ps = new spv.ds.PresentationStructure(
			raw.filter,
			query,
			raw.nodes,
			item_states );

		on_success(ps);
	}
}




/**
 * Hides multiple server calls required to initialize
 *
 * @param {string} menu_query
 * @param {spv.ds.ipprot_nova.input.BopReadMoreMode} readmore_mode
 * @param {string} language
 * @param {number} ttl Time To Live for this session in seconds
 * @param {string} origin URL of the embedding page
 * @param {string} category
 * @param {function(spv.ds.impl.SessionInitData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionCreate = function(
		menu_query,
		readmore_mode,
		language,
		ttl,
		origin,
		category,
		on_success,
		on_failure)
{
	/** @type {spv.ds.impl.SessionInitData} */
	var sid;

	var disabled_flag_mode = spv.ds.ipprot_nova.input.BopDisabledFlagMode.IGNORE_DISABLED_FLAG;
	/** @param {spv.ds.ipprot_nova.output.SessionInitData} proto_sid */
	var on_session_success = function(proto_sid)
	{
		sid = spv.ds.impl.IPPCommon.convertToSessionInitData(proto_sid);
		on_success(sid);
	};
	this._impl.create_session(
			menu_query,
			readmore_mode,
			language,
			disabled_flag_mode,
			ttl,
			origin,
			category,
			on_session_success,
			on_failure);
};



/**
 * @param {string} session_id
 * @param {string} language
 * @param {function(spv.ds.impl.SessionInitData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionResume = function(
		session_id,
		language,
		on_success,
		on_failure)
{
	this._impl.get_categories( load_categories, on_failure );

	var proto_bumps = [];
	var ipp = this;

	function load_categories( categories )
	{
		load_bumps( categories, 0 );
	}
	function load_bumps( categories, i )
	{
		if( categories.length <= i )
		{
			ipp._impl.init_settings(
				finish,
				on_failure );
			return;
		}

		ipp._impl.get_bump(
			session_id,
			categories[i].ID,
			language,
			store_bump_and_iterate,
			on_failure );

		function store_bump_and_iterate( proto_bump )
		{
			proto_bumps.push( proto_bump );
			load_bumps( categories, i+1 );
		}
	}
	function finish(proto_intro_page)
	{
		var bumps = spv.ds.impl.IPPCommon.convertToBumps( proto_bumps );
		var intro_page = spv.ds.impl.IPPCommon.convertToIntroPage(proto_intro_page);
		var init_data = new spv.ds.impl.SessionInitData(
			session_id,
			bumps,
			intro_page);
		on_success( init_data );
	}
};




/**
 * @param {string} session_id
 * @param {spv.ds.Config} config
 * @param {string} language
 * @param {function(spv.ds.ConfigLoadResult, spv.ds.SessionStateUpdate, spv.ds.impl.AuxData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionLoadConfig = function(
		session_id,
		config,
		language,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.ConfigBop} config_bop */
	var handle_protocol = function(config_bop)
	{
		var config_load_result = spv.ds.impl.IPPCommon.convertToConfigLoadResult(config_bop);
		var out_bop = config_bop.bops[0];
		var state_update = spv.ds.impl.IPPCommon.convertToSessionStateUpdate(out_bop);
		var aux_data = spv.ds.impl.IPPCommon.extractAuxiliaryData(out_bop);
		on_success(
			config_load_result,
			state_update,
			aux_data );
	};
	this._impl.load_config_from_string(
			session_id,
			config.data,
			config.encoding,
			language,
			"MemorizeNothing",
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} config_name
 * @param {function(spv.ds.ConfigLoadResult, spv.ds.SessionStateUpdate, spv.ds.impl.AuxData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionLoadConfigByName = function(
		session_id,
		config_name,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.ConfigBop} config_bop */
	var handle_protocol = function(config_bop)
	{
		var config_load_result = spv.ds.impl.IPPCommon.convertToConfigLoadResult(config_bop);
		var out_bop = config_bop.bops[0];
		var state_update = spv.ds.impl.IPPCommon.convertToSessionStateUpdate(out_bop);
		var aux_data = spv.ds.impl.IPPCommon.extractAuxiliaryData(out_bop);
		on_success(config_load_result, state_update, aux_data);
	};
	this._impl.load_config(
			session_id,
			config_name,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} config_name
 * @param {function(spv.ds.ConfigLoadResult, spv.ds.SessionStateUpdate, spv.ds.impl.AuxData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionLoadPublicConfig = function(
		session_id,
		config_name,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.ConfigBop} config_bop */
	var handle_protocol = function(config_bop)
	{
		var config_load_result = spv.ds.impl.IPPCommon.convertToConfigLoadResult(config_bop);
		var out_bop = config_bop.bops[0];
		var state_update = spv.ds.impl.IPPCommon.convertToSessionStateUpdate(out_bop);
		var aux_data = spv.ds.impl.IPPCommon.extractAuxiliaryData(out_bop);
		on_success(config_load_result, state_update, aux_data);
	};
	this._impl.load_public_config(
			session_id,
			config_name,
			handle_protocol,
			on_failure);
};



/**
 * @param {string} session_id
 * @param {function(string)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionGeneratePublicConfig = function(
	session_id,
	on_success,
	on_failure )
{
	var handle_protocol = function(result)
	{
		if(result == 'SERVICE_FAILURE')
			throw new Error(result);
		else
			on_success(result);
	};
	this._impl.generate_public_config(
		session_id,
		"", // Sending an empty string makes booster generate a unique name.
		handle_protocol,
		on_failure );
}



/**
 * @param {string} session_id
 * @param {string} user_name
 * @param {string} password
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionLogin = function(
		session_id,
		user_name,
		password,
		on_success,
		on_failure)
{
	/** @param {string} result_code */
	var handle_protocol = function(result_code)
	{
		if ('SUCCESS' == result_code)
		{
			on_success();
			return;
		}
		throw new Error('sessionLogin failed with code: ' + result_code);
	};
	this._impl.login(
			session_id,
			user_name,
			password,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {function(boolean)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionLogout = function(
		session_id,
		on_success,
		on_failure)
{
	this._impl.logout(
		session_id,
		on_success,
		on_failure);
};




/**
 * @param {string} session_id
 * @param {string} user_name
 * @param {string} password
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionCreateUser = function(
		session_id,
		user_name,
		password,
		on_success,
		on_failure)
{
	this._impl.create_user(
		session_id,
		user_name,
		password,
		on_success,
		on_failure );
};




/**
 * @param {string} session_id
 * @param {function(boolean)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionHasLoggedInUser = function(
		session_id,
		on_success,
		on_failure)
{
	this._impl.session_has_logged_in_user(
		session_id,
		on_success,
		on_failure);
};




/**
 * @param {string} session_id
 * @param {string} main_url
 * @param {string} user_name
 * @param {string} language
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionSendResetPasswordMail = function(
		session_id,
		main_url,
		user_name,
		language,
		on_success,
		on_failure)
{
	this._impl.send_reset_password_mail(
		session_id,
		main_url,
		user_name,
		language,
		on_success,
		on_failure);
};




/**
 * @param {string} token
 * @param {string} new_password
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.resetPassword = function(
		token,
		new_password,
		on_success,
		on_failure)
{
	this._impl.reset_password(
		token,
		new_password,
		on_success,
		on_failure);
}




/**
 * @param {string} session_id
 * @param {string} config_name
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionRemoveConfigByName = function(
		session_id,
		config_name,
		on_success,
		on_failure)
{
	/** @param {string} result_code */
	var handle_protocol = function(result_code)
	{
		if ('SUCCESS' == result_code)
		{
			on_success();
			return;
		}
		throw new Error('Remove config failed with code: ' + result_code);
	};
	this._impl.remove_config(
			session_id,
			config_name,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} config_name
 * @param {boolean} allow_overwrite
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionSaveConfigByName = function(
		session_id,
		config_name,
		allow_overwrite,
		on_success,
		on_failure)
{
	var handle_protocol = function(result_code)
	{
		if ('SUCCESS' == result_code)
		{
			on_success();
			return;
		}
		throw new Error(result_code);
	};
	this._impl.save_config(
			session_id,
			config_name,
			allow_overwrite,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} price_localization_id
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionSetPriceLocalization = function(
		session_id,
		price_localization_id,
		on_success,
		on_failure)
{
	/** @param {boolean} result_flag */
	var handle_protocol = function(result_flag)
	{
		if (true == result_flag)
		{
			on_success();
		}
		else
		{
			throw new Error('sessionSetPriceLocalization failed for id: ' +
							price_localization_id);
		}
	};
	this._impl.set_price_localization(
			session_id,
			price_localization_id,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} item_id
 * @param {string} category
 * @param {function(spv.ds.SessionStateUpdate, spv.ds.impl.AuxData)} on_bop
 * @param {function(Array.<spv.ds.impl.GoalStateResolver>, spv.ds.BipAttemptResultCode, string)} on_resolvers
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionToggleItem = function(
	session_id,
	item_id,
	category,
	language,
	on_bop,
	on_resolvers,
	on_failure )
{
	this.sessionAttemptBip(
		session_id,
		[item_id],
		category,
		language,
		on_bop,
		on_resolvers,
		on_failure);
};




/**
 * @private
 * @param {string} session_id
 * @param {Array.<string>} item_ids
 * @param {string} category
 * @param {string} language
 * @param {function(spv.ds.SessionStateUpdate, spv.ds.impl.AuxData)} on_bop
 * @param {function(Array.<spv.ds.impl.GoalStateResolver>, spv.ds.BipAttemptResultCode, string)} on_resolvers
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionAttemptBip = function(
	session_id,
	item_ids,
	category,
	language,
	on_bop,
	on_resolvers,
	on_failure)
{
	var handle_success = function( bip_attempt )
	{
		if( spv.ds.ipprot_nova.output.BipAttemptResultCode.ACTIVATED ==
			bip_attempt.attempt_result_code )
		{
			var state_update = spv.ds.impl.IPPCommon.convertToSessionStateUpdate( bip_attempt.bop );
			var aux_data = spv.ds.impl.IPPCommon.extractAuxiliaryData( bip_attempt.bop );
			on_bop(state_update, aux_data);
		}
		else if( spv.ds.ipprot_nova.output.BipAttemptResultCode.RESOLVERS ==
				bip_attempt.attempt_result_code )
		{
			var goal_resolver = spv.ds.impl.IPPCommon.convertToGoalStateResolver( bip_attempt.resolvers );
			var result = [goal_resolver];
			on_resolvers( result, bip_attempt.attempt_result_code, bip_attempt.resolvers_text);
		}
		else if( spv.ds.ipprot_nova.output.BipAttemptResultCode.MULTIPLE_RESOLVERS ==
				bip_attempt.attempt_result_code )
		{
			var goal_resolvers = [];
			for (var i = 0; i < bip_attempt.multiple_resolvers.length; i++) {
				var resolver = spv.ds.impl.IPPCommon.convertToGoalStateResolver(bip_attempt.multiple_resolvers[i]);
				goal_resolvers.push(resolver);
			}

			on_resolvers(goal_resolvers, bip_attempt.attempt_result_code, bip_attempt.resolvers_text);
		}
	};

	var input = item_ids.join(";");

	if( language )
		this._impl.attempt_bip_v2(
			session_id,
			input,
			language,
			handle_success,
			on_failure);
	else
		this._impl.attempt_bip(
				session_id,
				category, // Actually deprecated argument.
				input,
				handle_success,
				on_failure);
}



/**
 * @param {string} session_id
 * @param {string} category
 * @param {Array.<string>} item_ids
 * @param {string} language
 * @param {function(spv.ds.SessionStateUpdate, spv.ds.impl.AuxData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionBip = function(
		session_id,
		category,
		item_ids,
		language,
		on_success,
		on_failure)
{
	/** @param {spv.ds.ipprot_nova.output.OutBop} out_bop */
	var handle_protocol = function(out_bop)
	{
		var state_update = spv.ds.impl.IPPCommon.convertToSessionStateUpdate(out_bop);
		var aux_data = spv.ds.impl.IPPCommon.extractAuxiliaryData(out_bop);
		on_success(state_update, aux_data);
	};
	this._impl.bip(
			session_id,
			category,
			item_ids.join(';'),
			language,
			handle_protocol,
			on_failure);
};




/**
 * @param {string} session_id
 * @param {string} language
 * @param {function(spv.ds.SessionStateUpdate, spv.ds.impl.AuxData)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionUpdateBop = function(
	session_id,
	language,
	on_success,
	on_failure )
{
	/** @param {spv.ds.ipprot_nova.output.OutBop} out_bop */
	var handle_protocol = function( out_bop )
	{
		var state_update = spv.ds.impl.IPPCommon.convertToSessionStateUpdate(out_bop);
		var aux_data = spv.ds.impl.IPPCommon.extractAuxiliaryData(out_bop);
		on_success(state_update, aux_data);
	};
	this._impl.rebop( session_id, language, handle_protocol, on_failure );
}




/**
 * @param {string} session_id
 * @param {function(string)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.sessionSendHeartBeat = function(
	session_id,
	on_success,
	on_failure)
{
	this._impl.heartbeat(session_id, on_success, on_failure);
}




/**
 * @param {string} session_id
 * @param {number} config_id
 * @param {string} description
 * @param {Object.<string,string>} user_config_aux
 * @param {Object.<string,string>} config_storage_aux
 * @param {function()} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.userConfigV2Save = function(
	session_id,
	config_id,
	description,
	user_config_aux,
	config_storage_aux,
	on_success,
	on_failure)
{
	this._impl.user_config_v2_save(
		session_id,
		config_id,
		description,
		user_config_aux,
		config_storage_aux,
		on_success,
		on_failure);
};


/**
 * @param {string} session_id
 * @param {string} config_name
 * @param {string} config_description
 * @param {Object.<string,string>} user_config_aux
 * @param {Object.<string,string>} config_storage_aux
 * @param {function(number)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.userConfigV2SaveAs = function(
	session_id,
	config_name,
	config_description,
	user_config_aux,
	config_storage_aux,
	on_success,
	on_failure)
{
	this._impl.user_config_v2_save_as(
		session_id,
		config_name,
		config_description,
		user_config_aux,
		config_storage_aux,
		on_success,
		on_failure);
};


/**
 * @param {string} session_id
 * @param {number} config_id
 * @param {function()} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.userConfigV2Delete = function(
	session_id,
	config_id,
	on_success,
	on_failure)
{
	this._impl.user_config_v2_delete(
		session_id,
		config_id,
		on_success,
		on_failure);
};


/**
 * @param {string} session_id
 * @param {function(Array<spv.ds.UserConfigInfo>)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.userConfigV2List = function(
	session_id,
	on_success,
	on_failure)
{
	/**
	 * @param {Array<spv.ds.ipprot_nova.output.UserConfigInfo>} raw
	 * @return {Array<spv.ds.UserConfigInfo>}
	 */

	function convertConfigList( raw )
	{
		var list = [];
		for( var i=0, ii=raw.length; i < ii; ++i )
		{
			var r = raw[i]
			var c = new spv.ds.UserConfigInfo();
			c.name = r.Name;
			c.description = r.Description;
			c.id = r.Id;
			c.timestamp = r.Timestamp;
			c.user_config_aux = r.UserConfigAux;
			c.config_storage_aux = r.ConfigStorageAux;

			c.image_urls = {};
			for( var j=0, jj=r.Images.length; j < jj; ++j )
			{
				var image = r.Images[j];
				c.image_urls[image.ReferenceKey] = image.Url;
			}

			list.push(c);
		}
		return list;
	}

	function on_success_wrapper( list )
	{
		var c_list = convertConfigList(list);
		on_success( c_list );
	}

	this._impl.user_config_v2_list(
		session_id,
		on_success_wrapper,
		on_failure);
};


/**
 * @param {string} session_id
 * @param {number} config_id
 * @param {function()} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.userConfigV2Load = function(
	session_id,
	config_id,
	on_success,
	on_failure)
{
	this._impl.user_config_v2_load(
		session_id,
		config_id,
		on_success,
		on_failure);
};



/**
 * @param {string} session_id
 * @param {string} public_config_id
 * @param {Function} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.publicConfigV2Load = function(
	session_id,
	public_config_id,
	on_success,
	on_failure)
{
	this._impl.public_config_v2_load(
		session_id,
		public_config_id,
		on_success,
		on_failure);
};



/**
 * @param {string} session_id
 * @param {string} description
 * @param {Object.<string, string>} config_storage_aux
 * @param {function(spv.ds.PublicConfigInfo)} on_success
 * @param {function(Error)} on_failure
 */
spv.ds.impl.IPPNovaAdaptor.prototype.publicConfigV2Save = function(
	session_id,
	description,
	config_storage_aux,
	on_success,
	on_failure)
{
	this._impl.public_config_v2_save(
		session_id,
		description,
		config_storage_aux,
		on_success_wrapper,
		on_failure );

	function on_success_wrapper( result )
	{
		var converted_result = convert_public_config_info( result );
		on_success( converted_result );
	}

	function convert_public_config_info( raw )
	{
		var pci = new spv.ds.PublicConfigInfo();
		pci.guid = raw.Guid;
		pci.description = raw.Description;
		pci.config_storage_aux = raw.ConfigStorageAux;

		pci.image_urls = {};
		for( var j=0, jj=raw.Images.length; j < jj; ++j )
		{
			var image = raw.Images[j];
			pci.image_urls[image.ReferenceKey] = image.Url;
		}

		return pci;
	}
};