text: (function (root, factory) {
      if (typeof define === 'function' && define.amd) {
          define([], factory)
      } else if (typeof module === 'object' && module.exports) {
          module.exports = factory()
      } else {
          root.library = factory()
      }
  }(this, function () {
      'use strict'
  
      var library = {
  
      /** *******************************
       * Search
       *********************************/
          searchPressed: function (key) {
              var value = $('#searchinput').val()
              switchContent('search')
  
              if (key === 13) {
                  library.initSearch()
                  return false
              }
              return true
          },
  
          // init search
          initSearch: function () {
              var value = $('#searchinput').val()
              var searchService = $('#selectSearchService').val()
              $.cookie('searchScheme', searchService, { expires: 365 })
  
              if ((value.length < 100) && (value.length > 0)) {
                  showLoading(true)
                  // hide ios/android keyboard
                  document.activeElement.blur()
                  $('input').blur()
  
                  delete customTracklists[URI_SCHEME + ':trackresultscache']
                  $('#searchartists').hide()
                  $('#searchalbums').hide()
                  $('#searchtracks').hide()
  
                  if (searchService !== 'all') {
                      mopidy.library.search({'query': {any: [value]}, 'uris': [searchService + ':']}).then(library.processSearchResults, console.error)
                  } else {
                      mopidy.getUriSchemes().then(function (schemes) {
                          var query = {}
                          var uris = []
  
                          var regexp = $.map(schemes, function (scheme) {
                              return '^' + scheme + ':'
                          }).join('|')
  
                          var match = value.match(regexp)
                          if (match) {
                              var scheme = match[0]
                              query = {uri: [value]}
                              uris = [scheme]
                          } else {
                              query = {any: [value]}
                          }
                          mopidy.library.search({'query': query, 'uris': uris}).then(library.processSearchResults, console.error)
                      })
                  }
              }
          },
  
      /** ******************************************************
       * process results of a search
       *********************************************************/
          processSearchResults: function (resultArr) {
              $(SEARCH_TRACK_TABLE).empty()
              $(SEARCH_ARTIST_TABLE).empty()
              $(SEARCH_ALBUM_TABLE).empty()
  
              // Merge results from different backends.
              // TODO  should of coures have multiple tables
              var results = {'tracks': [], 'artists': [], 'albums': []}
              var i, j
              var emptyResult = true
  
              for (i = 0; i < resultArr.length; i++) {
                  if (resultArr[i].tracks) {
                      for (j = 0; j < resultArr[i].tracks.length; j++) {
                          results.tracks.push(resultArr[i].tracks[j])
                          emptyResult = false
                      }
                  }
                  if (resultArr[i].artists) {
                      for (j = 0; j < resultArr[i].artists.length; j++) {
                          results.artists.push(resultArr[i].artists[j])
                          emptyResult = false
                      }
                  }
                  if (resultArr[i].albums) {
                      for (j = 0; j < resultArr[i].albums.length; j++) {
                          results.albums.push(resultArr[i].albums[j])
                          emptyResult = false
                      }
                  }
              }
  
              customTracklists[URI_SCHEME + ':trackresultscache'] = results.tracks
  
              if (emptyResult) {
                  $('#searchtracks').show()
                  $(SEARCH_TRACK_TABLE).append(
                      '<li class="song albumli"><a href="#"><h1><i></i>No tracks found...</h1></a></li>'
                  )
                  toast('No results')
                  showLoading(false)
                  return false
              }
  
              if (results.artists.length > 0) {
                  $('#searchartists').show()
              }
  
              if (results.albums.length > 0) {
                  $('#searchalbums').show()
              }
  
              if (results.tracks.length > 0) {
                  $('#searchtracks').show()
              }
  
              // 'Show more' template
              var showMoreTemplate = '<li onclick="$(this).hide().siblings().show(); return false;"><a>Show {count} more</a></li>'
  
              // Artist results
              var child = ''
              var template = '<li><a href="#" onclick="return library.showArtist(this.id, mopidy)" id={id}><i class="{class}"></i> <strong>{name}</strong></a></li>'
              var tokens
  
              for (i = 0; i < results.artists.length; i++) {
                  tokens = {
                      'id': results.artists[i].uri,
                      'name': results.artists[i].name,
                      'class': getMediaClass(results.artists[i])
                  }
  
                  // Add 'Show all' item after a certain number of hits.
                  if (i === 4 && results.artists.length > 5) {
                      child += stringFromTemplate(showMoreTemplate, {'count': results.artists.length - i})
                      template = template.replace('<li>', '<li class="overflow">')
                  }
  
                  child += stringFromTemplate(template, tokens)
              }
  
              // Inject list items, refresh listview and hide superfluous items.
              $(SEARCH_ARTIST_TABLE).html(child).listview('refresh').find('.overflow').hide()
  
              // Album results
              child = ''
              template = '<li><a href="#" onclick="return library.showAlbum(this.id, mopidy)" id="{albumId}">'
              template += '<h5 data-role="heading"><i class="{class}"></i> {albumName}</h5>'
              template += '<p data-role="desc">{artistName}</p>'
              template += '</a></li>'
  
              for (i = 0; i < results.albums.length; i++) {
                  tokens = {
                      'albumId': results.albums[i].uri,
                      'albumName': results.albums[i].name,
                      'artistName': '',
                      'albumYear': results.albums[i].date,
                      'class': getMediaClass(results.albums[i])
                  }
                  if (results.albums[i].artists) {
                      for (j = 0; j < results.albums[i].artists.length; j++) {
                          if (results.albums[i].artists[j].name) {
                              tokens.artistName += results.albums[i].artists[j].name + ' '
                          }
                      }
                  }
                  if (tokens.albumYear) {
                      tokens.artistName += '(' + tokens.albumYear + ')'
                  }
                  // Add 'Show all' item after a certain number of hits.
                  if (i === 4 && results.albums.length > 5) {
                      child += stringFromTemplate(showMoreTemplate, {'count': results.albums.length - i})
                      template = template.replace('<li>', '<li class="overflow">')
                  }
  
                  child += stringFromTemplate(template, tokens)
              }
              // Inject list items, refresh listview and hide superfluous items.
              $(SEARCH_ALBUM_TABLE).html(child).listview('refresh').find('.overflow').hide()
  
              // Track results
              resultsToTables(results.tracks, SEARCH_TRACK_TABLE, URI_SCHEME + ':trackresultscache')
  
              showLoading(false)
          },
  
      /** *******************************
       * Playlists & Browse
       *********************************/
          getPlaylists: function () {
              //  get playlists without tracks
              mopidy.playlists.asList().then(processGetPlaylists, console.error)
          },
  
          getBrowseDir: function (rootdir) {
              //  get directory to browse
              showLoading(true)
              if (!rootdir) {
                  browseStack.pop()
                  if (browseStack.length > 0) {
                      rootdir = browseStack[browseStack.length - 1].uri  // Navigated one level up
                  } else {
                      rootdir = null  // Navigated to top of library
                  }
              } else if (browseStack.length === 0 || rootdir !== browseStack[browseStack.length - 1].uri) {
                  browseStack.push({'uri': rootdir, 'scrollPos': 0})  // Navigated one level down
              }
              mopidy.library.browse({'uri': rootdir}).then(function (resultArr) {
                  processBrowseDir(resultArr)
                  if (rootdir === null) {
                      $('.refreshLibraryBtnDiv').hide()  // Mopidy does not support refreshing list of backends.
                  } else {
                      $('.refreshLibraryBtnDiv').show()
                      $('#refreshLibraryBtn').data('url', rootdir)
                      $('#refreshLibraryBtn').off('click')
                      $('#refreshLibraryBtn').one('click', controls.refreshLibrary)
                  }
              }, console.error)
          },
  
          getCurrentPlaylist: function () {
              mopidy.tracklist.getTlTracks().then(processCurrentPlaylist, console.error)
          },
  
      /** ******************************************************
       * Show tracks of playlist
       ********************************************************/
          togglePlaylists: function () {
              if ($(window).width() <= 960) {
                  $('#playlisttracksdiv').toggle();
                  // Hide other div
                  ($('#playlisttracksdiv').is(':visible')) ? $('#playlistslistdiv').hide() : $('#playlistslistdiv').show()
              } else {
                  $('#playlisttracksdiv').show()
                  $('#playlistslistdiv').show()
              }
              return true
          },
  
      /** **********
       * Lookups
       ************/
          showTracklist: function (uri) {
              showLoading(true)
              $(PLAYLIST_TABLE).empty()
              library.togglePlaylists()
              var tracks = getPlaylistTracks(uri).then(function (tracks) {
                  resultsToTables(tracks, PLAYLIST_TABLE, uri, 'return library.togglePlaylists();', true)
                  showLoading(false)
              })
              updatePlayIcons(uri, '', controls.getIconForAction())
              $('#playlistslist li a').each(function () {
                  $(this).removeClass('playlistactive')
                  if (this.id === uri) {
                      $(this).addClass('playlistactive')
                  }
              })
              return false
          },
  
          showArtist: function (nwuri, mopidy) {
              $('#popupQueue').popup('close')
              $('#popupTracks').popup('close')
              $('#controlsmodal').popup('close')
              $(ARTIST_TABLE).empty()
  
          // TODO cache
              $('#h_artistname').html('')
              showLoading(true)
              mopidy.library.lookup({'uris': [nwuri]}).then(function (resultDict) {
                  var resultArr = resultDict[nwuri]
                  resultArr.uri = nwuri
                  processArtistResults(resultArr)
              }, console.error)
              switchContent('artists', nwuri)
              scrollToTop()
              return false
          },
  
          showAlbum: function (uri, mopidy) {
              $('#popupQueue').popup('close')
              $('#popupTracks').popup('close')
              $('#controlsmodal').popup('close')
              $(ALBUM_TABLE).empty()
              // fill from cache
              var pl = getTracksFromUri(uri, true)
              if (pl.length > 0) {
                  albumTracksToTable(pl, ALBUM_TABLE, uri)
                  var albumname = getAlbum(pl)
                  var artistname = getArtist(pl)
                  $('#h_albumname').html(albumname)
                  $('#h_albumartist').html(artistname)
                  $('#coverpopupalbumname').html(albumname)
                  $('#coverpopupartist').html(artistname)
                  showLoading(false)
                  mopidy.library.lookup({'uris': [uri]}).then(function (resultDict) {
                      var resultArr = resultDict[uri]
                      resultArr.uri = uri
                      processAlbumResults(resultArr)
                  }, console.error)
              } else {
                  showLoading(true)
                  $('#h_albumname').html('')
                  $('#h_albumartist').html('')
                  mopidy.library.lookup({'uris': [uri]}).then(function (resultDict) {
                      var resultArr = resultDict[uri]
                      resultArr.uri = uri
                      processAlbumResults(resultArr)
                  }, console.error)
              }
              // show page
              switchContent('albums', uri)
              scrollToTop()
              return false
          },
  
          getSearchSchemes: function (searchBlacklist, mopidy) {
              var backendName
              var searchScheme = $.cookie('searchScheme')
              if (searchScheme) {
                  searchScheme = searchScheme.replace(/"/g, '')
              } else {
                  searchScheme = 'all'
              }
              $('#selectSearchService').empty()
              $('#selectSearchService').append(new Option('All services', 'all'))
              mopidy.getUriSchemes().then(function (schemesArray) {
                  schemesArray = schemesArray.filter(function (el) {
                      return searchBlacklist.indexOf(el) < 0
                  })
                  for (var i = 0; i < schemesArray.length; i++) {
                      backendName = getMediaHuman(schemesArray[i])
                      if (!backendName) {
                          // No mapping defined, revert to just showing the scheme with first letter capitalized.
                          backendName = schemesArray[i].charAt(0).toUpperCase() + schemesArray[i].slice(1)
                      }
                      $('#selectSearchService').append(new Option(backendName, schemesArray[i]))
                  }
                  $('#selectSearchService').val(searchScheme)
                  $('#selectSearchService').selectmenu('refresh', true)
              }, console.error)
          }
      }
      return library
  }))
  