const {dialog} = require('electron').remote
var sqlite3 = require('sqlite3').verbose();
const ipc = require('electron').ipcRenderer
const fs = require("fs");
const path = require('path')

class App {

	constructor() {
		this.db = new Db()
		Templates.init()
		this.loadComponents()
	}


	loadComponents() {
		this.Player = new Player()		
		this.loadPlaylists()
		this.mainControls()
		new PlaylistEditor()
		Tab.init()
	}

	loadPlaylists() {

		this.db.serialize(() => {
			let items = []
			this.db.each('SELECT *, t2.id as id FROM songs t1 INNER JOIN playlist_items t2 ON t1.id = t2.song_id  ORDER BY t2.position ASC', (err, row) => {
				if(typeof items[row.playlist_id] == 'undefined') {
					items[row.playlist_id] = []
				} 

				items[row.playlist_id].push(row)
			})

			this.db.each('SELECT * FROM playlists ORDER BY position ASC', (err, row) => {
				new Playlist(row, items[row.id])
			})
		})
	}

	mainControls() {

		$(document).on('click' , '.Tray' , () => {
			ipc.send('put-in-tray')
		})

		$(document).on('click' , '.Close' , () => {
			ipc.send('quit')
		})

		ipc.on('tray-removed', function () {
			ipc.send('remove-tray')
		})		
	}
}

let DbInstance = null
class Db {

	constructor() {

		if(!DbInstance) {
			this.create()		
			DbInstance = this.db
		}

		return DbInstance
	}


	create() {
		this.db = new sqlite3.Database('src/amp3r.db');
	}
}

class Player {

	constructor() {

		if(!Player.Instance) {
			this.init()
			Player.Instance = this
		}

		return Player.Instance
	}

	init() {
		this.Instance = this
		this.player =  WaveSurfer.create({
		    container: '#waveform',
		    waveColor: '#efefef',
		    progressColor: '#ff4949', 
		    height: 42
		});

		this.eventHandler()
		this.db = new Db()
		this.timer = new Timer()
		this.t = null
	}

	eventHandler() {
		this.handlePlayPause()
		this.handleStop()
		this.handleLoad()
		this.handleSeek()
		this.handleFinish()
		this.handleError()
		this.handleNext()
		this.handlePrev()
	}

	handlePlayPause() {
		$(document).on('click' , '.Play' , () => {
			this.player.playPause()
		})

		this.player.on('play' , () => {
			$('.Play').html('<i class="zmdi zmdi-pause"></i>')
			if(!this.t) {
				this.interval()
			}
		})

		this.player.on('pause' , () => {
			$('.Play').html('<i class="zmdi zmdi-play"></i>')
		})		
	}

	handleStop() {
		$(document).on('click' , '.Stop' , () => {
			this.stop()
		})
	}

	handleLoad() {
		this.player.on('ready' , () => {
			this.player.play()
			Flash.fade(this.msg)
			this.timer.setDuration(this.player.getDuration())

			this.db.serialize(() => {
				this.db.run('UPDATE songs SET play_count = play_count +1 WHERE id = ?', this.current.data('song_id'))
			})		
		})
	}

	handleSeek() {
		this.player.on('seek' , () => {
			this.timer.setCurrent(this.player.getCurrentTime())
		})
	}

	handleFinish() {
		this.player.on('finish' , () => {
			this.stop()
			this.next()
		})
	}

	handleError() {
		this.player.on('error' , (err) => {
			this.stop()
			Flash.error('This file could not be played')
			this.next()
		})
	}

	handleNext() {
		$(document).on('click' , '.Next' , () => {
			this.next()
		})
	}

	handlePrev() {
		$(document).on('click' , '.Prev' ,() => {
			this.prev()
		})
	}

	interval() {
		this.t = true
		this.t = setInterval(() => {
			this.timer.setCurrent(this.player.getCurrentTime())
		}, 1000)		
	}

	load(source) {
		this.player.pause()
		this.player.load(source)
	}

	setTitle(value) {
		$('.App-title').text(value)
	}

