"use strict";

import { MusicController } from "./controller";
import {
    PlayerName,
    Track,
    PlayerDevice,
    SpotifyAudioFeature,
    PlayerType,
    PlaylistItem,
    CodyResponse,
    CodyConfig,
    PaginationItem,
    PlayerContext,
    TrackStatus,
    SpotifyAuthState
} from "./models";
import { MusicClient } from "./client";
import { MusicPlayerState } from "./playerstate";
import { AudioStat } from "./audiostat";
import { MusicStore } from "./store";
import { MusicUtil } from "./util";
import { PlaylistService } from "./playlist.service";
import { UserProfile, SpotifyUser } from "./profile";
import { AlbumService } from './album.service';

// initi the props
require('dotenv').config();

// get the instances
const musicClient = MusicClient.getInstance();
const musicCtr = MusicController.getInstance();
const musicPlayerCtr = MusicPlayerState.getInstance();
const musicStore = MusicStore.getInstance();
const musicUtil = new MusicUtil();
const audioStat = AudioStat.getInstance();
const playlist = PlaylistService.getInstance();
const userProfile = UserProfile.getInstance();
const albumSvc = AlbumService.getInstance();

/**
 * Initialize/set music credentials and settings
 * @param config <CodyConfig>
 */
export function setConfig(config: CodyConfig) {
    musicStore.setConfig(config);
}

/**
 * Valid types are: album, artist, playlist, and track
 * keywords: send the keywords to search against.
 * Use specific filter name if you want to search against certain
 * fields.
 * Example searchTracks("track:what a time artist:tom")
 *
 * @param string
 * @param limit (min of 1 and a max of 50)
 */
export function searchTracks(keywords: string, limit: number = 50) {
    return playlist.search("track", keywords, limit);
}

/**
 * Valid types are: album, artist, playlist, and track
 * keywords: send the keywords to search against.
 * Use specific filter name if you want to search against certain
 * fields.
 * Example searchTracks("track:what a time artist:tom")
 *
 * @param string
 * @param limit (min of 1 and a max of 50)
 */
export function searchArtists(keywords: string, limit: number = 50) {
    return playlist.search("artist", keywords, limit);
}

/**
 * Returns true if the user has granted Mac OS access for iTunes control
 */
export function isItunesAccessGranted() {
    return musicStore.itunesAccessGranted;
}

/**
 * Returns false if cody music has been configured to to disable it
 * or if it's the OS is not Mac,
 * otherwise it's set to true by default
 */
export function isItunesDesktopEnabled() {
    return musicStore.itunesDesktopEnabled;
}

/**
 * Returns false if cody music has been configured to to disable it
 * or if it's the OS is not Mac,
 * otherwise it's set to true by default
 */
export function isItunesDesktopSongTrackingEnabled() {
    return musicStore.itunesDesktopTrackingEnabled;
}

/**
 * Get the Spotify accessToken provided via through the setConfig api
 * @returns {string} the spotify access token string
 */
export function getSpotifyAccessToken() {
    return musicStore.credentialByKey("spotifyAccessToken");
}

/**
 * Refresh the Spotify accessToken
 * @returns {Promise<boolean>} whether or not the refresh was successful
 */
 export async function refreshSpotifyAccessToken(): Promise<boolean> {
    const response = await musicClient.refreshSpotifyToken();
    return response.status === "success";
}

/**
 * Returns false if cody music has been configured to to disable it,
 * otherwise it's set to true by default
 */
export function isSpotifyDesktopEnabled() {
    return musicStore.spotifyDesktopEnabled;
}

/**
 * Checks if the Spotify desktop or web player is running or not
 * @returns {Promise<boolean>}
 */
export async function isSpotifyRunning() {
    let running = await isPlayerRunning(PlayerName.SpotifyDesktop);
    if (!running) {
        // check the web
        running = await musicPlayerCtr.isSpotifyWebRunning();
    }
    return running;
}

/**
 * Checks if the iTunes desktop player is running or not
 * @returns {Promise<boolean>}
 */
export function isItunesRunning() {
    return isPlayerRunning(PlayerName.ItunesDesktop);
}

/**
 * Checks if one of the specified players is running
 * @param player {spotify|spotify-web|itunes}
 * @returns {Promise<boolean>}
 */
export async function isPlayerRunning(player: PlayerName) {
    if (player === PlayerName.SpotifyWeb) {
        return await musicPlayerCtr.isSpotifyWebRunning();
    } else {
        let state = await musicCtr.run(player, "checkPlayerRunningState");
        try {
            return JSON.parse(state);
        } catch (err) {
            return false;
        }
    }
}

/**
 * Returns whether there's an active track,
 * (spotify web, spotify desktop, or itunes desktop)
 * @returns {Promise<boolean>}
 */
export async function hasActiveTrack(): Promise<boolean> {
    const track: Track = await getRunningTrack();
    if (track && track.id) {
        return true;
    }
    return false;
}

