"use strict";

require("core-js/modules/es.promise");

require("core-js/modules/web.dom-collections.iterator");

Object.defineProperty(exports, "__esModule", {
  value: true
});
exports.default = exports.RadioMaker = exports.StationVM = exports.StationDataPrototype = exports.GeneratorTypeMap = void 0;

var _Subsonic = _interopRequireDefault(require("./Subsonic"));

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

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

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

const GeneratorTypeMap = {
  rs: {
    title: 'Random Songs',
    op: 'getRandomSongs',
    // these MUST match the order of the function call in the Subsonic
    paramTitles: ['Genre', 'From Year', 'To Year', 'Music Folder'],
    paramKeys: ['g', 'fy', 'ty', 'id'],
    // shouldn't need to be called
    fetchMethod: 'getMusicFolders',
    render: function (g) {
      let rv = 'Random Songs';
      if (g.p.g) rv += ' with ' + this.paramTitles[0] + ' ' + g.p.g;
      if (g.p.fy) rv += ' ' + this.paramTitles[1] + ' ' + g.p.fy;
      if (g.p.ty) rv += ' ' + this.paramTitles[2] + ' ' + g.p.ty;
      if (g.p.id) rv += ' in ' + this.paramTitles[3] + ' ' + (_SubsonicCache.default.MusicFolders[g.p.id] ? _SubsonicCache.default.MusicFolders[g.p.id].name : g.p.id);
      return rv;
    }
  },
  pl: {
    title: 'Playlist',
    op: 'getPlaylist',
    paramTitles: ['Playlist'],
    paramKeys: ['id'],
    render: function (g) {
      return 'Songs from {0} {1}'.f(this.paramTitles[0], _SubsonicCache.default.Playlists[g.p.id] ? _SubsonicCache.default.Playlists[g.p.id].name : g.p.id);
    }
  },
  al: {
    title: 'Album',
    op: 'getAlbum',
    paramTitles: ['Album'],
    paramKeys: ['id'],
    fetchMethod: 'getAlbum',
    render: function (g) {
      if (!_SubsonicCache.default.Albums[g.p.id]) {
        _Subsonic.default[this.fetchMethod](g.p.id).then(function (data) {
          _SubsonicCache.default.Albums[g.p.id] = data.album || {
            id: g.p.id,
            name: '...missing album...'
          };
        }).catch(function (err) {
          console.error(err);
          _SubsonicCache.default.Albums[g.p.id] = {
            id: g.p.id,
            name: '...missing album...'
          };
        });
      }

      return 'Songs from {0} {1}'.f(this.paramTitles[0], _SubsonicCache.default.Albums[g.p.id] ? _SubsonicCache.default.Albums[g.p.id].title || _SubsonicCache.default.Albums[g.p.id].name : g.p.id);
    }
  },
  alc: {
    title: 'Complete Album',
    op: 'getCompleteAlbum',
    paramTitles: ['Album'],
    paramKeys: ['id'],
    fetchMethod: 'getAlbum',
    render: function (g) {
      if (!_SubsonicCache.default.Albums[g.p.id]) {
        _Subsonic.default[this.fetchMethod](g.p.id).then(function (data) {
          _SubsonicCache.default.Albums[g.p.id] = data.album || {
            id: g.p.id,
            name: '...missing album...'
          };
        }).catch(function (err) {
          console.error(err);
          _SubsonicCache.default.Albums[g.p.id] = {
            id: g.p.id,
            name: '...missing album...'
          };
        });
      }

      return 'Songs from {0} {1}'.f(this.paramTitles[0], _SubsonicCache.default.Albums[g.p.id] ? _SubsonicCache.default.Albums[g.p.id].title || _SubsonicCache.default.Albums[g.p.id].name : g.p.id);
    }
  },
  ss: {
    title: 'Similar Songs',
    op: 'getSimilarSongs2',
    paramTitles: ['Artist'],
    paramKeys: ['id'],
    fetchMethod: 'getArtist',
    render: function (g) {
      return '{0} to {1} {2}'.f(this.title, this.paramTitles[0], _SubsonicCache.default.ArtistsById[g.p.id] ? _SubsonicCache.default.ArtistsById[g.p.id].name : g.p.id);
    }
  },
  ts: {
    title: 'Top Songs',
    op: 'getTopSongs',
    paramTitles: ['Artist'],
    paramKeys: ['n'],
    render: function (g) {
      return '{0} of {1} {2}'.f(this.title, this.paramTitles[0], g.p.n);
    }
  },
  // more complex recursive generators
  tsa: {
    title: 'Top Related Songs',
    op: 'getTopSimilarSongs',
    paramTitles: ['Artist', 'Count', 'Depth'],
    paramKeys: ['id', 'c', 'd'],
    fetchMethod: 'getArtist',
    render: function (g) {
      return '{0} of {1} {2}, {3}: {4}, {5}: {6}'.f(this.title, this.paramTitles[0], _SubsonicCache.default.ArtistsById[g.p.id] ? _SubsonicCache.default.ArtistsById[g.p.id].name : g.p.id, this.paramTitles[1], g.p.c || 10, this.paramTitles[2], g.p.d || 2);
    }
  },
  ar: {
    title: 'Artist Songs',
    op: 'getArtistSongs',
    paramTitles: ['Artist'],
    paramKeys: ['id'],
    fetchMethod: 'getArtist',
    render: function (g) {
      return 'All Songs of {1} {2}'.f(this.title, this.paramTitles[0], _SubsonicCache.default.ArtistsById[g.p.id] ? _SubsonicCache.default.ArtistsById[g.p.id].name : g.p.id);
    }
  },
  arl: {
    title: 'Artist Albums',
    op: 'getArtistAlbums',
    paramTitles: ['Artist'],
    paramKeys: ['id'],
    fetchMethod: 'getArtist',
    render: function (g) {
      return 'All Albums of {1} {2}'.f(this.title, this.paramTitles[0], _SubsonicCache.default.ArtistsById[g.p.id] ? _SubsonicCache.default.ArtistsById[g.p.id].name : g.p.id);
    }
  },
  md: {
    title: 'Music Directory Songs',
    op: 'getMusicDirectorySongs',
    paramTitles: ['Directory'],
    paramKeys: ['id'],
    fetchMethod: 'getMusicDirectory',
    render: function (g) {
      if (!_SubsonicCache.default.MusicPaths[g.p.id]) {
        _Subsonic.default[this.fetchMethod](g.p.id).then(function (data) {
          _SubsonicCache.default.MusicPaths[g.p.id] = data.directory || {
            id: g.p.id,
            name: '...missing folder...'
          };
        }).catch(function (err) {
          console.error(err);
          _SubsonicCache.default.MusicPaths[g.p.id] = {
            id: g.p.id,
            name: '...missing folder...'
          };
        });
      }

      return 'All Songs under {1} {2}'.f(this.title, this.paramTitles[0], _SubsonicCache.default.MusicPaths[g.p.id] ? _SubsonicCache.default.MusicPaths[g.p.id].name : g.p.id);
    }
  },
  ma: {
    title: 'Music Directory Albums',
    op: 'getMusicDirectoryAlbums',
    paramTitles: ['Directory'],
    paramKeys: ['id'],
    fetchMethod: 'getMusicDirectory',
    render: function (g) {
      if (!_SubsonicCache.default.MusicPaths[g.p.id]) {
        _Subsonic.default[this.fetchMethod](g.p.id).then(function (data) {
          _SubsonicCache.default.MusicPaths[g.p.id] = data.directory || {
            id: g.p.id,
            name: '...missing folder...'
          };
        }).catch(function (err) {
          console.error(err);
          _SubsonicCache.default.MusicPaths[g.p.id] = {
            id: g.p.id,
            name: '...missing folder...'
          };
        });
      }

      return 'All Albums under {1} {2}'.f(this.title, this.paramTitles[0], _SubsonicCache.default.MusicPaths[g.p.id] ? _SubsonicCache.default.MusicPaths[g.p.id].name : g.p.id);
    }
  },
  rt: {
    title: 'Radio Station Current Contents',
    op: 'getPlaylist',
    paramTitles: ['Station'],
    paramKeys: ['id'],
    render: function (g) {
      return 'Fetch pre-loaded songs from {0} {1}'.f(this.paramTitles[0], _SubsonicCache.default.Stations[g.p.id] ? _SubsonicCache.default.Stations[g.p.id].name : g.p.id);
    }
  }
};
exports.GeneratorTypeMap = GeneratorTypeMap;
const StationDataPrototype = {
  addGenerator: function (type, params, w, l) {
    if (typeof type === 'string') {
      this.radiostation.g.push({
        t: type,
        p: params,
        w: w,
        l: l
      });
    } else {
      this.radiostation.g.push(type);
    }
  },
  generate: function (g) {
    g = g.g;
    const map = GeneratorTypeMap[g.t];
    const params = [];

    for (let i = 0; i < map.paramKeys.length; ++i) {
      params[i] = g.p[map.paramKeys[i]];
    } // limit needs to be passed in here...


    console.log(g);

    if (map.op === 'getRandomSongs' && g.p.l) {
      params[map.paramKeys.length] = g.p.l;
    } else if (map.op === 'getSimilarSongs2' && g.p.l) {
      params[map.paramKeys.length] = g.p.l;
    }

    console.log(map.op, _Subsonic.default);
    return _Subsonic.default[map.op].apply(_Subsonic.default, params);
  },
  getNameForId: function (g) {
    const op = g.fetchMethod || g.op;
    if (!op) return null;
    const id = g.p.id;
    return _Subsonic.default[op].apply(_Subsonic.default, [id]);
  },
  renderWeightLimit: function (g) {
    let rv = '';
    if (g.p.l || g.p.w) rv += ' ( ';
    if (g.p.l) rv += 'limit: ' + g.p.l;
    if (g.p.l && g.p.w) rv += ', ';
    if (g.p.w) rv += 'weight: ' + g.p.w;
    if (g.p.l || g.p.w) rv += ' )';
    return rv;
  },
  renderGenerator: function (g) {
    if (!GeneratorTypeMap[g.t]) return '?' + g.t;
    return GeneratorTypeMap[g.t].render(g) + this.renderWeightLimit(g);
  }
};
exports.StationDataPrototype = StationDataPrototype;
const GeneratorVM = {
  s: null,
  g: null,

  get name() {
    return this.s && this.g ? this.s.stationData.renderGenerator(this.g) : null;
  }

};