	play(object) {

		if(object.hasClass('active') === false) {
			this.load(object.data('path'))
			this.current = object
			Flash.clear()
			this.msg = Flash.success('loading...', false)
			$('.Song').removeClass('active')
			object.addClass('active')
			this.setTitle(object.data('title'))			
		}
	}

	stop(hard) {
		$('.Song').removeClass('active')
		this.player.stop()
		clearInterval(this.t)
		this.t = null		
	}

	next() {

		let object
		this.player.stop()
		
		if(!$('.Song.active').length) {
			object = $('.TabPane.active .Song').eq(0)
		} else {
			object = $('.Song.active').next('.Song')
		}

		if(object.length) {
			this.play(object)
			return 
		}

		Flash.error('End of playlist')
	}

	prev() {

		let object 
		this.player.stop()

		if(!$('.Song.active').length) {
			object = $('.TabPane.active .Song').last()
			this.play(object)
			return 
		}

		if($('.Song.active').prev('.Song').length) {
			object = $('.Song.active').prev('.Song')
			this.play(object)
			return 
		}
	}

}

class Timer {

	constructor() {
		this.current = $('.Current')
		this.duration = $('.Duration')
	}

	setCurrent(value) {
		this.current.text(Helper.format(value))
	}

	setDuration(value) {
		this.duration.text(Helper.format(value))
	}

}

class Tab {

	static init() {

		if(!Tab.Initialised) {
			Tab.Initialised = true
			Tab.setVars()
			Tab.eventHandler()		
			Tab.contextMenu()
		}

		Tab.refresh()
	}

	static setVars() {
		Tab.state = 0
		Tab.step = 40
		Tab.leftBtn = $('.TabLeft')
		Tab.rightBtn = $('.TabRight')
		Tab.addBtn = $('.Add')
		Tab.outerDiv = $('.TabContainer')
		Tab.innerDiv = $('.TabInner')		
	}

	static refresh() {
		Tab.outerWidth = Tab.outerDiv.outerWidth()
		Tab.innerWidth = Tab.innerDiv.outerWidth()
		Tab.addWidth = Tab.addBtn.outerWidth()

		if(Tab.innerWidth > Tab.outerWidth) {
			Tab.rightBtn.removeClass('hidden')
		}
	}

	static eventHandler() {

		Tab.handleResize()
		Tab.handleClickLeft()
		Tab.handleClickRight()
	}

	static handleResize() {

		$(window).on('resize' , () => {
			Tab.refresh()
		})		
	}

	static handleClickLeft() {

		Tab.leftBtn.click(() => {
			if(Tab.state > 0) {
				Tab.state -= Tab.step
				Tab.innerDiv.css({
					'margin-left' : '-'+ (Tab.state) +'px'
				})
			}
			if(Tab.state == 0) {
				Tab.leftBtn.addClass('hidden')
			}
		})
	}

	static handleClickRight() {

		Tab.rightBtn.click(() => {

			if(Tab.state < Tab.innerWidth - Tab.outerWidth + this.step) {
				Tab.state += Tab.step
				Tab.innerDiv.css({
					'margin-left' : '-'+ (Tab.state) +'px'
				})
				Tab.leftBtn.removeClass('hidden')
			}
		})
	}

