/*jslint laxbreak:true */
/*jslint laxcomma:true */
/*jslint loopfunc:true */
/*jslint strict:true */
/*jslint browser:true */
/*jslint devel:true */
define([
	"underscore"
	, "backbone"
	, "toolbox"
	, "models/dmap-type"
	, "collections/dmap"
]
, function (_, Backbone, Toolbox, DMAPType, DMAPCollection) {
	"use strict";

	var browserTags	= {
		"abal":		"abal"
		, "abar":	"abar"
		, "abcp":	"abcp"
		, "abgn":	"abgn"
		, "abro":	"abro"
	}

	, Model	= Backbone.Model.extend({
		idAttribute:	"_id"

		, initialize:	function (attributes, options) {
			_.bindAll(this
				, "getAttibutesId"
				, "toJSON"
				, "update"
				, "parse"
				, "fetch"
				, "save"
				, "destroy"
				, "unset"
				, "clear"
			);

			var contentCodes = options && options.contentCodes || null;

			if (attributes instanceof ArrayBuffer) {//(options.buffer instanceof ArrayBuffer) {
				attributes	= parseWrapper(attributes, contentCodes);
			}

			if (attributes && attributes.byteLength) {
				delete attributes.byteLength;
			}

			attributes._id	= this.getAttributesId(attributes);

			this.set(attributes);

			return this;
		}

		, getAttributesId:	function(attributes) {
			return /*attributes["dmap_persistentid"] || */attributes["dmap_itemid"] || attributes["dmap_containeritemid"] || null;
		}

		, toJSON: function(options) {
			var attrs	= _.clone(this.attributes);

			if (!_.isUndefined(attrs._id)) {
				attrs.id	= attrs._id;
				delete attrs._id;
			}

			return attrs;
		}

		, update:		function (attributes, options) {
			this.trigger("dmap.model.update", this, arguments);

			return this.apply(this, arguments);
		}

		, parse:		function (reponse) {
			return this.attributes;
		}

		, fetch:		function (options) {
			this.trigger("dmap.model.fetch", this, arguments);

			return this;
		}

		, save:			function (attributes, options) {
			this.trigger("dmap.model.save", this, arguments);

			return this;
		}

		, destroy:		function (options) {
			this.trigger("dmap.model.destroy", this, arguments);

			return this;
		}

		, unset:		function (options) {
			this.trigger("dmap.model.unset", this, arguments);

			return this;
		}

		, clear:		function (options) {
			this.trigger("dmap.model.unset", this, arguments);

			return this;
		}
	})

	, parseContentCode	= function(dictChunk) {
		var codeElement	= {};

		while (dictChunk.byteLength > 8) {
			var contentCode		= DMAPType.prototype.getType(dictChunk);
			var contentLength	= DMAPType.prototype.getInt32(dictChunk.slice(4,8));
			//console.log(contentLength);

			switch (contentCode) {
				case 'mcnm':
					codeElement.code	= DMAPType.prototype.getString(dictChunk.slice(8, 8+contentLength));
				break;

				case 'mcna':
					var codeName		= DMAPType.prototype.getString(dictChunk.slice(8, 8+contentLength));
					codeElement.name	= codeName.replace(/\./g, '_');
				break;

				case 'mcty':
					var typeData		= DMAPType.prototype.getInt16(dictChunk.slice(8, 8+contentLength));
					codeElement.type	= new DMAPType(typeData);
				break;
			}

			dictChunk	=	dictChunk.slice(8+contentLength);
		}

		return codeElement;
	}

	, parseBinaryChunk		= function (binaryChunk, contentCodes) {
		var entry	= {};

		while (binaryChunk.byteLength > 8) {
			var tagName		= DMAPType.prototype.getType(binaryChunk);
			var tagLength	= DMAPType.prototype.getInt32(binaryChunk.slice(4,8));

			if (tagName !== "mstt") { // Skip the status code, we really don't care for it.
				if (tagName === "mdcl") { // Our contentCodes is probably empty as we found the content codes
					var contentCode		= parseContentCode(binaryChunk.slice(8, 8+tagLength));
					var codeId			= contentCode.code.toLowerCase();

					entry[codeId]		= _.extend({}, contentCode, contentCode.type);
				} else {
					var codeInfo	= contentCodes.get(tagName);

					if (!_.isObject(codeInfo)) {
						console.info("Unknow content code: " + tagName, tagLength);
					} else {
						if (codeInfo.getCode() !== 12 ) {
							entry[codeInfo.name]		= codeInfo.unpack(binaryChunk.slice(8), tagLength);
						} else if ((tagName in browserTags) === true) {
							tagLength				= binaryChunk.byteLength;	// This is how fucked DAAP is, we overwrite the length because
																				// in this specific case 'mlit' which is reported as a container is in fact a string
							var browserList			= binaryChunk.slice(8);
							entry[codeInfo.name]	= [];

							while (browserList.byteLength > 8) {
								var itemTag				= DMAPType.prototype.getType(browserList);
								var itemLength			= DMAPType.prototype.getInt32(browserList.slice(4,8));

								entry[codeInfo.name].push(DMAPType.prototype.getUTFString(browserList.slice(8), itemLength));

								browserList	= browserList.slice(8 + itemLength);
							}
						} else if (codeInfo.getCode() === 12) {
							var result	= parseBinaryChunk(binaryChunk.slice(8, tagLength), contentCodes);

							if (result) {
								var keys = null;

								if (_.isString(result)) {
									if ( !(entry[codeInfo.name] instanceof DMAPCollection) ) {
										entry[codeInfo.name]	= [];
									}

									entry[codeInfo.name].push(result);
								} else if (getItemId(result) !== null) {
									if ( !(entry[codeInfo.name] instanceof DMAPCollection) ) {
										entry[codeInfo.name]	= new DMAPCollection();
									}

									entry[codeInfo.name].add(new Model(result));
								// Here we found a listing item and we are going to squash the single item array
								} else if (_.isObject(result) && Object.keys(result).length === 1) {
									entry[codeInfo.name]	= result[Object.keys(result)[0]];
								}
							} else {
								// We could alternatively autodetect the type here
							}
						}
					}
				}
			}

			binaryChunk	= binaryChunk.slice(8 + tagLength);
		}

		return entry;
	}

	, parseWrapper			= function(binaryData, contentCodes) {
		if (binaryData.byteLength <= 8) {
			return parseBinaryChunk(binaryData, contentCodes);
		} else {
			var containerType	= DMAPType.prototype.getType(binaryData);
			//var binaryLength	= DMAPType.prototype.getUint32(binaryData.slice(4,8));
			var contentData		= binaryData.slice(8);
			switch (containerType) {
				// Update
				//case "mupd":
				//	// Unsupported
				//	return;
				//break;
				// Browsing list
				case "abro":
				// Content codes
				case "mccr":
				// Server Info
				case "msrv":
				// Login
				case "mlog":
				// Item listing
				case "mlit":
				// Database
				case "adbs":
				// Database info
				case "avdb":
				// Playlist info
				case "aply":
				// List of songs
				case "apso":
					return parseBinaryChunk(contentData, contentCodes);
				default:
					console.info("Unknown type: " + containerType, contentData);
					return parseBinaryChunk(binaryData, contentCodes);
			}
		}
	}

	, getItemId			= function (item) {
		return item["dmap_persistentid"] || item["dmap_itemid"] || item["dmap_containeritemid"] || null;
	}
	;

	return Model;
});