"use strict";

exports.__esModule = true;
exports.default = exports.empty = void 0;

var _URI = _interopRequireDefault(require("subfirelib/utils/URI"));

var _md = _interopRequireDefault(require("subfirelib/utils/md5"));

var _utils = require("subfirelib/utils/utils");

var _SubsonicCache = _interopRequireDefault(require("./SubsonicCache"));

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

/* temp */

/*eslint no-extend-native: "off" */
Array.prototype.unique = function (stripEmpty) {
  let a = [];

  for (let i = 0, l = this.length; i < l; i++) {
    if (stripEmpty && (this[i] === null || this[i] === undefined)) continue;

    if (a.indexOf(this[i]) === -1) {
      a.push(this[i]);
    }
  }

  return a;
};

const empty = v => {
  return v === undefined || v === null || v === '' || Array.isArray(v) && v.length === 0 || typeof v === 'object' && Object.entries(v).length === 0;
};

exports.empty = empty;
const CurrentPromises = {};
const Subsonic = {
  serverAPIVersion: '1.13.0',
  _u: '',
  _s: '',
  _p: '',
  _b: '0',
  _c: 'SubFireMobile',
  coverArtCache: {},

  _initFromExisting(s) {
    this.serverAPIVersion = s.serverAPIVersion;
    this._u = s._u;
    this._s = s._s;
    this._p = s._p;
    this._b = s._b;
    this._c = s._c;
    this.coverArtCache = s.coverArtCache;
  },

  _buildURI: function (method, params = {}) {
    const that = this;
    let uri = this._s + '/rest/' + method + '.view';
    uri = (0, _URI.default)(uri);
    uri.addQuery(params);
    uri.addQuery({
      v: that.serverAPIVersion,
      f: 'json',
      c: that._c,
      u: that._u
    });

    if (that._p && (0, _utils.versionCompare)(that.serverAPIVersion, '1.13.0') >= 0) {
      const s = Math.random().toString(36).replace(/[^a-z]+/g, '');
      const t = (0, _md.default)(that._p + s);
      uri.addQuery({
        s: s,
        t: t
      });
    } else {
      // currently unsupported
      that._encP = 'enc:' + (0, _utils.hexEncode)(that._p);
      uri.addQuery({
        p: that._encP
      });
    } // console.log(params);
    // uri = uri + $.param(params, traditional);


    return uri.toString();
  },
  // I hate that i'm having to add this last
  _execute: function (method, params = {}) {
    const that = this;
    return new Promise(function (rs, rj) {
      if (that.connected === false) {
        rj({
          'subsonic-response': {
            status: 'failed',
            error: {
              code: '40',
              message: 'INTERNAL Wrong username or password'
            }
          }
        });
      }

      const uri = that._buildURI(method, params); // console.log(uri);


      fetch(uri.toString()).then(function (res) {
        if (!res.ok) {
          rj(res);
          return;
        }

        return res.text();
      }).then(function (res) {
        // console.warn(res);
        res = JSON.parse(res);
        const reply = res['subsonic-response']; // ampache returns "ok" but with error...

        if (reply.status === 'failed' || reply.error) {
          rj(reply);
        } else {
          // console.warn('resolve', method, reply);
          rs(reply);
        }
      }).catch(function (err) {
        console.error(err);
        rj(err);
      });
    });
  },
  configure: function (server, username, password, bitrate, clientName, doReset = true) {
    this.serverAPIVersion = '1.13.0';
    this._u = username;
    this._s = server;
    this._b = bitrate || '0';
    this._p = password || ''; // fix this

    this._c = clientName || 'SubFireLibDemoIncomplete';
    if (doReset) _SubsonicCache.default.reset();
  },
  open: function (server, username, password, bitrate, clientName) {
    this.connected = null;
    this.coverArtCache = {}; // reset capabilities

    this.configure(server, username, password, bitrate, clientName);
    const that = this;
    return new Promise(function (rs, rj) {
      that.ping().then(function (res) {
        that.serverAPIVersion = res.version;
        that.connected = true;
        rs(res);
      }).catch(function (err) {
        console.warn('catch api check', err);

        if (err && err.error && err.error.code === 41) {
          that.serverAPIVersion = err.version; // console.warn('ping again', that._u, that._p);

          return that.ping();
        }

        that.connected = false;
        console.warn('reject');
        rj(err);
        return null;
      }).then(function (res2) {
        // console.warn('backup try', res2);
        if (res2) {
          that.connected = true;
          rs(res2);
        }
      }).catch(function (err) {
        // second ping failed, we really are invalid even on ampache.
        rj(err);
      });
    });
  },
  ping: function () {
    return this._execute('ping');
  },
  getNowPlaying: function () {
    return this._execute('getNowPlaying');
  },
  getPlaylists: function () {
    return this._execute('getPlaylists').then(res => {
      // cache for easy access but it needs to be refreshed regularly so cache isn't permanent
      _SubsonicCache.default.Playlists = res.playlists.playlist;
      return res.playlists.playlist;
    });
  },
  getPlaylist: function (id) {
    if (!id) return Promise.resolve(null);
    return this._execute('getPlaylist', {
      id: id
    }).then(res => res.playlist);
  },
  createPlaylist: function (name, id) {
    const that = this;
    const promiseKey = 'createPlaylist' + name;

    if (CurrentPromises[promiseKey]) {
      // console.log('returning promise', MusicPathsPromises[key]);
      return CurrentPromises[promiseKey];
    }

    const thisPromise = new Promise(function (rs, rj) {
      const params = {
        name: name
      };
      if (id) params.playlistId = id;
      return that._execute('createPlaylist', params).then(function (data) {
        // console.log('new playlist', data);
        if (data.playlist) {
          rs(data.playlist);
          return 'got one...must be 6.0b2+';
        } // 6.0b2: data will have the playlist so no need to do the rest of this, just rs()


        return that.getPlaylists();
      }).then(function (res = []) {
        // console.log(res);
        if (res === 'got one...must be 6.0b2+') return;
        const p = res,
              l = p.length;
        let m = id ? null : p[0];

        for (let i = 1; i < l; ++i) {
          if (id) {
            m = p[i].id === id ? p[i] : m;
          } else {
            if (p[i].name === name && p[i].id * 1 > m.id * 1 && p[i].owner === that._u) {
              m = p[i];
            }
          }
        }

        rs(m);
      }).catch(function (err) {
        rj(err);
      }).finally(() => {
        delete CurrentPromises[promiseKey];
      });
    });
    CurrentPromises[promiseKey] = thisPromise;
    return thisPromise;
  },
  updatePlaylist: function (id, comment, share, name, songsToAdd, indexesToRemove) {
    const params = {
      playlistId: id
    };
    if (comment) params.comment = comment;
    if (share === true || share === false) params.public = share;
    if (name) params.name = name;
    let playlistsParamSet = []; // first update - remove

    if (indexesToRemove) {
      playlistsParamSet[0] = Object.assign({}, params, {
        songIndexToRemove: indexesToRemove
      });
    }

    if (songsToAdd) {
      const reducer = (paramSet, song, idx, songs) => {
        const paramIdx = Math.floor(idx / 100);

        if (typeof paramSet[paramIdx] === 'undefined') {
          paramSet[paramIdx] = Object.assign({}, params, {
            songIdToAdd: []
          });
        }

        paramSet[paramIdx].songIdToAdd.push(song);
        return paramSet;
      };

      playlistsParamSet = songsToAdd.reduce(reducer, playlistsParamSet);
    } // if neither happened, then just update - used for comments, share, name


    if (playlistsParamSet.length === 0) {
      playlistsParamSet[0] = Object.assign({}, params);
    }

    const that = this;
    return new Promise((rs, rj) => {
      async function f() {
        let pl = {
          res: 'not ready'
        };

        for (let p of playlistsParamSet) {
          // console.log('p', p);
          pl = await that._execute('updatePlaylist', p);
        }

        pl = await that.getPlaylist(id);
        rs(pl);
      }

      f().catch(err => {
        rj(err);
      });
    });
  },
  deletePlaylist: function (id) {
    return this._execute('deletePlaylist', {
      id: id
    });
  },
  getMusicFolders: function () {
    if (!empty(_SubsonicCache.default.MusicFolders)) {
      return Promise.resolve(_SubsonicCache.default.MusicFolders);
    }

    return this._execute('getMusicFolders').then(res => {
      _SubsonicCache.default.MusicFolders = res.musicFolders.musicFolder;

      _SubsonicCache.default.MusicFolders.unshift({
        id: -1,
        name: 'All Music Folders'
      });

      return _SubsonicCache.default.MusicFolders;
    });
  },
  getMusicFolder: function (id) {
    return this.getMusicFolders().then(mf => {
      return mf.find(f => f.id + '' === id + '');
    });
  },
  getIndexes: function (id) {
    const key = (id || -1) + '';

    if (!empty(_SubsonicCache.default.MusicDirectoryIndexes[key])) {
      return Promise.resolve(_SubsonicCache.default.MusicDirectoryIndexes[key]);
    }

    const params = id === null || id === undefined || id === -1 ? {} : {
      musicFolderId: id
    };
    return this._execute('getIndexes', params).then(res => {
      if (!Array.isArray(res.indexes.index)) res.indexes.index = [res.indexes.index];
      _SubsonicCache.default.MusicDirectoryIndexes[key] = res.indexes.index;
      return res.indexes.index;
    });
  },
  getMusicDirectory: function (id) {
    const key = (id || -1) + '';
    const promiseKey = 'getMusicDirectory' + key;

    if (!empty(_SubsonicCache.default.MusicPaths[key])) {
      // console.log('returning cache', key);
      return Promise.resolve(_SubsonicCache.default.MusicPaths[key]);
    }

    if (CurrentPromises[promiseKey]) {
      // console.log('returning promise', MusicPathsPromises[key]);
      return CurrentPromises[promiseKey];
    } // console.log('first promise', key);


    const thisPromise = this._execute('getMusicDirectory', {
      id: id
    }).then(res => {
      _SubsonicCache.default.MusicPaths[key] = res.directory;
      delete CurrentPromises[promiseKey];
      return res.directory;
    });

    CurrentPromises[promiseKey] = thisPromise;
    return thisPromise;
  },
  getGenres: function () {
    if (!empty(_SubsonicCache.default.Genres)) {
      return Promise.resolve(_SubsonicCache.default.Genres);
    }

    return this._execute('getGenres').then(res => {
      _SubsonicCache.default.Genres = res.genres.genre;
      return res.genres.genre;
    });
  },
  getArtists: function (id) {
    const key = (id || -1) + '';

    if (!empty(_SubsonicCache.default.ArtistIndexes[key])) {
      return Promise.resolve(_SubsonicCache.default.ArtistIndexes[key]);
    }

    const params = id === null || id === undefined || id === -1 ? {} : {
      musicFolderId: id
    };
    return this._execute('getArtists', params).then(res => {
      if (!res.artists.index) return [];
      if (!Array.isArray(res.artists.index)) res.artists.index = [res.artists.index];
      _SubsonicCache.default.ArtistIndexes[key] = res.artists.index;

      if (key === '-1') {
        for (let idx of _SubsonicCache.default.ArtistIndexes[key]) {
          const artists = idx.artist;

          for (let a of artists) {
            _SubsonicCache.default.ArtistsById[a.id] = a;
            _SubsonicCache.default.ArtistsByName[a.name] = a;

            _SubsonicCache.default.ArtistNames.push(a.name);
          }
        }
      }

      return res.artists.index;
    });
  },
  getArtist: function (id) {
    const key = (id || -1) + '';
    const promiseKey = 'getArtist' + key;

    if (!empty(_SubsonicCache.default.ArtistIndexes[key]) && !empty(_SubsonicCache.default.ArtistIndexes[key].album)) {
      // console.log('return cache');
      return Promise.resolve(_SubsonicCache.default.ArtistIndexes[key]);
    } // existing promise


    if (CurrentPromises[promiseKey]) {
      // console.log('return existing');
      return CurrentPromises[promiseKey];
    } // console.log('return new');


    const p = this._execute('getArtist', {
      id: id
    }).then(res => {
      // console.log('updated', res);
      _SubsonicCache.default.ArtistIndexes[key] = res.artist;
      delete CurrentPromises[promiseKey];
      return res.artist;
    });

    CurrentPromises[promiseKey] = p;
    return p;
  },
  getAlbum: function (id) {
    const key = (id || -1) + '';
    const promiseKey = 'getAlbum' + key;

    if (!empty(_SubsonicCache.default.Albums[key]) && !empty(_SubsonicCache.default.Albums[key])) {
      // console.log('return cache');
      return Promise.resolve(_SubsonicCache.default.Albums[key]);
    } // existing promise


    if (CurrentPromises[promiseKey]) {
      // console.log('return existing');
      return CurrentPromises[promiseKey];
    } // console.log('return new');


    const p = this._execute('getAlbum', {
      id: id
    }).then(res => {
      // console.log('updated', res);
      _SubsonicCache.default.Albums[key] = res.album;
      delete CurrentPromises[promiseKey];
      return res.album;
    });

    CurrentPromises[promiseKey] = p;
    return p;
  },
  getTopSongs: function (name, count) {
    return this._execute('getTopSongs', {
      artist: name,
      count: count || 50
    });
  },
  getTopArtistSongs: function (id, count) {
    return this._execute('getTopSongs', {
      artist: _SubsonicCache.default.ArtistsById[id].name,
      count: count || 50
    });
  },
  getSong: function (id) {
    return this._execute('getSong', {
      id: id
    }).then(res => res.song);
  },
  // fixme accept music folder id
  getStarred: function (musicFolderId) {
    const params = musicFolderId && musicFolderId * 1 !== -1 ? {
      musicFolderId
    } : null;
    return this._execute('getStarred', params).then(res => res ? res.starred : []);
  },
  getStarred2: function (musicFolderId) {
    const params = musicFolderId && musicFolderId * 1 !== -1 ? {
      musicFolderId
    } : null;
    return this._execute('getStarred2', params).then(res => res ? res.starred2 : []);
  },
  // todo unified star list from both (see subfirepl)
  getVideos: function () {
    return this._execute('getVideos');
  },
  getAlbumList: function (type, params) {
    params = params || {};
    params.type = type;
    return this._execute('getAlbumList', params);
  },
  getAlbumList2: function (type, params) {
    params = params || {};
    params.type = type;
    return this._execute('getAlbumList2', params);
  },
  getRandomSongs: function (genre, fromYear, toYear, id, size) {
    const params = {};
    if (id) params.musicFolderId = id;
    if (genre) params.genre = genre;
    if (fromYear) params.fromYear = fromYear;
    if (toYear) params.toYear = toYear;
    params.size = size || 50;
    return this._execute('getRandomSongs', params);
  },
  getArtistInfo2: function (id, similarArtistCount) {
    return this.getArtistInfo(id, true, similarArtistCount);
  },
  getArtistInfo: function (id, useID3, similarArtistCount = 20) {
    const key = 'getArtistInfo' + (id || -1) + '';

    if (!empty(_SubsonicCache.default.ArtistInfoById[key])) {
      console.log('return cache');
      return Promise.resolve(_SubsonicCache.default.ArtistInfoById[key]);
    } // existing promise


    if (CurrentPromises[key]) {
      console.log('return existing');
      return CurrentPromises[key];
    }

    const method = useID3 ? 'getArtistInfo2' : 'getArtistInfo';
    const params = {
      id: id,
      count: similarArtistCount
    };

    const p = this._execute(method, params).then(res => {
      var _ai$biography;

      const ai = res.artistInfo || res.artistInfo2;
      const linkIndex = (ai == null ? void 0 : (_ai$biography = ai.biography) == null ? void 0 : _ai$biography.indexOf('<a')) || -1;

      if (linkIndex >= -1) {
        ai.link = ai.biography.substring(linkIndex);
        ai.biography = ai.biography.substring(0, linkIndex);
      }

      _SubsonicCache.default.ArtistInfoById[key] = ai;
      delete CurrentPromises[key];
      console.log('return fetch');
      return ai;
    });

    CurrentPromises[key] = p;
    return p;
  },
  getAlbumInfo2: function (id) {
    return this.getAlbumInfo(id, true);
  },
  getAlbumInfo: function (id, useID3) {
    const key = 'getAlbumInfo' + (id || -1) + '';

    if (!empty(_SubsonicCache.default.AlbumInfoById[key])) {
      // console.log('return cache');
      return Promise.resolve(_SubsonicCache.default.AlbumInfoById[key]);
    } // existing promise


    if (CurrentPromises[key]) {
      // console.log('return existing');
      return CurrentPromises[key];
    }

    const method = useID3 ? 'getAlbumInfo2' : 'getAlbumInfo';
    const params = {
      id: id
    };

    const p = this._execute(method, params).then(res => {
      var _ai$notes;

      const ai = res.albumInfo || res.albumInfo2;
      const linkIndex = (ai == null ? void 0 : (_ai$notes = ai.notes) == null ? void 0 : _ai$notes.indexOf('<a')) || -1;

      if (linkIndex >= -1) {
        ai.link = ai.notes.substring(linkIndex);
        ai.notes = ai.notes.substring(0, linkIndex);
      }

      _SubsonicCache.default.AlbumInfoById = ai;
      delete CurrentPromises[key];
      return ai;
    });

    CurrentPromises[key] = p;
    return p;
  },
  getSimilarSongs2: function (id, count) {
    return this.getSimilarSongs(id, true, count);
  },
  getSimilarSongs: function (id, useID3, count) {
    const method = useID3 ? 'getSimilarSongs2' : 'getSimilarSongs';
    const params = {
      id: id,
      count: count
    };
    return this._execute(method, params);
  },
  search: function (search, useID3, params) {
    const method = useID3 ? 'search3' : 'search2';
    params.query = search;
    return this._execute(method, params);
  },
  savePlayQueue: function (ids, current, currentMediaTime) {
    const method = 'savePlayQueue';
    const params = {
      id: ids,
      current: current,
      position: currentMediaTime || 0
    };
    return this._execute(method, params, true);
  },
  getPlayQueue: function () {
    return this._execute('getPlayQueue');
  },
  getChatMessages: function (since) {
    return this._execute('getChatMessages', since ? {
      since: since
    } : null);
  },
  addChatMessage: function (message) {
    return this._execute('addChatMessage', {
      message: message
    });
  },
  createBookmark: function (id, position, comment) {
    const params = {
      id: id,
      position: position
    };
    if (comment) params.comment = comment;
    return this._execute('createBookmark', params);
  },
  deleteBookmark: function (id) {
    const params = {
      id: id
    };
    return this._execute('deleteBookmark', params);
  },
  getBookmarks: function () {
    return this._execute('getBookmarks', null);
  },
  getInternetRadioStations: function () {
    return this._execute('getInternetRadioStations', null);
  },
  createInternetRadioStation: function (name, streamUrl, homepageUrl) {
    return this._execute('createInternetRadioStation', {
      name: name,
      streamUrl: streamUrl,
      homepageUrl: homepageUrl
    });
  },
  updateInternetRadioStation: function (id, name, streamUrl, homepageUrl) {
    return this._execute('updateInternetRadioStation', {
      id: id,
      name: name,
      streamUrl: streamUrl,
      homepageUrl: homepageUrl
    });
  },
  deleteInternetRadioStation: function (id) {
    return this._execute('deleteInternetRadioStation', {
      id: id
    });
  },
  getPodcasts: function (includeEpisodes, id) {
    const params = {};
    if (includeEpisodes) params.includeEpisodes = true;
    if (id) params.id = id;
    return this._execute('getPodcasts', params);
  },
  getNewestPodcasts: function (count) {
    const params = {};
    if (count) params.count = count;
    return this._execute('getNewestPodcasts', params);
  },
  getCompleteAlbum: function (id) {
    const that = this;
    return new Promise(function (rs, rj) {
      that._execute('getAlbum', {
        id: id
      }).then(function (data) {
        data.album.song = [data.album.song];
        rs(data);
      }).catch(function (error) {
        rj(error);
      });
    });
  },
  getSimilarArtists: function (id, count, depth) {
    const that = this;
    let c = empty(count) ? '10' : count,
        d = empty(depth) ? '2' : depth;
    c = c * 1;
    d = d * 1;
    let similarArtists = ['' + id];
    return new Promise(function (rs, rj) {
      const key = 'getArtistInfo' + (id || -1) + '';
      console.log('cached', _SubsonicCache.default.ArtistInfoById[key]);
      const pinit = _SubsonicCache.default.ArtistInfoById[id] ? Promise.resolve({
        artistInfo2: _SubsonicCache.default.ArtistInfoById[key]
      }) : that.getArtistInfo2(id, count);
      pinit.then(function (data) {
        const nextloaders = [];
        const info = _SubsonicCache.default.ArtistInfoById[id] = data;

        if (info.similarArtist) {
          info.similarArtist.forEach(function (a) {
            // cache artist name, because it may not be cached in station mode
            if (!_SubsonicCache.default.ArtistsById[a.id]) _SubsonicCache.default.ArtistsById[a.id] = a;
            if (d) nextloaders.push(that.getSimilarArtists(a.id, c, d - 1));
            similarArtists.push(a.id);
          });
        } // console.log('similar to', id, similarArtists, nextloaders.length);


        if (!nextloaders.length) {
          rs((0, _utils.arrayUnique)(similarArtists));
          return;
        }

        Promise.all(nextloaders).then(function (res) {
          res.forEach(function (sa) {
            // console.log('sa', sa);
            sa.forEach(function (s) {
              // console.log(similarArtists, s);
              similarArtists = similarArtists.concat(s);
            });
          }); // console.log('similar to', id, similarArtists);

          rs((0, _utils.arrayUnique)(similarArtists));
        }).catch(function (err2) {
          console.error(err2);
          rj(err2);
        });
      }).catch(function (err) {
        rj(err);
      });
    });
  },
  getTopSimilarSongs: function (id, count, depth) {
    const that = this;
    count = empty(count) ? '10' : count;
    depth = empty(depth) ? '2' : depth;
    return new Promise(function (rs, rj) {
      that.getSimilarArtists(id, count, depth).then(function (data) {
        console.log('sa', data);
        const nextloaders = [];
        data.forEach(function (i) {
          const a = _SubsonicCache.default.ArtistsById[i] ? _SubsonicCache.default.ArtistsById[i].name : null;
          if (a) nextloaders.push(that.getTopSongs(a));
        });
        return Promise.all(nextloaders);
      }).then(function (data) {
        console.warn('all', data);
        data = data.map(function (d) {
          return d.topSongs.song;
        }).flat().unique(true);
        console.log('rs', data);
        rs(data);
      }).catch(function (err) {
        console.log('rj', err);
        rj(err);
      });
    });
  },
  // TODO: rewrite these as async/await in order to be able to run them synchronously and reduce
  // risk of resource expiration
  // the following are needed only for the radio-generator. they recursively (bredth first search) try to find all songs under a particular artist/index/Folder
  getMusicDirectorySongs: function (id) {
    // console.log(id, 'fetching...');
    const that = this;
    const songs = [];
    return new Promise(function (rs, rj) {
      that.getMusicDirectory(id).then(function (data) {
        // console.log(id, 'returned', data);
        const children = data.child || [];
        const dirloaders = [];

        for (let i = 0; i < children.length; ++i) {
          if (children[i].isDir) {
            dirloaders.push(that.getMusicDirectorySongs(children[i].id));
          } else {
            // console.log('adding song', children[i]);
            songs.push(children[i]);
          }
        }

        if (dirloaders.length) {
          // console.log(id, 'more', dirloaders.length);
          Promise.all(dirloaders).then(function (res) {
            res.forEach(function (md) {
              md.musicDirectory.song.forEach(function (s) {
                songs.push(s);
              });
            });
            rs({
              musicDirectory: {
                song: songs
              }
            });
          }).catch(function (err2) {
            console.error(err2);
            rj(err2);
          });
        } else {
          // console.log(id, 'done', children.length, songs.length);
          rs({
            musicDirectory: {
              song: songs
            }
          });
        }
      }).catch(function (err) {
        console.error(err);
        rj(err);
      });
    });
  },
  getMusicDirectoryAlbums: function (id) {
    //console.log(id, "fetching...");
    const that = this;
    return new Promise(function (rs, rj) {
      that.getMusicDirectory(id).then(function (data) {
        const albums = []; //console.log(id, "returned", data);

        const children = data.child || [];
        const dirloaders = [];
        const songs = [];

        for (let i = 0; i < children.length; ++i) {
          if (children[i].isDir) {
            //console.log("fetching folder", children[i]);
            dirloaders.push(that.getMusicDirectoryAlbums(children[i].id));
          } else {
            //console.log("adding song", id, children[i]);
            songs.push(children[i]);
          }
        }

        if (songs.length) albums.push(songs);

        if (dirloaders.length) {
          //console.log(id, "more", dirloaders.length);
          Promise.all(dirloaders).then(function (res) {
            res.forEach(function (md) {
              //console.log(md);
              md.musicDirectoryAlbums.album.forEach(function (s) {
                //console.log('appending album length', s.length, s);
                albums.push(s);
              });
            });
            rs({
              musicDirectoryAlbums: {
                album: albums
              }
            });
          }).catch(function (err2) {
            console.error(err2);
            rj(err2);
          });
        } else {
          //console.log(id, "done", albums.length);
          rs({
            musicDirectoryAlbums: {
              album: albums
            }
          });
        }
      }).catch(function (err) {
        console.error(err);
        rj(err);
      });
    });
  },
  getArtistSongs: function (id) {
    const that = this;
    return new Promise(function (rs, rj) {
      that.getArtist(id).then(function (data) {
        // console.log(data);
        const album = data && data.album ? data.album : [];
        const albumloaders = album.map(function (a) {
          return that.getAlbum(a.id);
        }); // console.log(albumloaders);

        Promise.all(albumloaders).then(function (albums) {
          const rv = {
            albumCollection: {
              song: []
            }
          };
          albums.forEach(function (al) {
            // console.log(al);
            al.song.forEach(function (s) {
              rv.albumCollection.song.push(s);
            });
          }); // // console.log('rv', rv);

          rs(rv);
        }).catch(function (err) {
          rj(err);
        });
      }).catch(function (err) {
        console.error(err);
        rj(err);
      });
    });
  },
  getArtistAlbums: function (id) {
    const that = this;
    return new Promise(function (rs, rj) {
      that.getArtist(id).then(function (data) {
        // console.log(data);
        const albumloaders = data.album.map(function (a) {
          return that.getAlbum(a.id);
        }); // console.log(albumloaders);

        Promise.all(albumloaders).then(function (albums) {
          const rv = {
            albumCollection: {
              albums: []
            }
          };
          albums.forEach(function (al) {
            const alSongs = [];
            al.song.forEach(function (s) {
              alSongs.push(s);
            });
            rv.albumCollection.albums.push(alSongs);
          }); // console.log('rv', rv);

          rs(rv);
        }).catch(function (err) {
          rj(err);
        });
      }).catch(function (err) {
        console.error(err);
        rj(err);
      });
    });
  },

  getPlaylistAsAlbumList(id, id3) {
    const that = this;
    return new Promise(function (rs, rj) {
      that.getPlaylist(id).then(function (res) {
        rs(that.convertPlaylistToAlbumList(res.playlist, id3));
      });
    });
  },

  convertPlaylistToAlbumList(pl, id3) {
    const init = {
      done: {},
      result: []
    };
    pl = pl || {
      entry: []
    };
    pl.entry = pl.entry || [];
    const res = pl.entry.reduce(function (v, p, idx, a) {
      let r;

      if (id3) {
        // <album id="1768" name="Duets" coverArt="al-1768" songCount="2" created="2002-11-09T15:44:40"
        //  duration="514" artist="Nik Kershaw" artistId="829"/>
        r = {
          coverArt: p.coverArt,
          name: p.album,
          artist: p.artist,
          id: p.albumId,
          artistId: p.artistId,
          created: p.created
        };
      } else {
        // <album id="11" parent="1" title="Arrival" artist="ABBA" isDir="true" coverArt="22"
        //  userRating="4" averageRating="4.5"/>
        r = {
          coverArt: p.coverArt,
          name: p.album,
          artist: p.artist,
          id: p.parent
        };
      }

      if (v.done[r.id]) return v;
      v.done[r.id] = r;
      v.result.push(r);
      return v;
    }, init); // console.log(res);

    const rv = {};
    rv[id3 ? 'albumList2' : 'albumList'] = {
      album: res.result
    };
    return rv;
  },

  categorizePlaylists: function (p) {
    const playlists = p.filter(function (pl) {
      pl.comment = pl.comment || '';
      return pl.comment.indexOf('{"radiostation"') !== 0 && !pl.name.startsWith('zz-sbr');
    });
    const receivers = p.filter(pl => pl.comment === 'pending' && pl.name.startsWith('zz-sbr-') && pl.owner === Subsonic._u);
    const stationPlaylists = p.filter(function (pl) {
      pl.comment = pl.comment || '';
      return pl.comment.indexOf('{"radiostation"') === 0;
    });
    const myPlaylists = playlists.filter(pl => pl.owner === Subsonic._u);
    return {
      allPlaylists: p,
      playlists,
      stationPlaylists,
      receivers,
      myPlaylists
    };
  },
  getAvatarURL: function (username) {
    username = username || this._u;
    const key = 'av-' + username;
    let url;
    const firstUrl = this.coverArtCache[key];

    if (!firstUrl) {
      url = this._buildURI('getAvatar', {
        username: username
      });
      this.coverArtCache[key] = url;
    } else {
      // console.log("match", "using", firstUrl);
      url = firstUrl;
    }

    return url;
  },
  getCoverArtURL: function (id, size) {
    const params = {
      id: id || -1
    };
    let url;
    if (size) params.size = size;
    const key = id + '--' + size; // trimImageUrlEssentials(url);

    const firstUrl = this.coverArtCache[key];

    if (!firstUrl) {
      url = this._buildURI('getCoverArt', params);
      this.coverArtCache[key] = url;
    } else {
      // console.log("match", "using", firstUrl);
      url = firstUrl;
    }

    return url;
  },
  getHLSURL: function (id, params) {
    params = Object.assign({
      id: id,
      maxBitRate: this._b
    }, params || {});
    return this._buildURI('hls', params);
  },
  getStreamingURL: function (id, params) {
    params = Object.assign({
      id: id,
      maxBitRate: this._b
    }, params || {});
    return this._buildURI('stream', params);
  },
  getDownloadURL: function (id) {
    return this._buildURI('download', {
      id: id
    });
  }
}; // bind everybody so promises can be chained

const getMethods = obj => Object.getOwnPropertyNames(obj).filter(item => typeof obj[item] === 'function');

const methods = getMethods(Subsonic);

for (let m of methods) {
  Subsonic[m] = Subsonic[m].bind(Subsonic);
} // temp hack for those still not quite getting it.


window.fetcher = Subsonic;
var _default = Subsonic;
exports.default = _default;