	static contextMenu() {
		const menuObject = Object.create(contextMenu)
		menuObject.init('.Nav .TabButton', [
			{
				type : 'header', 
				text : 'options'
			}, 
			{
				type : 'item',
				text : '<i class="zmdi zmdi-edit"></i> Rename', 
				attr : {
					class : 'playlistRename', 
					'data-id' : function(e) {
						let el = $(e.currentTarget) 
						return el.data('id')
					}
				}
			}, 
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-arrow-left"></i> Move left', 
				display : (e) => {

					let el = $(e.currentTarget) 
					if(el.prev('.TabButton').length) {
						return true
					}

					return false
				},
				attr : {
					class : 'playlistMoveLeft', 
					'data-id' : (e) => {
						let el = $(e.currentTarget)
						return el.data('id')
					} 
				}
			}, 
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-arrow-right"></i> Move right', 
				display : (e) => {

					let el = $(e.currentTarget)
					if(el.next('.TabButton').length) {
						return true
					}

					return false
				}, 
				attr : {
					class : 'playlistMoveRight', 
					'data-id' : (e) => {
						let el = $(e.currentTarget) 
						return el.data('id')
					}
				}
			}, 
			{
				type : 'divider'
			}, 
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-delete"></i> Delete', 
				attr : {
					class : 'playlistDelete', 
					'data-id' : (e) => {
						let el = $(e.currentTarget)
						return el.data('id')
					}
				}
			}
		])				
	}

	/**
	 *	Search result generating new "virtual" temporary playlist with negative id.
	 *	User may have multiple playlists, so new appended playlist could not visible on screen - lets move this freakin navbar
	 */
	static forceMove(id) {
		let el = $('.TabButton[data-id="'+id+'"]'), 
			offset = el.offset().left, 
			offsetAll = offset + el.outerWidth()

		if(offsetAll < $(window).width() - Tab.state) {
			return 
		}

		while(Tab.state + $(window).width() <= offsetAll + 50) {
			Tab.state += 40
			Tab.innerDiv.css({
				'margin-left' : '-'+ (Tab.state) +'px'
			})
			Tab.leftBtn.removeClass('hidden')			
		}
	}

}

class Playlist {

	constructor(playlist, songs = [], isSearchResult = false) {

		this.db = new Db()

		this.playlist = playlist 
		this.songs = songs
		this.isSearchResult = isSearchResult

		this.createButton()
		this.createPlaylist()

		this.eventHandler()
	}

	createButton() {

		let html = Templates.get('TabButton' , {
				id : this.playlist.id, 
				title : this.playlist.title
			})

		this.button = $(html)
		$('.Nav').append(this.button)
		Tab.init()

		if(!Playlist.active) {
			Playlist.active = true
			this.is_active = true
			this.button.addClass('active')
		}

		if(this.isSearchResult) {
			$('.TabButton').removeClass('active')
			this.button.addClass('active')			
			$('.TabPane').removeClass('active')
			this.is_active = true
			Tab.forceMove(this.playlist.id)
		}
		
	}

	createPlaylist() {

		let songs = []

		for(let i = 0; i < this.songs.length; i++) {
			songs.push(new Song(this.songs[i]))
		}

		let pane = Templates.get('TabPane', {
			id : this.playlist.id
		})

		this.html = $(pane)
		this.html.find('.Playlist').append(songs)

		if(this.is_active) {
			this.html.addClass('active')
		}

		if(this.isSearchResult) {
			this.html.find('.Foot').html('').css({
				height : '62px'
			})
			this.button.trigger('click')
		}

		$('.Tabs').append(this.html)

	}

	eventHandler() {
		this.handleClickNavbar()
		this.handleOpenFile()
		this.handleOpenFolder()
		this.handleDblClick()
		this.handleSortable()
		this.handleSearch()
	}

	handleClickNavbar() {

		this.button.click(() => {
			$('.Nav').find('.TabButton').removeClass('active')
			this.button.addClass('active')
			$('.TabPane').removeClass('active')
			$('.TabPane[data-id="'+this.button.data('id')+'"]').addClass('active')
		})
	}

	handleDblClick() {
		this.button.on('dblclick' , () => {
			let Editor =  new PlaylistEditor()
			Editor.edit(this)
		})
	}

	handleSortable() {
		let Editor = new PlaylistEditor()
		Editor.songsSortable(this.html)
	}