/**
 * Returns the recommended tracks.
 * @param trackIds (optional) track IDs or URIs (5 max)
 * @param limit (optional) will default to 40 if not specified
 * @param market (optional) will default to none if not specified
 * @param min_popularity (optional) will default to a min or 20
 * @param target_popularity (optional) will default to 90
 * @param seed_genres (optional) the supported spotify genres (5 max)
 * @param seed_genres (optional) artist IDs or URIs (5 max)
 * @param features (optional) supports the tunable track attributes using min_*, max_*, and target_*
 *   i.e. {max_valence: 0.3, target_valence: 0.1}
 */
export async function getRecommendationsForTracks(
    trackIds: string[] = [],
    limit: number = 40,
    market: string = "",
    min_popularity: number = 20,
    target_popularity: number = 100,
    seed_genres: string[] = [],
    seed_artists: string[] = [],
    features: any = {}
): Promise<Track[]> {
    return musicPlayerCtr.getRecommendationsForTracks(
        trackIds,
        limit,
        market,
        min_popularity,
        target_popularity,
        seed_genres,
        seed_artists,
        features
    );
}

/**
 * Returns the currently running track.
 * Spotify web, desktop, or itunes desktop.
 * If it finds a spotify device but it's not playing, and mac iTunes is not playing
 * or paused, then it will return the Spotify track.
 * It will return an empty Track object if it's unable to
 * find a running track.
 * @returns {Promise<Track>}
 **/
export async function getRunningTrack(): Promise<Track> {
    let spotifyWebTrack = null;

    // spotify web try
    // 1st try spotify web
    if (musicStore.spotifyApiEnabled) {
        spotifyWebTrack = await getTrack(PlayerName.SpotifyWeb);
        // check if the spotify track is running (playing or paused)
        let spotifyWebTrackRunning = musicUtil.isTrackRunning(spotifyWebTrack);

        // if it's playing then return it
        if (
            spotifyWebTrackRunning &&
            spotifyWebTrack.state === TrackStatus.Playing
        ) {
            // spotify web track is running. it's the highest priority track
            return spotifyWebTrack;
        }
    }

    let spotifyDesktopTrack = null;
    // spotify desktop try
    if (musicStore.spotifyDesktopEnabled) {
        // next try spotify desktop
        const spotifyDesktopRunning = await isPlayerRunning(
            PlayerName.SpotifyDesktop
        );
        if (spotifyDesktopRunning) {
            spotifyDesktopTrack = await getTrack(PlayerName.SpotifyDesktop);
            const isSpotifyDesktopRunning = musicUtil.isTrackRunning(
                spotifyDesktopTrack
            );
            if (
                isSpotifyDesktopRunning &&
                spotifyDesktopTrack.state === TrackStatus.Playing
            ) {
                spotifyDesktopTrack["playerType"] =
                    PlayerType.MacSpotifyDesktop;
                return spotifyDesktopTrack;
            }
        }
    }

    let itunesDesktopTrack = null;
    // itunes desktop try
    if (
        musicStore.itunesDesktopTrackingEnabled &&
        musicStore.itunesAccessGranted
    ) {
        // still no track or it's paused, try itunes desktop
        const itunesDesktopRunning = await isPlayerRunning(
            PlayerName.ItunesDesktop
        );

        if (itunesDesktopRunning) {
            itunesDesktopTrack = await getTrack(PlayerName.ItunesDesktop);
            if (itunesDesktopTrack && !itunesDesktopTrack.id) {
                // get the 1st track
                itunesDesktopTrack = await musicCtr.run(
                    PlayerName.ItunesDesktop,
                    "firstTrackState"
                );
                if (
                    typeof itunesDesktopTrack === "string" &&
                    itunesDesktopTrack.includes("GRANT_ERROR")
                ) {
                    const errorStr = itunesDesktopTrack;
                    itunesDesktopTrack = new Track();
                    itunesDesktopTrack.error = errorStr;
                } else if (itunesDesktopTrack) {
                    try {
                        itunesDesktopTrack = JSON.parse(itunesDesktopTrack);
                        if (itunesDesktopTrack) {
                            itunesDesktopTrack["playerType"] =
                                PlayerType.MacItunesDesktop;
                        }
                    } catch (e) {
                        //
                    }
                }
            }

            // if itunes is not running, return the spotify web track we've gathered
            const isItunesTrackRunning = musicUtil.isTrackRunning(
                itunesDesktopTrack
            );

            if (
                isItunesTrackRunning &&
                itunesDesktopTrack.state === TrackStatus.Playing
            ) {
                // itunes track is playing, return it
                return itunesDesktopTrack;
            }
        }
    }

    // nothing is playing, check if any are paused in the following order
    // 1) spotify web
    // 2) spotify desktop
    // 3) itunes desktop
    if (spotifyWebTrack && spotifyWebTrack.state == TrackStatus.Paused) {
        return spotifyWebTrack;
    } else if (
        spotifyDesktopTrack &&
        spotifyDesktopTrack.state === TrackStatus.Paused
    ) {
        return spotifyDesktopTrack;
    } else if (
        itunesDesktopTrack &&
        itunesDesktopTrack.state === TrackStatus.Paused
    ) {
        return itunesDesktopTrack;
    }

    return new Track();
}

