define([
	'require',
	'dgrid/List',
	'dgrid/OnDemandGrid',
	'dgrid/Selection',
	'dgrid/Keyboard',
	'dgrid/extensions/ColumnHider',
	'dojo/_base/declare',
	'dojo/_base/array',
	'dojo/dom-construct',
	'dojo/Stateful',
	'dojo/when',
	'dstore/RequestMemory',
	'dojo/domReady!'
], function (require, List, Grid, Selection, Keyboard, Hider,
		declare, arrayUtil, domConstruct, Stateful, when, RequestMemory) {
	// Create DOM
	var headerNode = domConstruct.create('div', { id: 'header' });
	var listNode = domConstruct.create('div', { id: 'list-container' });
	var genresNode = domConstruct.create('div', { id: 'genres' }, listNode);
	var artistsNode = domConstruct.create('div', { id: 'artists' }, listNode);
	var albumsNode = domConstruct.create('div', { id: 'albums' }, listNode);
	var gridNode = domConstruct.create('div', { id: 'grid' });
	// Use require.toUrl for portability (looking up via module path)
	var songStore = new RequestMemory({ target: require.toUrl('./data.json') });

	domConstruct.create('div', {
		id: 'header-content',
		innerHTML: 'dTuned'
	}, headerNode);
	arrayUtil.forEach([ headerNode, listNode, gridNode ], function (node) {
		document.body.appendChild(node);
	});

	// a formatting function for the Duration column.
	function timeFormatter(t) {
		var tmp = parseInt(t, 10);
		var min;
		var sec;

		if (isNaN(tmp)) {
			return t;
		}

		min = Math.floor(tmp / 60);
		sec = tmp % 60;
		// don't forget to pad seconds.
		return '' + min + ':' + (sec < 10 ? '0' : '') + sec;
	}

	function unique(arr) {
		// Create a unique list of items from the passed array
		// (removing duplicates).
		var ret = [];

		// First, set up a hashtable for unique objects.
		var obj = {};
		for (var i = 0, l = arr.length; i < l; i++) {
			if (!(arr[i] in obj)) {
				obj[arr[i]] = true;
			}
		}

		// Now push the unique objects back into an array, and return it.
		for (var p in obj) {
			ret.push(p);
		}
		ret.sort();
		return ret;
	}

	function pickField(fieldName) {
		return function (object) {
			return object[fieldName];
		};
	}

	// Create the main grid to appear below the genre/artist/album lists.
	var grid = new (declare([Grid, Selection, Keyboard, Hider]))({
		collection: songStore,
		columns: {
			name: 'Name',
			time: { label: 'Duration', formatter: timeFormatter },
			year: 'Year',
			artist: 'Artist',
			album: 'Album',
			genre: 'Genre'
		}
	}, gridNode);

	// define a List constructor with the features we want mixed in,
	// for use by the three lists in the top region
	var TunesList = declare([List, Selection, Keyboard], {
		selectionMode: 'single'
	});

	// define our three lists for the top.
	var genresList = new TunesList({}, genresNode);
	var artistsList = new TunesList({}, artistsNode);
	var albumsList = new TunesList({}, albumsNode);

	// create the unique lists and render them
	var genres, artists, albums;

	songStore.fetch().then(function (songs) {
		genres = unique(arrayUtil.map(songs, pickField('genre')));
		artists = unique(arrayUtil.map(songs, pickField('artist')));
		albums = unique(arrayUtil.map(songs, pickField('album')));

		genres.unshift('All (' + genres.length + ' Genre' + (genres.length !== 1 ? 's' : '') + ')');
		artists.unshift('All (' + artists.length + ' Artist' + (artists.length !== 1 ? 's' : '') + ')');
		albums.unshift('All (' + albums.length + ' Album' + (albums.length !== 1 ? 's' : '') + ')');

		genresList.renderArray(genres);
		artistsList.renderArray(artists);
		albumsList.renderArray(albums);
	});

	// As items are selected in each of the genre, artist, and album dgrid lists the
	// associated value will be set on this stateful object so the main grid can
	// watch for updates and filter accordingly
	var gridFilter = new Stateful();

	// This function is used further down by the select handler for the artists list.
	// It builds a filtered list of album names depending on the selected genre and artist.
	function getFilteredAlbumList(gridFilter, songStore, selectedArtist) {
		var filterOptions = {};

		if (gridFilter.get('genre')) {
			filterOptions.genre = gridFilter.get('genre');
		}

		if (selectedArtist) {
			filterOptions.artist = selectedArtist;
		}

		return songStore.filter(filterOptions).fetch().then(function (filteredObjects) {
			var list = unique(arrayUtil.map(filteredObjects, pickField('album')));
			list.unshift('All (' + list.length +
				' Album' + (list.length !== 1 ? 's' : '') + ')');
			return list;
		});
	}

	gridFilter.watch(function () {
		var filter;

		if (this.genre || this.artist || this.album) {
			filter = {};

			if (this.genre) {
				filter.genre = this.genre;
			}
			if (this.artist) {
				filter.artist = this.artist;
			}
			if (this.album) {
				filter.album = this.album;
			}

			grid.set('collection', songStore.filter(filter));
		}
		else {
			if (grid.collection !== songStore) {
				grid.set('collection', songStore);
			}
		}
	});

	// start listening for selections on the lists.
	genresList.on('dgrid-select', function (event) {
		// filter the albums, artists and grid
		var row = event.rows[0];
		var selectedGenre = row.data;
		var filteredArtistList;

		if (row.id === '0') {
			// remove filtering
			gridFilter.set('genre', undefined);
			filteredArtistList = artists;
		}
		else {
			gridFilter.set('genre', selectedGenre);
			// filter the store on the current genre
			filteredArtistList = songStore.filter({ genre: selectedGenre }).fetch().then(function (filteredObjects) {
				// map the full album objects to a unique array of artist names (strings)
				var list = unique(arrayUtil.map(filteredObjects, pickField('artist')));
				// add the "All" option at the top
				list.unshift('All (' + list.length +
					' Artist' + (list.length !== 1 ? 's' : '') + ')');
				return list;
			});
		}

		when(filteredArtistList, function (list) {
			artistsList.refresh(); // clear contents
			artistsList.renderArray(list);
			artistsList.select('0'); // reselect "all", triggering albums+grid refresh
		});
	});

	artistsList.on('dgrid-select', function (event) {
		// filter the albums, grid
		var row = event.rows[0];
		var selectedArtist = row.data;
		var filteredAlbumList;

		if (row.id === '0') {
			gridFilter.set('artist', undefined);

			if (gridFilter.get('genre')) {
				// filter only by genre
				filteredAlbumList = getFilteredAlbumList(gridFilter, songStore);
			} else {
				// remove filtering entirely
				filteredAlbumList = albums;
			}
		}
		else {
			// create filter based on artist
			gridFilter.set('artist', selectedArtist);
			filteredAlbumList = getFilteredAlbumList(gridFilter, songStore, selectedArtist);
		}

		when(filteredAlbumList, function (list) {
			albumsList.refresh(); // clear contents
			albumsList.renderArray(list);
			albumsList.select('0'); // reselect "all" item, triggering grid refresh
		});
	});

	albumsList.on('dgrid-select', function (event) {
		// filter the grid
		var row = event.rows[0];
		var selectedAlbum = row.data;

		if (row.id === '0') {
			// show all albums
			gridFilter.set('album', undefined);
		} else {
			gridFilter.set('album', selectedAlbum);
		}
	});

	// set the initial selections on the lists.
	genresList.select('0');
});