	handleSearch() {

		this.html.find('.Search').click(() => {
			this.html.find('.SearchDiv').removeClass('hidden')
		})	

		this.html.find('.SearchInput').keyup((e) => {

			if(e.which == 27) {
				this.html.find('.SearchDiv').addClass('hidden')
				return false
			}
		})	

		this.html.find('.SearchInput').keydown((e) => {

			if(e.which == 13) {
				this.db.serialize(() => {
					this.db.all(' SELECT t2.id, t2.song_id, title, path, rate FROM songs t1 INNER JOIN playlist_items t2 ON t2.song_id = t1.id AND t2.playlist_id = ? WHERE t1.title LIKE "%" || ? || "%" ', this.playlist.id , this.html.find('.SearchInput').val() , (err, rows) => {

						new Playlist({
							id : -(Math.round(Math.random() * 100000)), 
							title:'Search result: '+this.html.find('.SearchInput').val(), 
						}, rows, true)
					})
				})

				return false
			}
		})
	}

	/**
	 *	Idea - database could store unique filepaths, but searching by possible very long string is possible performance death.
	 	We can store crc32() from path and we can searh by Integer
	 	In big collections (more than 10k) crc32() collision probability is pretty high
	 	So: 1) Check if crc32() from filepath exist in database - if not - definitely path not exist in database, and we can insert new Song Entity
	 	2) If exist - loop thru result and compare filepaths in results with potentially new file
	 	3) If file exist in database - insert only new PlaylistItem entity with song_id and playlist_id
	 	4) Iff not - insert new Song Entity and new PlaylistItem entity
	 */
	handleOpenFile() {

		this.html.find('.OpenFile').click(() => {
			dialog.showOpenDialog({
				filters : [{
					name : "Music",
					extensions : ["mp3"]
				}], 
				properties : ["multiSelections"]
			} , (data) => {
				if(data) {

					this.db.serialize(() => {

						for(let i = 0; i < data.length; i++) {

							this.findFile(data[i])
						}
						
					})

				}
			})			
		})
	}

	handleOpenFolder() {

		this.html.find('.OpenDir').click(() => {

			dialog.showOpenDialog({
				properties : ["multiSelections", "openDirectory"]
			}, (data) => {
				if(data) {
					for(let i = 0; i < data.length; i++) {
						this.scanFolder(data[i])
					}
				}
			})
		})
	}

	scanFolder(str) {

		fs.readdir(str, (err, dir) => {

	        for (let i = 0; i < dir.length; i++) {
	        	fs.lstat(str + path.sep + dir[i], (err, stats) => {

	        		if(err) {
	        			Flash.error(`Problem with scan folder ${dir[i]}`)
	        			return
	        		}

	        		if(stats.isDirectory()) {
	        			this.scanFolder(str+path.sep+dir[i])
	        		}

	        		if(stats.isFile()) {

	        			if(dir[i].substr(dir[i].length - 4) != '.mp3') {
	        				return 
	        			}

	        			this.findFile(str+path.sep+dir[i])
	        		}
	        	})
	        }
		});
	}

	findFile(file) {
		let a = this.db.each('SELECT count(id) as n FROM songs WHERE path_hash = ? ', Helper.crc32(file), (err, row) => {

			if(row.n == 0) {
				this.createAppend(file, this.html, this.db, this.playlist.id)
			} else {
				this.db.all('SELECT * FROM songs WHERE path_hash = ? ', Helper.crc32(file), (err, rows) => {

					let found = 0
					for(let k = 0; k < rows.length; k++) {
						if(rows[k].path == file) {
							this.append(file, rows[k].id, this.html, this.db, this.playlist.id)
							found = 1
							break
						}
					}
					if(!found) {
						this.createAppend(file, this.html)
					}

				})
			}
		})		
	}

	/**
	 *	Insert new entity "song" and new entity "playlist_item", then append to playlist
	 */
	createAppend(file, playlist, _db, playlistId) {

		var songId, 
			itemId

		_db.serialize(() => {

			_db.run(
				'INSERT INTO songs (title, path, path_hash, play_count, rate) VALUES(?, ?, ?, ?, ?)',
				Helper.parseFilename(file), file, Helper.crc32(file), 0, 0, function(err, rows) {	
					songId = this.lastID
					if(!songId) {
						Flash.error('Could not add this song to playlist.')
						return 
					}


					_db.run(
						'INSERT INTO playlist_items (playlist_id, song_id, position) VALUES(?, ?, ?)', 
						playlistId, songId, 99999, function(err, rows) {
							itemId = this.lastID

							if(!this.lastID) {
								Flash.error('Could not add this song to playlist')
								return
							}

							let item = new Song({
								id: this.lastID, 
								title: Helper.parseFilename(file),
								song_id : songId, 
								path : file, 
								rate : 0
							})

							playlist.find('.Playlist').append(item)

							let Editor = new PlaylistEditor()
							Editor.songsSortable(playlist)
						}
					)
				}				 
			)


		})

	} 