/**
 * Fetch the recently played spotify tracks
 * @param limit - The maximum number of items to return. Default: 50. Minimum: 1. Maximum: 50
 */
export async function getSpotifyRecentlyPlayedTracks(
    limit: number = 50
): Promise<Track[]> {
    return musicPlayerCtr.getSpotifyRecentlyPlayedTracks(limit);
}

/**
 * Fetch the recently played spotify tracks
 * @param limit - The maximum number of items to return. Default: 50. Minimum: 1. Maximum: 50
 * @param before - Returns all items before (but not including) this cursor position
 */
export async function getSpotifyRecentlyPlayedBefore(
    limit: number = 50,
    before: number = 0
): Promise<CodyResponse> {
    return musicPlayerCtr.getSpotifyRecentlyPlayedTracksBefore(limit, before);
}

/**
 * Fetch the recently played spotify tracks
 * @param limit - The maximum number of items to return. Default: 50. Minimum: 1. Maximum: 50
 * @param after - Returns all items before (but not including) this cursor position
 */
export async function getSpotifyRecentlyPlayedAfter(
    limit: number = 50,
    after: number = 0
): Promise<CodyResponse> {
    return musicPlayerCtr.getSpotifyRecentlyPlayedTracksAfter(limit, after);
}

/**
 * Fetch the spotify player context
 * Info about the device, is playing state, etc.
 */
export async function getSpotifyPlayerContext(): Promise<PlayerContext> {
    return musicPlayerCtr.getSpotifyPlayerContext();
}

/**
 * Returns a track by the given spotify track id
 * @param id
 * @param includeFullArtistData (optional - if true it will return full artist info)
 * @package includeAudioFeaturesData (optional)
 * @param includeGenre (optional)
 */
export async function getSpotifyTrackById(
    id: string,
    includeFullArtistData: boolean = false,
    includeAudioFeaturesData: boolean = false,
    includeGenre: boolean = false
): Promise<Track> {
    return musicPlayerCtr.getSpotifyTrackById(
        id,
        includeFullArtistData,
        includeAudioFeaturesData,
        includeGenre
    );
}

/**
 * Returns tracks by the given spotify track ids
 * @param ids
 * @param includeFullArtistData (optional - if true it will return full artist info)
 * @package includeAudioFeaturesData (optional)
 * @param includeGenre (optional)
 */
export async function getSpotifyTracks(
    ids: string[],
    includeFullArtistData: boolean = false,
    includeAudioFeaturesData: boolean = false,
    includeGenre: boolean = true
): Promise<Track[]> {
    return musicPlayerCtr.getSpotifyTracks(
        ids,
        includeFullArtistData,
        includeAudioFeaturesData,
        includeGenre
    );
}

/**
 * Returns the track of a given player {spotify|spotify-web|itunes}
 * - Spotify does not return a "genre"
 * - duration is in milliseconds
 * @param player {spotify|spotif-web|itunes}
 * @returns {artist, album, genre, disc_number, duration, played_count, track_number, id, name, state}
 */
export async function getTrack(player: PlayerName): Promise<Track> {
    let track: any;
    if (player === PlayerName.SpotifyWeb) {
        // fetch the web track
        track = await musicPlayerCtr.getSpotifyWebCurrentTrack();
    } else {
        // get the string representation of the track.
        // fetch the track from the specified player name.
        // first check to see if the app is running before checking
        const running = await isPlayerRunning(player);
        if (running) {
            track = await musicCtr.run(player, "state");
            if (track) {
                try {
                    track = JSON.parse(track);
                    if (track) {
                        if (player === PlayerName.ItunesDesktop) {
                            track.playerType = PlayerType.MacItunesDesktop;
                        } else {
                            track.playerType = PlayerType.MacSpotifyDesktop;
                        }
                    }
                } catch (e) { }
            }
        }
    }

    if (!track) {
        track = new Track();
    } else if (track && !track["playerType"]) {
        if (player === PlayerName.SpotifyWeb) {
            track["playerType"] = PlayerType.WebSpotify;
        } else if (player === PlayerName.SpotifyDesktop) {
            track["playerType"] = PlayerType.MacSpotifyDesktop;
        } else {
            track["playerType"] = PlayerType.MacItunesDesktop;
        }
    }

    return track;
}

/**
 * Returns the tracks that are found for itunes
 * @param player {itunes}
 * @param playListName
 */