const moreSongs = function (b) {
  for (let i = 0; i < b.length; ++i) {
    if (b[i].values.length) return true;
  }

  console.log('no more songs');
  return false;
};

const grabSong = function (b) {
  const r = (0, _utils.getRandomIntInclusive)(0, 100);

  for (let i = 0; i < b.length; ++i) {
    if (b[i].match(r)) {
      const p = b[i].pluck(); //console.log("plucked", p, b[i].name, b[i].values.length);

      return p;
    }
  }

  return null;
};

const StationVM = {
  s: null,

  get name() {
    return this.s ? this.s.name : null;
  },

  set name(n) {
    if (this.s) this.s.name = n;
  },

  get id() {
    return this.s ? this.s.id : null;
  },

  set id(n) {
    if (this.s) this.s.id = n;
  },

  get owner() {
    return this.s ? this.s.owner : null;
  },

  set max(m) {
    if (!this.s.stationData.radiostation.o) this.s.stationData.radiostation.o = {};
    this.s.stationData.radiostation.o.m = m;
  },

  get max() {
    return this.s.stationData.radiostation.o ? this.s.stationData.radiostation.o.m : null;
  },

  set tag(m) {
    if (!this.s.stationData.radiostation.o) this.s.stationData.radiostation.o = {};
    this.s.stationData.radiostation.o.t = m;
  },

  get tag() {
    return this.s.stationData.radiostation.o ? this.s.stationData.radiostation.o.t : null;
  },

  set block(m) {
    if (!this.s.stationData.radiostation.o) this.s.stationData.radiostation.o = {};
    this.s.stationData.radiostation.o.b = m;
  },

  get block() {
    return this.s.stationData.radiostation.o ? this.s.stationData.radiostation.o.b : null;
  },

  get generators() {
    console.log('reading generators', 'cache', this._gcache);
    if (this._gcache) return this._gcache;
    if (!this.s) return [];
    const rv = [];
    const gs = this.s.stationData.radiostation.g;

    for (let i = 0; i < gs.length; ++i) {
      rv[i] = Object.create(GeneratorVM);
      rv[i].s = this.s;
      rv[i].g = gs[i];
    }

    this._gcache = rv;
    return rv;
  },

  addGenerator: function (type, params, w, l) {
    this._gcache = null;
    this.s.stationData.addGenerator(type, params, w, l);
  },
  removeGenerator: function (g) {
    this._gcache = null;
    this.s.stationData.radiostation.g.remove(g);
  },
  generate: function (rs, rj) {
    console.log(this);
    const gs = this.generators,
          gfs = [],
          that = this;
    let songs;

    for (let i = 0; i < gs.length; ++i) {
      gfs[i] = this.s.stationData.generate(gs[i], _Subsonic.default);
    }

    let max = parseInt(that.max, 10);
    if (!max || isNaN(max)) max = 900;
    let block = parseInt(that.block, 10);
    if (!block || isNaN(block)) block = 0;
    Promise.all(gfs).then(function (values) {
      console.log('fetched', values);
      const values2 = [];

      const isInRatingRange = function (sa) {
        if (Array.isArray(sa)) sa = sa[0];
        const p = _SubsonicCache.default.MusicPathsRatings[sa.parent]; // console.warn(SubsonicCache.MusicPathsRatings, sa.parent, this.ra, p, this.rz);

        this.ra = (this.ra || 1) * 1;
        this.rz = (this.rz || 5) * 1;
        return this.ra <= p && p <= this.rz;
      };

      for (let i = 0; i < values.length; ++i) {
        // this will be different for the other types
        console.log(i, values[i]);
        let v = values[i];
        if (values[i].playlist) v = values[i].playlist.entry || [];else if (values[i].randomSongs) v = values[i].randomSongs.song || [];else if (values[i].topSongs) v = values[i].topSongs.song || [];else if (values[i].similarSongs) v = values[i].similarSongs.song || [];else if (values[i].similarSongs2) v = values[i].similarSongs2.song || [];else if (values[i].albumCollection) v = values[i].albumCollection.song || values[i].albumCollection.albums || [];else if (values[i].album) v = values[i].album.song || [];else if (values[i].musicDirectory) v = values[i].musicDirectory.song || [];else if (values[i].musicDirectoryAlbums) v = values[i].musicDirectoryAlbums.album || []; // rating

        console.log('rating', gs[i].g.p.ra, gs[i].g.p.rz);

        if (gs[i].g.p.ra || gs[i].g.p.rz) {
          v = v.filter(isInRatingRange.bind(gs[i].g.p));
        } // shuffle each Array (five to really mix it up)


        (0, _utils.arrayShuffle)(v);
        (0, _utils.arrayShuffle)(v);
        (0, _utils.arrayShuffle)(v);
        (0, _utils.arrayShuffle)(v);
        (0, _utils.arrayShuffle)(v);
        console.log(v.length); // now limit it

        console.log('limit?', gs[i].g.p.l);
        if (gs[i].g.p.l) v = v.slice(0, gs[i].g.p.l);
        console.log(v.length);
        values2[i] = v;
      }

      console.log(values2); // now mix them all, incorporating weight

      let buckets = [],
          rest = [],
          nextStart = 0;

      for (let i = 0; i < values2.length; ++i) {
        const g = gs[i];
        let weight = 0;
        if (g.g.p.w) weight = parseInt(g.g.p.w, 10); //console.log(g.name, weight, values2[i].length);

        if (weight) {
          const b = {
            name: 'whocares',
            // g.name,
            weight: weight,
            min: nextStart,
            max: nextStart + weight,
            values: values2[i],
            count: 0,

            /*jshint -W083 */
            match: function (r) {
              //console.log(this.name, "do i match", this.min, r, this.max, this.min <= r && r < this.max);
              return this.min <= r && r < this.max;
            },
            pluck: function () {
              this.count++;
              return this.values.length ? this.values.shift() : null;
            }
          };
          nextStart += weight;
          buckets.push(b);
        } else {
          rest = rest.concat(values2[i]);
        }
      } // shuffle rest


      (0, _utils.arrayShuffle)(rest);
      (0, _utils.arrayShuffle)(rest);
      (0, _utils.arrayShuffle)(rest);
      (0, _utils.arrayShuffle)(rest);
      (0, _utils.arrayShuffle)(rest); // convert rest to a bucket

      rest = {
        name: 'rest',
        min: nextStart,
        max: 100.01,
        weight: 'the rest',
        values: rest,
        count: 0,

        /*jshint -W083 */
        match: function (r) {
          //console.log(this.name, "do i match", this.min, r, this.max, this.min <= r && r < this.max);
          return this.min <= r && r < this.max;
        },
        pluck: function () {
          this.count++;
          return this.values.length ? this.values.shift() : null;
        }
      };
      buckets.push(rest);
      songs = [];
      let i = 0; // here, we keep to 900 to maximize the potential

      while (moreSongs(buckets) && songs.length < 2000 && i < 10000) {
        const s = grabSong(buckets); //console.log("fetching", songs.length, i, s);

        if (s) songs.push(s);
        i++;
      }

      console.log('stopped', songs.length, i);

      for (i = 0; i < buckets.length; ++i) {
        console.log(buckets[i].weight, buckets[i].count, buckets[i].values.length);
      } // fix this when above is ready
      // songs = Array.prototype.concat.apply([], values2);
      //console.log("songs before shuffles", songs.length, songs);
      // only shuffle array if nothing is weighted. otherwise, we want the weights to apply early on in playing...
      // five to really mix it up...


      if (buckets.length === 1) {
        (0, _utils.arrayShuffle)(songs);
        (0, _utils.arrayShuffle)(songs);
        (0, _utils.arrayShuffle)(songs);
        (0, _utils.arrayShuffle)(songs);
        (0, _utils.arrayShuffle)(songs);
      }

      if (that.block) {
        songs = that.blockParty(songs, block);
      } // now reduce to just song id's


      if (!that.generateOnly) {
        songs = songs.map(function (s) {
          if (Array.isArray(s)) {
            s = s.map(function (s) {
              return s.id;
            });
            return s;
          }

          return s.id;
        });
      } //console.log("songs before flatten", songs);


      songs = songs.flatten(); //console.log("songs after flatten", songs);

      if (that.generateOnly) return songs;
      return _Subsonic.default.createPlaylist(that.s.name, that.s.id);
    }).then(function (reply) {
      // updatePlaylist: function(id, comment, share, name, songsToAdd, indexesToRemove) {
      if (that.generateOnly) return songs.slice(0, max);
      return _Subsonic.default.updatePlaylist(that.s.id, that.s.comment, true, that.s.name, songs.slice(0, max), null);
    }).then(function (reply) {
      console.log('gen complete', reply);
      if (rs) rs(reply);
    }).catch(function (err) {
      console.error(err);
      if (rj) rj(err);
    });
  },
  pluckSongs: function (a) {
    const sl = this.keySongs[a];

    for (let i = 0; i < this.block; ++i) {
      if (sl.length === 0) break;
      this.newSongs.push(sl.shift());
    }
  },
  blockParty: function (songs, block) {
    console.log('in', songs); // songs.sort(function(s1,s2) {
    //   const a1 = s1.artistId * 1, a2 = s2.artistId * 1;
    //   return a1 - a2;
    // });
    // ok, here we go
    // 1 sort array into object of artist->songs/albums (remember if s is array, look at s[0] for artist)
    // get keys of artist->songs/albums (or store them in additional array)

    const keySongs = {};
    let keys = [];
    const newSongs = [];
    songs.forEach(function (s) {
      const s1 = Array.isArray(s) ? s[0] : s; //console.log(s, s1);

      let a = s1.artistId;
      if (!a) a = 'no-artist'; //console.log(a);

      if (!keySongs[a]) {
        keySongs[a] = [];
        keys.push(a);
      }

      keySongs[a].push(s);
    }); //console.log(keySongs);

    const pluck = this.pluckSongs.bind({
      keySongs: keySongs,
      block: block,
      newSongs: newSongs
    });

    do {
      // shuffle the keys
      keys = (0, _utils.arrayShuffle)(keys); // and pluck a 'block' of songs out

      keys.forEach(pluck);
    } while (songs.length > newSongs.length); // console.log("out", newSongs);


    return newSongs;
  }
};
exports.StationVM = StationVM;
const RadioMaker = {
  init: function () {
    console.log('init', _Subsonic.default, this);
  },
  getStations: function () {
    return new Promise(function (rs, rj) {
      _Subsonic.default.getPlaylists().then(function (playlist = []) {
        const rv = playlist.filter(function (o) {
          if (o.comment) {
            try {
              const stationData = JSON.parse(o.comment);

              if (stationData && typeof stationData === 'object') {
                if (stationData.radiostation && Array.isArray(stationData.radiostation.g)) {
                  o.stationData = stationData;
                  Object.setPrototypeOf(o.stationData, StationDataPrototype);
                  _SubsonicCache.default.Stations[o.id] = o;
                  return true;
                } else {
                  // not a station data
                  _SubsonicCache.default.Playlists[o.id] = o;
                  return false;
                } // // not a station data
                // SubsonicCache.Playlists[o.id] = o;
                // return false;

              }
            } catch (e) {
              // json.parse failed - just a playlist
              _SubsonicCache.default.Playlists[o.id] = o;
              return false;
            }
          } // no comment - just a playlist


          _SubsonicCache.default.Playlists[o.id] = o;
          return false;
        });
        rs(rv);
      }).catch(function (err) {
        rj(err);
      });
    });
  },
  regenerateAll: function () {
    const stationLaunches = [];
    window.stations.forEach(function (station) {
      if (station.owner !== _Subsonic.default._u) return;
      const stationVM = Object.create(StationVM);
      stationVM.s = station;
      stationLaunches.push(new Promise(stationVM.generate.bind(stationVM)));
    });
    console.log('about to fire...');
    Promise.all(stationLaunches).then(function (reply) {
      console.log(reply);
    }).catch(function (err) {
      console.error(err);
    });
  },
  createStation: function (name) {
    console.log('createStation', this);
    let id = null;
    return new Promise(function (rs, rj) {
      _Subsonic.default.createPlaylist(name).then(function (playlist) {
        const initialStationData = {
          radiostation: {
            g: []
          }
        };
        Object.setPrototypeOf(initialStationData, StationDataPrototype);
        playlist.comment = JSON.stringify(initialStationData);
        playlist.public = true; //   updatePlaylist: function(id, comment, share, name, songsToAdd, indexesToRemove) {

        id = playlist.id;
        return _Subsonic.default.updatePlaylist(playlist.id, playlist.comment, playlist.public);
      }).then(function (data) {
        console.log('data', data, 'now getting it again...');
        return _Subsonic.default.getPlaylist(id);
      }).then(function (data) {
        console.log('got it', data);
        data.playlist.stationData = JSON.parse(data.playlist.comment);
        Object.setPrototypeOf(data.playlist.stationData, StationDataPrototype);
        rs(data.playlist);
      }).catch(function (err) {
        rj(err);
      });
    });
  },
  saveStation: function (station) {
    station.comment = JSON.stringify(station.stationData);
    return _Subsonic.default.updatePlaylist(station.id, station.comment, station.public, station.name);
  },
  deleteStation: function (station) {
    return _Subsonic.default.deletePlaylist(station.id);
  },
  prototypeStations: function (stations) {
    stations.forEach(station => Object.setPrototypeOf(station.stationData, StationDataPrototype));
  }
};
exports.RadioMaker = RadioMaker;
var _default = RadioMaker;
exports.default = _default;