	append(file, song_id, playlist) {

		this.db.serialize(() => {
			this.db.run(
				' INSERT INTO playlist_items (playlist_id, song_id, position) VALUES(?, ?, ?) ', 
				this.playlist.id,
				song_id, 
				99999, 
				function() {
					if(this.lastID) {
						let item = new Song({
							id : this.lastID, 
							title : Helper.parseFilename(file), 
							song_id : song_id, 
							path: file, 
							rate : 0
						})

						playlist.find('.Playlist').append(item)	

						let Editor = new PlaylistEditor()
						Editor.songsSortable(playlist)

					} else {
						Flash.error('Could not add this song to playlist')
					}
				}
			)
		})
	}
}

class PlaylistEditor {

	constructor() {

		if(!PlaylistEditor.Instance) {
			PlaylistEditor.Instance = this
			this.init()
		}

		return PlaylistEditor.Instance

	}

	init() {
		this.db = new Db()
		this.eventHandler()
		this.songContextMenu()
	}

	eventHandler() {
		this.handleClickNew()
	}

	handleClickNew() {
		$(document).on('click' , '.Add' , () => {
			this.db.serialize(() => {
				this.db.run(' INSERT INTO playlists (title, position) VALUES(?, ?) ', 'Enter Title', 99999, function() {
					let playlist = new Playlist({
						id: this.lastID, 
						title: 'Enter Title'
					})

					let Editor = new PlaylistEditor()
					Editor.edit(playlist)
				})
			})			
		})
	}

	edit(instance) {
		let button = instance.button, 
			backup = instance.playlist.title

		button.attr('contenteditable' , true)
		this.selectAll(button)
		button.on('keydown' , (e) => {
			if(e.which == 13) {
				instance.playlist.title = button.text()
				this.save(instance)
				return false
			}
		})
		button.on('keyup' , (e) => {
			if(e.which == 27) {
				button.text(backup)
				button.attr('contenteditable' , false)
			}
		})
	}

	selectAll(el) {
	    var range = document.createRange();
	    range.selectNodeContents(el[0]);
	    var sel = window.getSelection();
	    sel.removeAllRanges();
	    sel.addRange(range);
	}	

	save(instance) {

		if(!instance.playlist.title.trim().length) {
			return Flash.error('Playlist name should have at least one letter')
		}

		this.db.serialize(() => {
			this.db.run(
				' UPDATE playlists SET title = ? WHERE id = ? ', 
				instance.playlist.title, instance.playlist.id, (err) => {
					if(err) {
						Flash.error('Cant save. Sorry')
					} else {
						Flash.success('Saved!')
						instance.button.attr('contenteditable', false)
						Tab.refresh()
					}
				}
			)
		})
	}

	songsSortable(playlist) {

		sortable(playlist.find('.Playlist'), 'destroy')

		sortable(playlist.find('.Playlist'), {
			placeholderClass : 'SongGhost Song'
		})

		if(playlist.find('.Playlist').data('listener') == 0) {

			sortable(playlist.find('.Playlist'), {
				placeholderClass : 'SongGhost Song'
			})[0].addEventListener('sortupdate' , () => {
				this.db.serialize(() => {
					playlist.find('.Song').each((k ,v) => {
						this.db.run(' UPDATE playlist_items SET position = ? WHERE id = ? ', k, $(v).data('id'))
					})
				})
			})		

			playlist.find('.Playlist').data('listener' , '1')

		} else {

			sortable(playlist.find('.Playlist'), {
				placeholderClass : 'SongGhost Song'
			})			
		}

	}