export async function getTracksByPlaylistName(
    player: PlayerName,
    playListName: string
): Promise<Track[]> {
    let playlistItems: Track[] = [];
    const params = null;
    const argv = [playListName];
    const result = await musicCtr.run(
        player,
        "playlistTracksOfPlaylist",
        params,
        argv
    );

    let jsonResult: any = {};
    if (result) {
        try {
            let jsonList;
            if (result.indexOf("[TRACK_END],") !== -1) {
                jsonList = result.split("[TRACK_END],");
            } else {
                jsonList = result.split("[TRACK_END]");
            }
            if (jsonList && jsonList.length > 0) {
                for (let i = 0; i < jsonList.length; i++) {
                    let jsonStr = jsonList[i].trim();
                    if (jsonStr.toLowerCase() !== "ok") {
                        try {
                            jsonResult[i] = JSON.parse(jsonStr);
                        } catch (err) {
                            // it might be the success response "ok"
                        }
                    }
                }
            }
        } catch (err) {
            //
        }
    }
    /**
     * result will have ...
     * '38':
        { artist: 'ZAYN',
            album: 'Dusk Till Dawn (feat. Sia) [Radio Edit] - Single',
            duration: 239000,
            played_count: 260,
            name: 'Dusk Till Dawn (feat. Sia) [Radio Edit]',
            id: '6680' },
     */
    if (jsonResult) {
        // go through the keys and create an array
        // of PlaylistItem
        playlistItems = Object.keys(jsonResult).map((key: string) => {
            let trackItem = jsonResult[key];
            let track: Track = new Track();
            track.name = trackItem.name;
            track.type = "track";
            track.id = trackItem.id;
            track.artist = trackItem.artist;
            track.album = trackItem.album;
            track.played_count = trackItem.played_count;
            track.duration = trackItem.duration;
            track.playerType = PlayerType.MacItunesDesktop;
            return track;
        });
    }
    return playlistItems;
}

export function getSpotifyAlbumTracks(albumId: string): Promise<Track[]> {
    return albumSvc.getSpotifyAlbumTracks(albumId);
}

/**
 * Currently only returns Spotify Web tracks not associated with a playlist.
 * @param player
 * @param qsOptions
 */
export async function getSpotifyLikedSongs(
    qsOptions: any = {}
): Promise<Track[]> {
    return getSavedTracks(PlayerName.SpotifyWeb, qsOptions);
}

/**
 * Currently only returns Spotify Web tracks not associated with a playlist.
 * @param player
 * @param qsOptions
 */
export async function getSavedTracks(
    player: PlayerName,
    qsOptions: any = {}
): Promise<Track[]> {
    let tracks: Track[] = [];
    if (player === PlayerName.SpotifyWeb) {
        tracks = await playlist.getSavedTracks(qsOptions);
    }
    return tracks;
}

/**
 * Returns a playlist by ID
 * @param playlist_id ID is preferred, but we'll transform a URI to an ID
 */
export async function getSpotifyPlaylist(
    playlist_id: string
): Promise<PlaylistItem> {
    return playlist.getSpotifyPlaylist(playlist_id);
}

/**
 * Returns the tracks that are found by the given playlist name
 * - currently spofity-web support only
 * @param player {spotify-web}
 * @param playlist_id (optional)
 * @param qsOptions (optional) {offset, limit}
 */
export async function getPlaylistTracks(
    player: PlayerName,
    playlist_id: string,
    qsOptions: any = {}
): Promise<CodyResponse> {
    if (player === PlayerName.SpotifyWeb) {
        return playlist.getPlaylistTracks(playlist_id, qsOptions);
    }

    // itunes or spotify desktop
    const tracks: Track[] = await getTracksByPlaylistName(player, playlist_id);
    let codyResp: CodyResponse = new CodyResponse();
    let pageItem: PaginationItem = new PaginationItem();
    pageItem.offset = 0;
    pageItem.next = "";
    pageItem.previous = "";
    pageItem.limit = -1;
    pageItem.total = tracks.length;
    pageItem.items = tracks;
    codyResp.data = pageItem;
    return codyResp;
}

/**
 * Plays a playlist at the beginning if the starting track id is not provided.
 * @param playlistId either the ID or URI of the playlist
 * @param startingTrackId either the ID or URI of the track
 * @param deviceId
 */
export function playSpotifyPlaylist(
    playlistId: string,
    startingTrackId: string = "",
    deviceId: string = ""
) {
    return musicCtr.spotifyWebPlayPlaylist(
        playlistId,
        startingTrackId,
        deviceId
    );
}

/**
 * Plays a specific track on the Spotify or iTunes desktop
 * @param player
 * @param params
 * spotify example  ["spotify:track:0R8P9KfGJCDULmlEoBagcO", "spotify:album:6ZG5lRT77aJ3btmArcykra"]
 *   -- provide the trackID then the album or playlist ID
 *   -- they can either be in either URI or ID format
 * itunes example   ["Let Me Down Slowly", "MostRecents"]
 *   -- provide the track name then the playlist name
 */
export function playTrackInContext(player: PlayerName, params: any[]) {
    return musicCtr.playTrackInContext(player, params);
}

/**
 * Mac iTunes only
 * This will allow you to play a playlist starting at a specific playlist track number.
 */
