"use strict";

exports.__esModule = true;
exports.default = exports.Station = exports.GeneratorTypeMap = void 0;

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

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: 'getMusicFolder',
    render: function (g) {
      var _g$o, _g$o2;

      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] + ' ' + (((_g$o = g.o) == null ? void 0 : _g$o.title) || ((_g$o2 = g.o) == null ? void 0 : _g$o2.name) || g.p.id);
      return rv;
    }
  },
  pl: {
    title: 'Playlist',
    op: 'getPlaylist',
    paramTitles: ['Playlist'],
    paramKeys: ['id'],
    render: function (g) {
      var _g$o3, _g$o4;

      return (0, _utils.format)('Songs from {0} {1}', this.paramTitles[0], ((_g$o3 = g.o) == null ? void 0 : _g$o3.title) || ((_g$o4 = g.o) == null ? void 0 : _g$o4.name) || g.p.id);
    }
  },
  al: {
    title: 'Album',
    op: 'getAlbum',
    paramTitles: ['Album'],
    paramKeys: ['id'],
    fetchMethod: 'getAlbum',
    render: function (g) {
      var _g$o5, _g$o6;

      return (0, _utils.format)('Songs from {0} {1}', this.paramTitles[0], ((_g$o5 = g.o) == null ? void 0 : _g$o5.title) || ((_g$o6 = g.o) == null ? void 0 : _g$o6.name) || g.p.id);
    }
  },
  alc: {
    title: 'Complete Album',
    op: 'getCompleteAlbum',
    paramTitles: ['Album'],
    paramKeys: ['id'],
    fetchMethod: 'getAlbum',
    render: function (g) {
      var _g$o7, _g$o8;

      return (0, _utils.format)('Complete {0} {1}', this.paramTitles[0], ((_g$o7 = g.o) == null ? void 0 : _g$o7.title) || ((_g$o8 = g.o) == null ? void 0 : _g$o8.name) || g.p.id);
    }
  },
  ss: {
    title: 'Similar Songs',
    op: 'getSimilarSongs2',
    paramTitles: ['Artist'],
    paramKeys: ['id'],
    fetchMethod: 'getArtist',
    render: function (g) {
      var _g$o9, _g$o10;

      return (0, _utils.format)('{0} to {1} {2}', this.title, this.paramTitles[0], ((_g$o9 = g.o) == null ? void 0 : _g$o9.title) || ((_g$o10 = g.o) == null ? void 0 : _g$o10.name) || g.p.id);
    }
  },
  ts: {
    title: 'Top Songs',
    op: 'getTopArtistSongs',
    paramTitles: ['Artist'],
    paramKeys: ['id'],
    render: function (g) {
      var _g$o11, _g$o12;

      return (0, _utils.format)('{0} of {1} {2}', this.title, this.paramTitles[0], ((_g$o11 = g.o) == null ? void 0 : _g$o11.title) || ((_g$o12 = g.o) == null ? void 0 : _g$o12.name) || 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) {
      var _g$o13, _g$o14;

      return (0, _utils.format)('{0} of {1} {2}, {3}: {4}, {5}: {6}', this.title, this.paramTitles[0], ((_g$o13 = g.o) == null ? void 0 : _g$o13.title) || ((_g$o14 = g.o) == null ? void 0 : _g$o14.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) {
      var _g$o15, _g$o16;

      return (0, _utils.format)('All Songs of {1} {2}', this.title, this.paramTitles[0], ((_g$o15 = g.o) == null ? void 0 : _g$o15.title) || ((_g$o16 = g.o) == null ? void 0 : _g$o16.name) || g.p.id);
    }
  },
  arl: {
    title: 'Artist Albums',
    op: 'getArtistAlbums',
    paramTitles: ['Artist'],
    paramKeys: ['id'],
    fetchMethod: 'getArtist',
    render: function (g) {
      var _g$o17, _g$o18;

      return (0, _utils.format)('All Albums of {1} {2}', this.title, this.paramTitles[0], ((_g$o17 = g.o) == null ? void 0 : _g$o17.title) || ((_g$o18 = g.o) == null ? void 0 : _g$o18.name) || g.p.id);
    }
  },
  md: {
    title: 'Music Directory Songs',
    op: 'getMusicDirectorySongs',
    paramTitles: ['Directory'],
    paramKeys: ['id'],
    fetchMethod: 'getMusicDirectory',
    render: function (g) {
      var _g$o19, _g$o20;

      return (0, _utils.format)('All Songs under {1} {2}', this.title, this.paramTitles[0], ((_g$o19 = g.o) == null ? void 0 : _g$o19.title) || ((_g$o20 = g.o) == null ? void 0 : _g$o20.name) || g.p.id);
    }
  },
  ma: {
    title: 'Music Directory Albums',
    op: 'getMusicDirectoryAlbums',
    paramTitles: ['Directory'],
    paramKeys: ['id'],
    fetchMethod: 'getMusicDirectory',
    render: function (g) {
      var _g$o21, _g$o22;

      return (0, _utils.format)('All Albums under {1} {2}', this.title, this.paramTitles[0], ((_g$o21 = g.o) == null ? void 0 : _g$o21.title) || ((_g$o22 = g.o) == null ? void 0 : _g$o22.name) || g.p.id);
    }
  },
  rt: {
    title: 'Radio Station Current Contents',
    op: 'getPlaylist',
    paramTitles: ['Station'],
    paramKeys: ['id'],
    render: function (g) {
      var _g$o23, _g$o24;

      return (0, _utils.format)('Fetch pre-loaded songs from {0} {1}', this.paramTitles[0], ((_g$o23 = g.o) == null ? void 0 : _g$o23.title) || ((_g$o24 = g.o) == null ? void 0 : _g$o24.name) || g.p.id);
    }
  }
}; // these are used when doing the block party mode

exports.GeneratorTypeMap = GeneratorTypeMap;

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

  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();
      return p;
    }
  }

  return null;
};