	static savePlaylistOrder() {

		let db = new Db()
		db.serialize(() => {
			$('.Nav .TabButton').each((k, v) => {
				db.run(' UPDATE playlists SET position = ? WHERE id = ? ', k, $(v).data('id'))
			})
		})
	}

	static playlistDelete(id) {

		let db = new Db(), 
			el = $('.TabButton[data-id="'+id+'"]')

		if(el.length) {		

			el.fadeOut(240, function() {
				$(this).remove()
			})

			$('.TabPane[data-id="'+id+'"]').fadeOut(240, function() {
				$(this).remove()
			})

			db.serialize(() => {
				db.run(' DELETE FROM playlist_items WHERE playlist_id = ? ', id)
				db.run(' DELETE FROM playlists WHERE id = ? ', id)
				Flash.success('Playlist was removed')
				Tab.refresh()
			})
		}
	}

	songContextMenu() {

		const menu = Object.create(contextMenu)
		const songMenu = menu.init('.Song' , [
			{
				type : 'header', 
				text : 'options'
			}, 
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-play"></i> Play',
				display : (e) => {

					let el = $(e.currentTarget)
					if(el.hasClass('active') === true) {
						return false
					}

					return true
				},
				attr : {
					class : 'songPlay', 
					'data-id' : (e) => {
						let el = $(e.currentTarget) 
						return el.data('id') 
					}
				}
			},
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-stop"></i> Stop',
				display : (e) => {

					let el = $(e.currentTarget)
					if(el.hasClass('active') === true) {
						return true
					}

					return false
				}, 
				attr : {
					class : 'songStop', 
					'data-id' : (e) => {

						let el = $(e.currentTarget)
						return el.data('id')
					}
				}
			},
			{
				type : 'divider'
			}, 
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-edit"></i> Rename',
				attr : {
					class : 'songRename', 
					'data-id' : (e) => {
						let el = $(e.currentTarget)
						return el.data('id')
					}
				}
			}, 			
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-long-arrow-up"></i> Move Up', 
				display : (e) => {

					let el = $(e.currentTarget) 
					if(el.prev('.Song').length) {
						return true 
					}

					return false
				}, 
				attr : {
					class : 'songMoveUp', 
					'data-id' : (e) => {
						let el = $(e.currentTarget)
						return el.data('id')
					}
				}
			}, 
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-long-arrow-down"></i> Move Down', 
				display : (e) => {

					let el = $(e.currentTarget)
					if(el.next('.Song').length) {
						return true
					}

					return false
				}, 
				attr : {
					class : 'songMoveDown', 
					'data-id' : (e) => {

						let el = $(e.currentTarget)
						return el.data('id')
					}
				}
			}, 
			{
				type : 'divider'
			}, 
			{
				type : 'item', 
				text : '<i class="zmdi zmdi-delete"></i> Delete', 
				attr : {
					class : 'songDelete', 
					'data-id' : (e) => {

						let el = $(e.currentTarget)
						return el.data('id')
					}
				}
			}
		])
	}

	editSong(id) {

		let db = new Db()

		let item = $('.Song[data-id="'+id+'"]'), 
			title = item.find('.SongTitle'), 
			backup = title.text(), 
			songId = item.data('song_id')

		title.attr('contenteditable' , true)
		this.selectAll(title)

		title.on('keydown' , function(e) {

			if(e.which == 13) {
				db.serialize(() => {
					db.run(' UPDATE songs SET title = ? WHERE id = ? ', $(this).text(), songId)
				})

				title.attr('contenteditable' , false)
				Flash.success('Song was renamed')
				$('.Song[data-song_id="'+songId+'"] .SongTitle').text($(this).text())

				return false
			}
		})

		title.on('keyup' , (e) => {

			if(e.which == 27) {
				title.attr('contenteditable' , false) 
				title.text(backup)
			}
		})
	}

	saveSongOrder(id, id2) {

		let items = []

		this.db.serialize(() => {
			this.db.all(' SELECT * FROM playlist_items WHERE id = ? OR id = ? ', id, id2, (err, rows) => {
				items = rows
				this.db.serialize(() => {
					this.db.run(' UPDATE playlist_items SET position = ? WHERE id = ? ', items[0].position, items[1].id)
					this.db.run(' UPDATE playlist_items SET position = ? WHERE id = ? ', items[1].position, items[0].id)
				})
			})
		})

	}

	deletePlaylistItem(id) {

		$('.Song[data-id="'+id+'"]').fadeOut(240 , function() {
			$(this).remove()
			Flash.success('Playlist item was removed')
		})

		this.db.serialize(() => {
			this.db.run(' DELETE FROM playlist_items WHERE id = ? ', id)
		})
	}
}