export function playItunesTrackNumberInPlaylist(
    playlistName: string,
    trackNumber: number
) {
    const emptyParams: any = [];
    const scriptArgs: any = [playlistName, trackNumber];
    return musicCtr.run(
        PlayerName.ItunesDesktop,
        "playTrackNumberInPlaylist",
        emptyParams,
        scriptArgs
    );
}

/**
 * Quits/closes the mac Spotify or iTunes player
 * @param player
 */
export function quitMacPlayer(player: PlayerName) {
    return musicCtr.quitApp(player);
}

/**
 * This is only meant for Mac iTunes or Mac Spotify desktop
 * @param player
 * @param params
 */
export async function playTrackInLibrary(player: PlayerName, params: any[]) {
    return await musicCtr.run(player, "playSongFromLibrary", params);
}

/**
 * Initiate and play the specified Spotify device
 * @param device_id {string}
 */
export function playSpotifyDevice(device_id: string) {
    return musicCtr.playPauseSpotifyDevice(device_id, true);
}

/**
 * Initiate and play the specified Spotify device
 * @param device_id {string}
 * @param play {boolean} true to play and false to keep current play state
 */
export function transferSpotifyDevice(device_id: string, play: boolean) {
    return musicCtr.playPauseSpotifyDevice(device_id, play);
}

/**
 * Check if the access/refresh tokens have expired
 */
export function accessExpired(): Promise<boolean> {
    return userProfile.accessExpired();
}

/**
 * Fetch the user's profile
 */
export function getUserProfile(): Promise<SpotifyUser> {
    return userProfile.getUserProfile();
}

/**
 * Helper API to return whether or not the user is logged in to their spotify account or not.
 * It's not fool proof as it only determines if there are any devices found or not.
 * {oauthActivated, loggedIn}
 */
export function spotifyAuthState(): Promise<SpotifyAuthState> {
    return userProfile.spotifyAuthState();
}

/**
 * Initiate the play command for a specific player
 * @param player {spotify|spotify-web|itunes}
 * @param options { uris, device_id }
 * example
 * -- the uris can be in either URI or ID format
 * {device_id: <spotify_device_id>, uris: ["spotify:track:4iV5W9uYEdYUVa79Axb7Rh", "spotify:track:1301WleyT98MSxVHPZCA6M"], context_uri: <playlist_uri, album_uri>}
 */
export function play(player: PlayerName, options: any = {}) {
    if (player === PlayerName.SpotifyWeb) {
        return musicCtr.spotifyWebPlay(options);
    } else {
        return musicCtr.run(player, "play");
    }
}

/**
 * Play a specific spotify track by trackId (it can be the URI or the ID)
 * @param trackId
 * @param deviceId (optional)
 */
export function playSpotifyTrack(trackId: string, deviceId: string = "") {
    return musicCtr.spotifyWebPlayTrack(trackId, deviceId);
}

/**
 * Initiate the play command for a given trackId for a specific player
 * @param player {spotify|spotify-web|itunes}
 * @param trackId {any (string|number)}
 */
export function playTrack(PlayerName: PlayerName, trackId: any) {
    return musicCtr.run(PlayerName, "playTrack", [trackId]);
}

/**
 * Initiate the pause command for a given player
 * @param player {spotify|spotify-web|itunes}
 * @param options
 */
export function pause(player: PlayerName, options: any = {}) {
    if (player === PlayerName.SpotifyWeb) {
        return musicCtr.spotifyWebPause(options);
    } else {
        return musicCtr.run(player, "pause");
    }
}

/**
 * Initiate the play/pause command for a given player
 * @param player {spotify|spotify-web|itunes}
 * @param options
 */
export function playPause(player: PlayerName) {
    return musicCtr.run(player, "playPause");
}

/**
 * Initiate the next command for a given player
 * @param player {spotify|spotify-web|itunes}
 * @param options
 */
export function next(player: PlayerName, options: any = {}) {
    if (player === PlayerName.SpotifyWeb) {
        return musicCtr.spotifyWebNext(options);
    } else {
        return musicCtr.run(player, "next");
    }
}

/**
 * Initiate the previous command for a given player
 * @param player {spotify|spotify-web|itunes}
 * @param options
 */
export function previous(player: PlayerName, options: any = {}) {
    if (player === PlayerName.SpotifyWeb) {
        return musicCtr.spotifyWebPrevious(options);
    } else {
        return musicCtr.run(player, "previous");
    }
}

/**
 * Repeats a playlist
 * @param player
 * @param deviceId
 */
export function setRepeatPlaylist(player: PlayerName, deviceId: string = "") {
    if (player === PlayerName.SpotifyWeb) {
        return musicPlayerCtr.setPlaylistRepeat(deviceId);
    } else {
        return musicCtr.run(player, "repeatOn");
    }
}

/**
 * Repeats a track
 * @param player
 * @param deviceId
 */
export function setRepeatTrack(player: PlayerName, deviceId: string = "") {
    if (player === PlayerName.SpotifyWeb) {
        return musicPlayerCtr.setTrackRepeat(deviceId);
    } else {
        return musicCtr.run(player, "repeatOn");
    }
}

