"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
const DataSource_1 = require("./DataSource");
const node_fetch_1 = require("node-fetch");
const qs = require("qs");
const imageSize = require("image-size");
const storyboard_1 = require("storyboard");
const LAST_FM_SIZES = ['mega', 'extralarge', 'large', 'medium', 'small', ''];
class LastfmDataSource extends DataSource_1.DataSource {
    initialize() {
        return Promise.resolve();
    }
    /**
     * This method will download the first image from images which
     * size is the first element of sizes. If the image is almost a square
     * that is to say the difference between its height and width is inferior
     * to about 5% of its height then it will return the image as a buffer;
     * Otherwise, or if the image cannot be fetched, this will either return
     * the result of fetchLargestImage called with images and sizes minus the first element
     * or an empty buffer (new Buffer('')) if sizes doesn't have an element left
     * @param images an array of the image object returned by LastFM
     * @param sizes an array of LastFM sizes, sorted by size
     * @return a buffer containing the image if found
     */
    fetchLargestImage(images, sizes = LAST_FM_SIZES) {
        return __awaiter(this, void 0, void 0, function* () {
            const match = images.find((el) => el.size === sizes[0]);
            if (!match) {
                if (sizes.length === 1)
                    return;
                return this.fetchLargestImage(images, sizes.slice(1));
            }
            try {
                const res = yield node_fetch_1.default(match['#text']);
                const buffer = yield res.buffer();
                const dims = imageSize(buffer);
                if (Math.abs(dims.height - dims.width) < 0.05 * dims.height) {
                    return buffer;
                }
            }
            catch (err) { }
            if (sizes.length === 1)
                return new Buffer('');
            return this.fetchLargestImage(images, sizes.slice(1));
        });
    }
    getArtistArtwork(artist) {
        return __awaiter(this, void 0, void 0, function* () {
            const q = qs.stringify({
                method: 'artist.getinfo',
                api_key: this.apiKey,
                format: 'json',
                artist
            });
            const res = yield node_fetch_1.default(`http://ws.audioscrobbler.com/2.0/?${q}`);
            const data = yield res.json();
            if (!data.artist)
                return;
            const images = data.artist.image;
            return this.fetchLargestImage(images);
        });
    }
    getAlbumCover(artist, album) {
        return __awaiter(this, void 0, void 0, function* () {
            const q = qs.stringify({
                method: 'album.getinfo',
                api_key: this.apiKey,
                format: 'json',
                artist, album
            });
            // console.log(`http://ws.audioscrobbler.com/2.0/?${q}`)
            const res = yield node_fetch_1.default(`http://ws.audioscrobbler.com/2.0/?${q}`);
            const data = yield res.json();
            if (!data.album)
                return;
            const images = data.album.image;
            return this.fetchLargestImage(images);
        });
    }
    search(query, types = ['artist', 'album', 'track']) {
        if (!types.every((t) => ['artist', 'album', 'track'].includes(t))) {
            throw new Error('Trying to search for unknown type');
        }
        storyboard_1.mainStory.info('datasource', `Performing search for '${query}'`);
        return Promise.all(types.map((type) => __awaiter(this, void 0, void 0, function* () {
            const q = qs.stringify({
                method: type + '.search',
                api_key: this.apiKey,
                format: 'json',
                [type]: query
            });
            const time = Date.now();
            const res = yield node_fetch_1.default(`http://ws.audioscrobbler.com/2.0/?${q}`);
            const data = yield res.json();
            storyboard_1.mainStory.debug('datasource', `GET http://ws.audioscrobbler.com/2.0/?${q} ${Date.now() - time} ms`, {
                attach: data, attachLevel: 'trace'
            });
            const results = data.results[`${type}matches`][type].map((item) => {
                const cover = (item.image || []).reduce((acc, val) => {
                    return Object.assign({}, acc, { [val.size]: val['#text'] });
                }, {});
                return Object.assign({}, {
                    type,
                    name: item.name,
                    id: type === 'album' ? `${item.artist}/${item.name}` : item.name,
                    cover: cover.medium
                }, item.artist ? { artist: item.artist } : {});
            });
            return results;
        }))).then((values) => {
            return [].concat(...values);
        });
    }
    autocomplete(query, type) {
        throw new Error("Method not implemented.");
    }
    getArtistTopAlbums(id) {
        return __awaiter(this, void 0, void 0, function* () {
            const q = qs.stringify({
                method: 'artist.gettopalbums',
                api_key: this.apiKey,
                format: 'json',
                artist: id
            });
            const time = Date.now();
            const res = yield node_fetch_1.default(`http://ws.audioscrobbler.com/2.0/?${q}`);
            const { topalbums } = yield res.json();
            storyboard_1.mainStory.debug('datasource', `GET http://ws.audioscrobbler.com/2.0/?${q} ${Date.now() - time} ms`, {
                attach: { topalbums }, attachLevel: 'trace'
            });
            return topalbums.album.map((album) => {
                const cover = (album.image || []).reduce((acc, val) => {
                    return Object.assign({}, acc, { [val.size]: val['#text'] });
                }, {});
                return {
                    type: 'album',
                    name: album.name,
                    id: `${album.artist.name}/${album.name}`,
                    artist: album.artist.name,
                    cover: cover.medium,
                };
            });
        });
    }
    getArtistById(id) {
        return __awaiter(this, void 0, void 0, function* () {
            storyboard_1.mainStory.info('datasource', `Fetching artist for '${id}'`);
            const q = qs.stringify({
                method: 'artist.getinfo',
                api_key: this.apiKey,
                format: 'json',
                artist: id
            });
            const time = Date.now();
            const res = yield node_fetch_1.default(`http://ws.audioscrobbler.com/2.0/?${q}`);
            const data = yield res.json();
            storyboard_1.mainStory.debug('datasource', `GET http://ws.audioscrobbler.com/2.0/?${q} ${Date.now() - time} ms`, {
                attach: data, attachLevel: 'trace'
            });
            const ent = data.artist;
            const covers = (ent.image || []).reduce((acc, val) => {
                return Object.assign({}, acc, { [val.size]: val['#text'] });
            }, {});
            return {
                type: 'artist',
                name: ent.name,
                id: ent.name,
                cover: covers.medium,
                largeCover: covers.mega,
                bio: (ent.bio || {}).summary,
                topAlbums: yield this.getArtistTopAlbums(id)
            };
        });
    }
    getAlbumById(id) {
        return __awaiter(this, void 0, void 0, function* () {
            storyboard_1.mainStory.info('datasource', `Fetching album for '${id}'`);
            const [artist, album] = id.split('/');
            const q = qs.stringify({
                method: 'album.getinfo',
                api_key: this.apiKey,
                format: 'json',
                artist, album
            });
            const time = Date.now();
            const res = yield node_fetch_1.default(`http://ws.audioscrobbler.com/2.0/?${q}`);
            const data = yield res.json();
            storyboard_1.mainStory.debug('datasource', `GET http://ws.audioscrobbler.com/2.0/?${q} ${Date.now() - time} ms`, {
                attach: data, attachLevel: 'trace'
            });
            const ent = data.album;
            const covers = (ent.image || []).reduce((acc, val) => {
                return Object.assign({}, acc, { [val.size]: val['#text'] });
            }, {});
            const tracks = ent.tracks.track.map((track) => ({
                name: track.name,
                duration: track.duration,
                artist: track.artist.name,
                id: `${track.artist.name}/${track.name}`,
                number: +track['@attr'].rank
            }));
            return {
                type: 'album',
                name: ent.name,
                artist: ent.artist,
                id: `${ent.artist}/${ent.name}`,
                cover: covers.medium,
                largeCover: covers.mega,
                tracks
            };
        });
    }
    getTrackById(id) {
        throw new Error("Method not implemented.");
    }
}
exports.LastfmDataSource = LastfmDataSource;
//# sourceMappingURL=LastfmDataSource.js.map