class Song {

	constructor(song) {

		this.html = Templates.get('Song' , {
			id : song.id, 
			title : song.title, 
			song_id : song.song_id, 
			path : song.path, 
			rate : song.rate
		})

		this.player = new Player()
		this.db = new Db()
		this.html = $(this.html)

		if(song.rate) {
			this
				.html
				.find('.SongStars i:lt('+song.rate+')')
				.addClass('zmdi-star rated')
				.removeClass('zmdi-star-outline')
		}

		this.eventHandler()
		return this.html
	}

	eventHandler() {
		this.handlePlay()
		this.handleRate()
	}

	handlePlay() {
		this.html.click(() => {
			this.player.play(this.html)
		})
	}

	handleRate() {
		this.rateMouseIn()
		this.rateMouseOut()
		this.rateClick()
	}

	rateMouseIn() {
		this.html.find('.SongStars i').on('mouseenter' , function() {

			$(this)
				.parent()
				.find('i')
				.removeClass('zmdi-star')
				.addClass('zmdi-star-outline')

			$(this)
				.addClass('zmdi-star')
				.prevAll()
				.removeClass('zmdi-star-outline')
				.addClass('zmdi-star')
		})
	}

	rateMouseOut() {
		this.html.find('.SongStars i').on('mouseleave' , function() {
			$(this)
				.removeClass('zmdi-star')
				.addClass('zmdi-star-outline')
				.prevAll()
				.removeClass('zmdi-star')
				.addClass('zmdi-star-outline')

			$(this)
				.parent()
				.find('.rated')
				.removeClass('zmdi-star-outline')
				.addClass('zmdi-star')
		})
	}

	rateClick() {

		this.html.find('.SongStars i').click((e) => {
			e.stopImmediatePropagation()
			this.saveRating(this.html.find('.SongStars i').index($(e.currentTarget)) +1)
		})
	}

	saveRating(rate) {

		let songs = $('.Song[data-song_id="'+this.html.data('song_id')+'"]') 

		songs
			.find('.SongStars i')
			.removeClass('rated zmdi-star')
			.addClass('zmdi-star-outline')

		songs
			.find('.SongStars i:lt('+rate+')')
			.removeClass('zmdi-star-outline')
			.addClass('zmdi-star rated')

		this.db.run(' UPDATE songs SET rate = ? WHERE id = ? ', rate, this.html.data('song_id'))

	}

}

class Flash {

	static error(msg, autofade = true) {
		let el = Flash.template('error')
		el.html(msg)
		$('body').append(el)

		if(!autofade) {
			return el
		}

		Flash.fade(el)
	}

	static success(msg, autofade = true) {
		let el = Flash.template('success')
		el.html(msg)
		$('body').append(el)

		if(!autofade) {
			return el
		}

		Flash.fade(el)
	}

	static fade(el) {

		setTimeout(() => {
			el.fadeOut(240, () => {
				el.remove()
			})
		}, 800)

	}

	static template(context = 'success') {
		if(context == 'success') {
			return $('<div class="Flash FlashSuccess"></div>')
		}
		if(context == 'error') {
			return $('<div class="Flash FlashError"></div>')
		}
	}

	static clear() {
		$('.Flash').remove()
	}

}

class Helper {