/**
 * Turn repeat off
 * @param player
 * @param deviceId
 */
export function setRepeatOff(player: PlayerName, deviceId: string = "") {
    if (player === PlayerName.SpotifyWeb) {
        return musicPlayerCtr.setRepeatOff(deviceId);
    } else {
        return musicCtr.run(player, "repeatOff");
    }
}

/**
 * Turn on/off repeat for a given player
 * @param player {spotify|spotify-web|itunes}
 * @param options
 */
export function setRepeat(
    player: PlayerName,
    repeat: boolean,
    deviceId: string = ""
) {
    if (
        player === PlayerName.SpotifyWeb ||
        (player === PlayerName.SpotifyDesktop && musicUtil.isWindows())
    ) {
        return musicPlayerCtr.updateRepeatMode(repeat, deviceId);
    } else {
        let repeatParam = repeat ? "repeatOn" : "repeatOff";
        return musicCtr.run(player, repeatParam);
    }
}

/**
 * Turn on/off shuffling for a given player
 * @param player {spotify|spotify-web|itunes}
 */
export function setShuffle(
    player: PlayerName,
    shuffle: boolean,
    deviceId: string = ""
) {
    if (player === PlayerName.SpotifyWeb) {
        // use spotify web api
        return musicPlayerCtr.setShuffle(shuffle, deviceId);
    } else {
        let shuffleParam = shuffle ? ["true"] : ["false"];
        return musicCtr.run(player, "setShuffling", shuffleParam);
    }
}

/**
 * Return whether shuffling is on or not
 * @param player {spotify|spotify-web|itunes}
 */
export async function isShuffling(player: PlayerName) {
    if (player === PlayerName.SpotifyWeb) {
        // use the web api
    }
    const val = await musicCtr.run(player, "isShuffling");
    if (musicUtil.isBooleanString(val)) {
        return JSON.parse(val);
    }
    return val;
}

/**
 * Returns whether the player is on repeat or not
 * - spotify returns true or false, and itunes returns "off", "one", "all"
 * @param player {spotify|spotify-web|itunes}
 */
export async function isRepeating(player: PlayerName) {
    let val = await musicCtr.run(player, "isRepeating");
    if (musicUtil.isBooleanString(val)) {
        return JSON.parse(val);
    }
    return val;
}

/**
 * Update the players volume
 * @param player {spotify|spotify-web|itunes}
 * @param volume {0-100}
 */
export function setVolume(player: PlayerName, volume: number) {
    return musicCtr.setVolume(player, volume);
}

/**
 * Increments the players volume by a number
 * @param player {spotify|spotify-web|itunes}
 */
export function volumeUp(player: PlayerName) {
    return musicCtr.run(player, "volumeUp");
}

/**
 * Decrements the players volume by a number
 * @param player {spotify|spotify-web|itunes}
 */
export function volumeDown(player: PlayerName) {
    return musicCtr.run(player, "volumeDown");
}
/**
 * Mutes the players volume
 * @param player {spotify|spotify-web|itunes}
 */
export function mute(player: PlayerName, device_id: string = "") {
    if (player === PlayerName.SpotifyWeb) {
        return musicPlayerCtr.setMute(true, device_id);
    }
    return musicCtr.run(player, "mute");
}

/**
 * Unmutes the players volume
 * @param player {spotify|spotify-web|itunes}
 */
export function unmute(player: PlayerName, device_id: string = "") {
    if (player === PlayerName.SpotifyWeb) {
        return musicPlayerCtr.setMute(false, device_id);
    }
    return musicCtr.run(player, "unMute");
}

/**
 * Unmutes the players volume
 * @param player {spotify|spotify-web|itunes}
 */
export function setItunesLoved(loved: boolean) {
    return musicCtr.setItunesLoved(loved);
}

/**
 * Save tracks to your liked playlist
 * @param trackIds (i.e. ["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"])
 */
export function saveToSpotifyLiked(trackIds: string[]): Promise<CodyResponse> {
    return playlist.saveToSpotifyLiked(trackIds);
}

/**
 * Remove tracks from your liked playlist
 * @param trackIds (i.e. ["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"])
 */
export function removeFromSpotifyLiked(
    trackIds: string[]
): Promise<CodyResponse> {
    return playlist.removeFromSpotifyLiked(trackIds);
}

/**
 * Returns the playlists for a given player
 * @param player {spotify|spotify-web|itunes}
 * @param (optional) {limit, offset, all}
 */