class Station {
  constructor(playlist) {
    if (!playlist) {
      playlist = {
        id: -1,
        comment: JSON.stringify({
          radiostation: {
            g: [],
            o: {}
          }
        }),
        share: true,
        name: 'New Station',
        entry: []
      };
    }

    this.playlist = playlist;

    try {
      this.stationData = JSON.parse(playlist.comment || false);
    } catch (e) {
      throw e;
    }

    if (!this.stationData) {
      throw new TypeError('Playlist is not a station');
    }
  }

  get name() {
    return this.playlist.name;
  }

  set name(n) {
    this.playlist.name = n;
  }

  get id() {
    return this.playlist.id;
  }

  set id(i) {
    this.playlist.id = i;
  }

  get coverArt() {
    return this.playlist.coverArt || this.playlist.id;
  }

  set coverArt(i) {
    this.playlist.coverArt = i;
  }

  get songCount() {
    return this.playlist.songCount;
  }

  set songCount(i) {
    this.playlist.songCount = i;
  }

  get owner() {
    return this.playlist.owner;
  }

  get public() {
    return this.playlist.public;
  }

  get o() {
    if (!this.stationData.radiostation.o) this.stationData.radiostation.o = {};
    return this.stationData.radiostation.o;
  }

  set max(m) {
    this.o.m = m;
  }

  get max() {
    return this.o.m;
  }

  set tag(m) {
    this.o.t = m;
  }

  get tag() {
    return this.o.t;
  }

  set block(m) {
    this.o.b = m;
  }

  get block() {
    return this.o.b;
  }

  async getGeneratorObject(g) {
    g.gtm = g.gtm || GeneratorTypeMap[g.t];
    const op = g.gtm.fetchMethod || g.gtm.op;
    const id = g.p.id;
    if (!id) return {};
    const object = await _Subsonic.default[op](id);
    g.o = object;
    return object;
  }

  addGenerator(type, params, w, l) {
    let g;

    if (typeof type === 'string') {
      g = {
        gtm: GeneratorTypeMap[type],
        t: type,
        p: params,
        w: w,
        l: l
      };
    } else {
      g = { ...type,
        gtm: GeneratorTypeMap[type.t]
      };
    }

    this.stationData.radiostation.g.push(g);
    return g;
  }

  removeGenerator(g) {
    (0, _utils.arrayRemove)(this.stationData.radiostation.g, g);
  }

  changeGeneratorId(g, id, object) {
    g.p.id = id;
    g.o = object || null;

    if (!object) {
      this.getGeneratorObject(g).then();
    }
  }

  async generators() {
    for (const g of this.stationData.radiostation.g) {
      g.o = await this.getGeneratorObject(g);
    }

    return this.stationData.radiostation.g;
  } // internal cache only


  get generatorArray() {
    return this.stationData.radiostation.g;
  }

  renderWeightLimit(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;
  }

  renderStation() {
    let s = '';
    s += this.name;
    s += ' ';
    s += this.id;
    s += ' ';
    s += this.generatorArray.map(g => this.render(g)).join(' ');
    return s;
  }

  render(g) {
    if (!g.gtm) g.gtm = GeneratorTypeMap[g.t];
    return g.gtm.render(g) + this.renderWeightLimit(g);
  }