	static format(totalSeconds) {

		let h = Math.floor(totalSeconds / 3600);
		totalSeconds = parseInt(totalSeconds)
		totalSeconds %= 3600;
		let m = Math.floor(totalSeconds / 60);
		let s = totalSeconds % 60;

		if(h < 1) {
			return Helper.pad(m)+':'+Helper.pad(s) 
		}

		return h+':'+Helper.pad(m)+':'+Helper.pad(s)

	}

	static pad(n) {
		if(n < 10) {
			return '0'+n
		}

		return n
	}

	static crcTable() {
	    var c;
	    var crcTable = [];
	    for(var n =0; n < 256; n++){
	        c = n;
	        for(var k =0; k < 8; k++){
	            c = ((c&1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1));
	        }
	        crcTable[n] = c;
	    }
	    return crcTable;		
	}

	static crc32(str) {
	    var crcTable = window.crcTable || (window.crcTable = Helper.crcTable());
	    var crc = 0 ^ (-1);

	    for (var i = 0; i < str.length; i++ ) {
	        crc = (crc >>> 8) ^ crcTable[(crc ^ str.charCodeAt(i)) & 0xFF];
	    }

	    return (crc ^ (-1)) >>> 0;
	}

	static parseFilename(path) {
		return path.split('\\').pop().slice(0, -4)
	}	
}

class Templates {

	static init() {

		Templates.html = []

		$.get('elements/TabButton.html')
		.done((data) => {
			Templates.html['TabButton'] = data
		})

		$.get('elements/TabPane.html')
		.done((data) => {
			Templates.html['TabPane'] = data
		})

		$.get('elements/Song.html')
		.done((data) => {
			Templates.html['Song'] = data
		})

	}

	static get(template, vars) {
		let copy = Templates.html[template]
		return Templates.parse(copy, vars)

	}

	static parse(data, vars) {

		let parsed = data.match(/\{\{(.*?)\}\}/g)

		for(let k in parsed) {
			let str = parsed[k].replace('{{', '')
			str = str.replace('}}', '')
			data = data.replace(parsed[k], vars[str])
		}

		return data
	}

}


const app = new App()	

;~function() {

	$(document).on('click' , '.playlistRename' , function() {
		$('.TabButton[data-id="'+$(this).data('id')+'"]').trigger('dblclick')
	})

	$(document).on('click' , '.playlistMoveLeft' , function() {

		let el = $('.TabButton[data-id="'+$(this).data('id')+'"]')

		el.prev('.TabButton').before(el)
		PlaylistEditor.savePlaylistOrder()
	})

	$(document).on('click' , '.playlistMoveRight' , function() {

		let el = $('.TabButton[data-id="'+$(this).data('id')+'"]')

		el.next('.TabButton').after(el)
		PlaylistEditor.savePlaylistOrder()		
	})

	$(document).on('click' , '.playlistDelete' , function() {

		PlaylistEditor.playlistDelete($(this).data('id'))
	})

	$(document).on('click' , '.songPlay' , function() {

		$('.Song[data-id="'+$(this).data('id')+'"]').trigger('click')
	})

	$(document).on('click' , '.songStop' , () => {
		$('.Stop').trigger('click')
	})

	$(document).on('click' , '.songRename' , function() {

		let Editor = new PlaylistEditor()
		Editor.editSong($(this).data('id'))
	})

	$(document).on('click' , '.songMoveUp' , function() {

		let el = $('.Song[data-id="'+$(this).data('id')+'"]'), 
			up = el.prev('.Song')

		el.after(up)

		let Editor = new PlaylistEditor()
		Editor.saveSongOrder(el.data('id'), up.data('id'))
	})

	$(document).on('click' , '.songMoveDown' , function() {

		let el = $('.Song[data-id="'+$(this).data('id')+'"]'), 
			down = el.next('.Song')

		el.before(down)

		let Editor = new PlaylistEditor()
		Editor.saveSongOrder(el.data('id'), down.data('id'))
	})

	$(document).on('click' , '.songDelete' , function() {

		let Editor = new PlaylistEditor()
		Editor.deletePlaylistItem($(this).data('id'))
	})
}()