export async function getPlaylists(
    player: PlayerName,
    qsOptions: any = {}
): Promise<PlaylistItem[]> {
    let playlists: PlaylistItem[] = [];
    if (player === PlayerName.SpotifyWeb) {
        playlists = await playlist.getPlaylists(qsOptions);
    } else {
        let result = await musicCtr.run(player, "playlistTrackCounts");
        if (result) {
            try {
                if (result.indexOf("[TRACK_END],") !== -1) {
                    result = result.split("[TRACK_END],");
                } else {
                    result = result.split("[TRACK_END]");
                }
                if (result && result.length > 0) {
                    for (let i = 0; i < result.length; i++) {
                        let resultItem = result[i];
                        if (resultItem && resultItem.trim().length > 0) {
                            try {
                                // {name, count}
                                let item = JSON.parse(resultItem.trim());
                                let playlistItem: PlaylistItem = new PlaylistItem();
                                playlistItem.type = "playlist";
                                playlistItem.public = true;
                                playlistItem.name = item.name;
                                playlistItem.id = item.name;
                                playlistItem.tracks.total = item.count;
                                if (player === PlayerName.ItunesDesktop) {
                                    playlistItem.playerType =
                                        PlayerType.MacItunesDesktop;
                                } else {
                                    playlistItem.playerType =
                                        PlayerType.MacSpotifyDesktop;
                                }
                                playlists.push(playlistItem);
                            } catch (err) {
                                //
                            }
                        }
                    }
                }
            } catch (err) {
                //
            }
        }
    }

    return playlists;
}

/**
 * Get the full list of the playlist names for a given player
 * @param player {spotify|spotify-web|itunes}
 * @param qsOptions (optional) {limit, offset}
 */
export async function getPlaylistNames(
    player: PlayerName,
    qsOptions: any = {}
): Promise<string[]> {
    if (player === PlayerName.SpotifyWeb) {
        return playlist.getPlaylistNames(qsOptions);
    }
    // result will string of playlist names separated by a comma
    let result = await musicCtr.run(player, "playlistNames");
    // trim the names just in case
    if (result) {
        result = result.split(",");
        // now trim
        result = result.map((name: string) => {
            return name.trim();
        });
    }

    return result;
}

/**
 * Launches a player device
 * @param playerName {spotify|spotify-web|itunes}
 * @param options (spotify-web only) {playlist_id | album_id | track_id }
 */
export function launchPlayer(playerName: PlayerName, options: any = {}) {
    if (playerName === PlayerName.SpotifyWeb) {
        return musicPlayerCtr.launchWebPlayer(options);
    } else {
        return musicCtr.startPlayer(playerName, options);
    }
}

/**
 * Plays a Spotify track within a playlist.
 * It will also launch Spotify if it is not already available by checking the device Ids.
 * @param trackId (optional) If it's not supplied then the playlistId must be provided
 * @param playlistId (optional) If it's not supplied then the trackId must be provided
 * @param playerName (optional) SpotifyWeb or SpotifyDesktop
 */
export function launchAndPlaySpotifyTrack(
    trackId: string = "",
    playlistId: string = "",
    playerName: PlayerName = PlayerName.SpotifyWeb
) {
    return musicPlayerCtr.launchAndPlaySpotifyTrack(
        trackId,
        playlistId,
        playerName
    );
}

/**
 * Plays a Spotify Mac Desktop track within a playlist.
 * It will also launch Spotify if it is not already available by checking the device Ids.
 * @param trackId (optional) If it's not supplied then the playlistId must be provided
 * @param playlistId (optional) If it's not supplied then the trackId must be provided
 */
export function playSpotifyMacDesktopTrack(
    trackId: string = "",
    playlistId: string = ""
) {
    musicCtr.playSpotifyDesktopTrack(trackId, playlistId);
}

/**
 * Returns available Spotify devices
 * @returns {Promise<PlayerDevice[]>}
 */
export function getSpotifyDevices(): Promise<PlayerDevice[]> {
    return musicPlayerCtr.getSpotifyDevices();
}

/**
 * Returns the genre for a provided arguments
 * @param artist {string} is required
 * @param songName {string} is optional
 * @param spotifyArtistId {string} is optional
 */
export function getGenre(
    artist: string,
    songName: string = "",
    spotifyArtistId: string = ""
): Promise<string> {
    return musicCtr.getGenre(artist, songName, spotifyArtistId);
}

/**
 * Returns the spotify genre for a provided arguments
 * @param artist {string} is required
 * @param spotifyArtistId {string} is optional
 */
export function getSpotifyGenre(artist: string): Promise<string> {
    return musicCtr.getGenreFromSpotify(artist);
}

/**
 * Returns the spotify genre for a provided arguments
 * @param spotifyArtistId {string} is required
 */
export function getSpotifyGenreByArtistId(
    spotifyArtistId: string
): Promise<string> {
    return musicCtr.getGenreFromSpotify("" /*artist name*/, spotifyArtistId);
}

/**
 * Returns the highest frequency single genre from the provided list
 * @param genreList
 */
export function getHighestFrequencySpotifyGenre(genreList: string[]): string {
    return musicCtr.getHighestFrequencySpotifyGenre(genreList);
}

/**
 * Returns the recent top tracks Spotify for a user.
 */
export function getTopSpotifyTracks(): Promise<Track[]> {
    return playlist.getTopSpotifyTracks();
}

/**
 * Returns the audio features of the given track IDs
 * @param ids these are the track ids (sans spotify:track)
 */