  getSongs(res) {
    if (Array.isArray(res)) return res;
    if (res.entry) return res.entry;
    if (res.song) return res.song;
    if (res.playlist) return res.playlist.entry || [];
    if (res.randomSongs) return res.randomSongs.song || [];
    if (res.topSongs) return res.topSongs.song || [];
    if (res.similarSongs) return res.similarSongs.song || [];
    if (res.similarSongs2) return res.similarSongs2.song || [];
    if (res.albumCollection) return res.albumCollection.song || res.albumCollection.albums || [];
    if (res.album) return res.album.song || [];
    if (res.musicDirectory) return res.musicDirectory.song || [];
    if (res.musicDirectoryAlbums) return res.musicDirectoryAlbums.album || [];
    return [];
  }

  async generate(g, forBucket) {
    const gtm = g.gtm;
    const params = [];

    for (let i = 0; i < gtm.paramKeys.length; ++i) {
      params[i] = g.p[gtm.paramKeys[i]];
    }

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

    const res = await _Subsonic.default[gtm.op].apply(_Subsonic.default, params);
    const songs = this.getSongs(res);

    if (forBucket) {
      return {
        songs,
        g
      };
    }

    return songs;
  }

  async generateAllAndSave() {
    const songs = await this.generateAll();
    const songIds = songs.map(c => c.id);
    const {
      id,
      name,
      comment
    } = this.playlist;
    const shared = this.playlist.public;
    let pl = await _Subsonic.default.createPlaylist(name, id || null);
    await _Subsonic.default.updatePlaylist(pl.id, comment || '', shared, name, songIds, null);
  }

  async generateAll() {
    const gs = await this.generators();
    const values = [];

    for (let g of gs) {
      const nextValues = await this.generate(g, true);
      Array.from({
        length: 5
      }, () => (0, _utils.arrayShuffle)(nextValues.songs));
      let limit = parseInt(g.p.l || 0, 10);

      if (limit) {
        nextValues.songs = nextValues.songs.slice(0, limit);
      }

      values.push(nextValues);
    }

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

    for (const v of values) {
      let weight = parseInt(v.g.p.w || 0, 10);

      if (weight) {
        buckets.push({
          name: 'whocares',
          // g.name,
          weight: weight,
          min: nextStart,
          max: nextStart + weight,
          values: v.songs,
          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;
      } else {
        rest = rest.concat(v.songs);
      }
    }

    Array.from({
      length: 5
    }, () => (0, _utils.arrayShuffle)(rest));
    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);
    let songs = [];
    let i = 0;

    while (moreSongs(buckets) && songs.length < 2000 && i < 10000) {
      const s = grabSong(buckets);
      if (s) songs.push(s);
      i++;
    }

    if (buckets.length === 1) {
      Array.from({
        length: 5
      }, () => (0, _utils.arrayShuffle)(songs));
    }

    let block = parseInt(this.block, 10);
    if (!block || isNaN(block)) block = 0;

    if (block) {
      songs = this.blockParty(songs, block);
    }

    let max = parseInt(this.max, 10);
    if (!max || isNaN(max)) max = 900;
    songs = songs.flat(1000).slice(0, max);
    return songs;
  } // block party mode


  pluckSongs(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(songs, block) {
    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);
    });
    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);

    return newSongs;
  }

  async saveStation() {
    const station = this; // copy the data

    const sd = { ...station.stationData
    }; // now trim down the excess

    const rs = sd.radiostation;
    rs.g = rs.g.map(g => ({
      t: g.t,
      p: g.p,
      l: g.l,
      w: g.w
    })); // safety-check incomplete parameters to avoid a blow-up in current radio mode.

    rs.o = rs.o || {};
    rs.o.t = rs.o.t || '';
    rs.o.m = rs.o.m || '';
    rs.o.b = rs.o.b || '';
    station.comment = JSON.stringify(sd);
    console.log(station.id, station.comment, station.public, station.name);
    let pl;

    if (!station.id) {
      pl = await _Subsonic.default.createPlaylist(station.name);
    } else {
      pl = station;
    }

    pl = await _Subsonic.default.updatePlaylist(pl.id, station.comment, station.public === undefined ? true : station.public, station.name);
    return new Station(pl);
  } // async deleteStation(station) {
  //   return Subsonic.deletePlaylist(station.id);
  // }


}

exports.Station = Station;

const createStations = async function (pls) {
  pls.stations = (pls.stationPlaylists || []).map(pl => new Station(pl));
  return pls;
};

var _default = createStations;
exports.default = _default;