export function getSpotifyAudioFeatures(
    ids: string[]
): Promise<SpotifyAudioFeature[]> {
    return audioStat.getSpotifyAudioFeatures(ids);
}

/**
 * Create a playlist for a Spotify user. (The playlist will be empty until you add tracks.)
 * @param name the name of the playlist you want to create
 * @param isPublic if the playlist will be public or private
 * @param description (Optioal) displayed in Spotify Clients and in the Web API
 */
export function createPlaylist(
    name: string,
    isPublic: boolean,
    description: string = ""
): Promise<CodyResponse> {
    return playlist.createPlaylist(name, isPublic, description);
}

/**
 * Deletes a playlist of a given playlist ID.
 * @param playlist_id (uri or id)
 */
export function deletePlaylist(playlist_id: string): Promise<CodyResponse> {
    return playlist.deletePlaylist(playlist_id);
}

/**
 * Follow a playlist of a given playlist ID.
 * @param playlist_id (uri or id)
 */
export function followPlaylist(playlist_id: string): Promise<CodyResponse> {
    return playlist.followPlaylist(playlist_id);
}

/**
 * Replace tracks of a given playlist. This will wipe out
 * the current set of tracks and add the tracks specified.
 * @param playlist_id
 * @param track_ids
 */
export function replacePlaylistTracks(
    playlist_id: string,
    track_ids: string[]
) {
    return playlist.replacePlaylistTracks(playlist_id, track_ids);
}

/**
 * Add tracks to a given Spotify playlist.
 * @param playlist_id the Spotify ID for the playlist
 * @param tracks Tracks should be the uri (i.e. "spotify:track:4iV5W9uYEdYUVa79Axb7Rh")
 * but if it's only the id (i.e. "4iV5W9uYEdYUVa79Axb7Rh") this will add
 * the uri part "spotify:track:"
 * @param position The position to insert the tracks, a zero-based index.
 */
export function addTracksToPlaylist(
    playlist_id: string,
    tracks: string[],
    position: number = 0
) {
    return playlist.addTracksToPlaylist(playlist_id, tracks, position);
}

/**
 * Remove tracks from a given Spotify playlist.
 * @param playlist_id the Spotify ID for the playlist
 * @param tracks Tracks should be the uri (i.e. "spotify:track:4iV5W9uYEdYUVa79Axb7Rh")
 * but if it's only the id (i.e. "4iV5W9uYEdYUVa79Axb7Rh") this will add
 * the uri part "spotify:track:"
 */
export function removeTracksFromPlaylist(
    playlist_id: string,
    tracks: string[]
) {
    return playlist.removeTracksFromPlaylist(playlist_id, tracks);
}

/**
 * Returns whether or not the spotify access token has been provided.
 * @returns <boolean>
 */
export function requiresSpotifyAccessInfo(): boolean {
    return !musicStore.hasSpotifyAccessToken() ? true : false;
}

/**
 * Deprecated - use "getTrack(player)"
 */
export function getPlayerState(player: PlayerName): Promise<Track> {
    return getTrack(player);
}

/**
 * Deprecated - use "getRunningTrack()" instead
 */
export function getCurrentlyRunningTrackState(): Promise<Track> {
    return getRunningTrack();
}

/**
 * Deprecated - please use "getPlayerState"
 */
export function getState(player: PlayerName): Promise<Track> {
    return getTrack(player);
}

/**
 * Deprecated - please use "launchPlayer('spotify')"
 **/
export function startSpotifyIfNotRunning() {
    return musicCtr.launchApp(PlayerName.SpotifyDesktop);
}

/**
 * Deprecated - please use "launchPlayer('itunes')"
 */
export function startItunesIfNotRunning() {
    return musicCtr.launchApp(PlayerName.ItunesDesktop);
}

/**
 * Deprecated - please use "isSpotifyRunning" or "isItunesRunning"
 */
export function isRunning(player: PlayerName): Promise<boolean> {
    return isPlayerRunning(player);
}

/**
 * Deprecated - please use "setRepat(player, repeat)"
 */
export function repeatOn(player: PlayerName) {
    return setRepeat(player, true);
}

/**
 * Deprecated - please use "setRepat(player, repeat)"
 */
export function repeatOff(player: PlayerName) {
    return setRepeat(player, false);
}

/**
 * Deprecated - please use "unmute(player)"
 */
export function unMute(player: PlayerName) {
    return unmute(player);
}

/**
 * Deprecated - please use "setConfig(config: CodyConfig)"
 * Set Credentials (currently only supports Spotify)
 * Accepted credentials: clientId, clientSecret, refreshToken, accessToken
 * @param credentials
 */
export function setCredentials(credentials: any) {
    musicStore.setCredentials(credentials);
}

/**
 * Deprecated - please use "getSpotifyAccessToken()"
 * Get the accessToken provided via through the setCredentials api
 * @returns {string} the access token string
 */
export function getAccessToken() {
    return musicStore.credentialByKey("spotifyAccessToken");
}
