// @ts-check
// Fusion Research Driver - Music Module - Favorite Management
//
// Copyright 2013-2021 Fusion Research, Inc.
// by Alexa Jade Fanti
//
// Comments:
//   This contains objects and data structures to support favorite Management.
//
//   Wa want to read all the favorites (at least the top 100) and populate system 
//   variables for the Title and Art for each. Then we need to re-do that whenever
//   we get notifications about favorites being added or removed.
//
// Depends: 
//   fusion_stringutils.js
//   fusion_misc.js
//   fusion_debugging.js
//   fusion_http.js
//   fusion_url.js
//   fusion_serverdiscovery.js
//
//System.Print("Fusion Driver - Music Module - Favorite Management loaded...\r\n");


/******************************************************************
 * START Server Global Variables and Functions
 ******************************************************************/
/******************************************************************
 * END   Server Global Variables and Functions
 ******************************************************************/


/******************************************************************
 * START Favorite Entry Object
 ******************************************************************/
function FavoriteEntry(owningFavoriteTool,
                       name, 
                       path,
                       art, 
                       favoriteID,
                       type, // i.e. 'audio'
                       canOpen, 
                       canPlayLoad, 
                       canPlayAdd, 
                       canPlayInsert, 
                       canSearch,
                       canAddFavorite,
                       canRemoveFavorite)
{
 
// <entry path="/favorite=0" can_open="true" can_play_load="false" can_play_add="false" can_play_insert="false" can_search="false" 
//        art="html/images/favorites.png" name="Test1" can_add_favorite="false" can_remove_favorite="true" favorite_id="0"/>
// <entry path="/favorite=1" can_open="false" can_play_load="true" can_play_add="true" can_play_insert="true" can_search="false" 
//        art="music/55e23e26/cover.png" name="Amnesia" type="audio" url="db:album.title=Amnesia" favorite_url="db:album.title=Amnesia" can_add_favorite="false" can_remove_favorite="true" favorite_id="1"/>
// <entry path="/favorite=2" can_open="false" can_play_load="true" can_play_add="true" can_play_insert="true" can_search="false" 
//        art="music/df052d24/cover.png" name="H2O Just Add Water - The Music (ST)" type="audio" url="db:album.title=H2O%20Just%20Add%20Water%20-%20The%20Music%20(ST)" favorite_url="db:album.title=H2O%20Just%20Add%20Water%20-%20The%20Music%20(ST)" can_add_favorite="false" can_remove_favorite="true" favorite_id="2"/>


    this.Initialize();
    this._owner = owningFavoriteTool;
//	if (name              !== undefined) try { this.name              = name.toString();              } catch(err) { }
    if (name              !== undefined) try { this.name              = System.ConvertFromUTF8(name.toString()); } catch(err) { }
    if (path              !== undefined) try { this.path              = System.ConvertFromUTF8(path.toString()); } catch(err) { }
    if (art               !== undefined) try { this.art               = art.toString();               } catch(err) { }
    if (favoriteID        !== undefined) try { this.favoriteID        = favoriteID.toString();        } catch(err) { }
    if (type              !== undefined) try { this.type              = type.toString();              } catch(err) { }
    
    if (canOpen           !== undefined) try { this.canOpen           = parseBool(canOpen);           } catch(err) { }
    if (canPlayLoad       !== undefined) try { this.canPlayLoad       = parseBool(canPlayLoad);       } catch(err) { }
    if (canPlayAdd        !== undefined) try { this.canPlayAdd        = parseBool(canPlayAdd);        } catch(err) { }
    if (canPlayInsert     !== undefined) try { this.canPlayInsert     = parseBool(canPlayInsert);     } catch(err) { }
    if (canSearch         !== undefined) try { this.canSearch         = parseBool(canSearch);     } catch(err) { }
    if (canAddFavorite    !== undefined) try { this.canAddFavorite    = parseBool(canAddFavorite);    } catch(err) { }
    if (canRemoveFavorite !== undefined) try { this.canRemoveFavorite = parseBool(canRemoveFavorite); } catch(err) { }
}


FavoriteEntry.prototype.Initialize = function()
{
    this.name              = "";
    this.path              = "";
    this.art               = "";
    this.favoriteID        = "";
    this.type              = "";
    this.canOpen           = false;
    this.canPlayLoad       = false;
    this.canPlayAdd        = false;
    this.canPlayInsert     = false;
    this.canSearch         = false;
    this.canAddFavorite    = false;
    this.canRemoveFavorite = false;
}

FavoriteEntry.prototype.toString = function()
{
    return this.name + " [" + this.type + "] (" + this.path + ")";
}
/******************************************************************
 * END   Favorite Entry Object
 ******************************************************************/


/******************************************************************
 * START FavoriteTool Class
 ******************************************************************/
function FavoriteTool(owningServer)
{
    this._owner = owningServer;
    this.Initialize();
    this._timer_fetch = new Timer(); // Used to delay fetching for a little bit!
}

FavoriteTool.prototype.Initialize = function()
{
    this.ipaddress = OperationalServerIP();
    this.entries = new Array();
}




// Reporting methods are responsible for updating the RTI System Variables and Events 
// as things change for the Queue object. They report what has already been set elsewhere.
// This means we need to "record" things BEFORE "reporting" them.
// These handle reporting for the specific player/queue (by index) as well 
// as reporting to ANY RTI System Views the player/queue may be selected for.
FavoriteTool.prototype.ReportEntries = function()
{
    DebugFavTool("ReportEntries : We have " + this.entries.length + " favorites (out of a possible 100)", g_DEBUG_LEVEL_TRACE);

    for (var x = 0; x < Math.min(this.entries.length, 100); x++) {
        var entry = this.entries[x];
        var number = x +1;
        DebugFavTool("ReportEntries : Reporting # " + number + ": " + entry.toString(), g_DEBUG_LEVEL_TRACE);
        SysVar_Write("SysVar_Favorite_" + number + "_Title", entry.name);
        SysVar_WriteArt("SysVar_Favorite_" + number + "_Art", entry.art);
    }
    // Clear the empty ones...
    for (var x = this.entries.length; x < 100; x++) {
        var number = x +1;
        DebugFavTool("ReportEntries : Clearing  # " + number, g_DEBUG_LEVEL_TRACE);
        SysVar_Write("SysVar_Favorite_" + number + "_Title", "");
        SysVar_WriteArt("SysVar_Favorite_" + number + "_Art", "");
    }
};


//WARNING: These are coppied from the LibraryLocation Object... when they change we need to update both!
FavoriteTool.prototype.recordEntries = function(entries)
{
    this.entries = new Array();
    if ((entries !== undefined) && (entries !== null) && (entries instanceof Array)) {
        
        var start = this.entries.length;
        var count = entries.length;
        
        for(var i = 0; i < entries.length; i++) this.entries.push(entries[i]);
    }
    this.ReportEntries();
}
//==============> END    Setter Methods

//==============> START  Getter Methods
FavoriteTool.prototype.getEntry = function(index)
{
    try {
        if (index < 0) return null;
        if (index >= this.entries.length) return null;
        return this.entries[index];
    } catch(err) {
        DebugFavTool("getEntry error = " + err, g_DEBUG_LEVEL_ERROR);
    }
}

FavoriteTool.prototype.getCount = function()
{
    if ((this.entries === undefined) || (this.entries === null)) return 0;
    return this.entries.length;
}

FavoriteTool.prototype.Fetch = function()
{
    var playerMac = "";
    if ((this._owner !== undefined) && (this._owner)) playerMac = this._owner.GetAnyPlayerMAC();

    if (playerMac == '') {
        DebugFavTool("Fetch : Can't fetch favorites without a player MAC address!", g_DEBUG_LEVEL_ERROR);
        return false;
    }

    DebugFavTool("Fetch : Immediately (non-blocking)..!", g_DEBUG_LEVEL_TRACE);
    this.InternalFetch(playerMac);
    return true;
}


FavoriteTool.prototype.InternalFetch = function(playerMac)
{
    var url = GetURL_ServerRequestLibrary("browse", 
                                          playerMac, 
                                          '/favorite', // Path
                                          0, // Index
                                          100, // Step
                                          ''); // Search

    DebugFavTool("InternalFetch asking for " + url, g_DEBUG_LEVEL_TRACE);

    var httpReq = new HttpRequest(url);
    httpReq.SetTag(this);
    httpReq.SetCallback(FavoriteTool_Callback);
    httpReq.Fetch();
}


// This is a callback used by the Favorite Tool object when fetching it's list!
// It depends on the FavoriteTool Object being set in the "tag" and having a 
// InternalParseXML method!
function FavoriteTool_Callback(httpRequest, httpResponse)
{
    var xml = httpResponse.GetBody_AsXML();
    var me = httpRequest.GetTag();
    me.InternalParseXML(xml);
}

// This is eventually called by the HTTP engine when we have the response to a fetch request.
// We have to parse the XML and recode our information and trigger events as things happen.
FavoriteTool.prototype.InternalParseXML = function(xml)
{
    if (xml === undefined) {
        DebugFavTool("InternalParse XML is missing", g_DEBUG_LEVEL_ERROR);
    }
    
    var start = parseInt(xml.librarystats.@start.toString());
    if (isNaN(start)) start = 0;
    var total = parseInt(xml.librarystats.@count.toString());
    if (isNaN(total)) total = -1;

    try {
        var entries = new Array();
        var items = xml.library.entry;
        for (var x = 0; x < items.length(); x++) {
            var new_entry = new FavoriteEntry(this,
                                              items[x].@name,
                                              items[x].@path,
                                              items[x].@art,
                                              items[x].@favorite_id,
                                              items[x].@type,
                                              items[x].@can_open,
                                              items[x].@can_play_load,
                                              items[x].@can_play_add,
                                              items[x].@can_play_insert,
                                              items[x].@can_search,
                                              items[x].@can_add_favorite,
                                              items[x].@can_remove_favorite);
            if ((new_entry.type == 'audio') || (new_entry.canPlayLoad)) {
                DebugFavTool("InternalParseXML: Adding favorite #" + x + " " + new_entry.toString(), g_DEBUG_LEVEL_INFO);
                entries.push(new_entry);
            } else {
                DebugFavTool("InternalParseXML: NOT adding the non-playable favorite #" + x + " " + new_entry.toString(), g_DEBUG_LEVEL_WARN);
            }

//DebugFavTool("Read Entry " + x + ": " + new_entry.toString(), g_DEBUG_LEVEL_TRACE);

        }
        this.recordEntries(entries);

    } catch(err) {
        DebugFavTool("InternalParseXML failed with error: " + err, g_DEBUG_LEVEL_ERROR);
    }
}

FavoriteTool.prototype.toString = function()
{
    return this.ipaddress + " has " + this.getCount() + " favorites";
}

FavoriteTool.prototype.GetPath = function(favoriteIndex)
{
    if ((favoriteIndex < 0) || (favoriteIndex > 99)) {
        DebugFavTool("GetPath index (" + favoriteIndex + ") out of range, must be between 1 and 99. Returning empty string.", g_DEBUG_LEVEL_ERROR);
        return '';        
    }

    var favoriteEntry = this.getEntry(favoriteIndex);
    if (favoriteEntry == null) {
        DebugFavTool("GetPath can't find favorite for index (" + favoriteIndex + "). Returning empty string.", g_DEBUG_LEVEL_ERROR);
        return '';        
    }

    return favoriteEntry.path;
}



FavoriteTool.prototype.AddFavorite = function(name, favoriteUrl, favoriteArtUrl)
{
    DebugFavTool("AddFavorite with name = '" + name + "' using URL '" + favoriteUrl + "'" + "with Art = '" + favoriteArtUrl + "'", g_DEBUG_LEVEL_TRACE);
    if ((name === undefined) || (name == null)) {
        DebugFavTool("AddFavorite name is invalid (" + name + "). Using 'Favorite " + this.entries.length + "'' instead.", g_DEBUG_LEVEL_WARN);
        name = "Favorite " + this.entries.length;
    }
    if ((favoriteUrl === undefined) || (favoriteUrl == null) || (favoriteUrl == '')) {
        DebugFavTool("AddFavorite can't functon with invalid (" + favoriteUrl + ") url", g_DEBUG_LEVEL_ERROR);
        return false;
    }

    var url = GetURL_ServerRequestFavorite("favorite_add", encodeURIComponent(name), encodeURIComponent(favoriteUrl), encodeURIComponent(favoriteArtUrl), "", "", "");
    DebugFavTool("AddFavorite calling URL = " + url, g_DEBUG_LEVEL_TRACE);

    var httpReq = new HttpRequest(url);
    httpReq.Fetch();
    return true;
}

FavoriteTool.prototype.RemoveFavorite = function(favoriteId)
{
    DebugFavTool("RemoveFavorite with ID = '" + favoriteId + "'", g_DEBUG_LEVEL_TRACE);
    if ((favoriteId === undefined) || (favoriteId == null) || (favoriteId == '')) {
       DebugFavTool("RemoveFavorite can't functon with invalid ID (" + favoriteId + ")", g_DEBUG_LEVEL_ERROR);
       return false;
    }

    // var id = "";
    // if (libraryEntryPath.substr(0, 10) == "/favorite=") {
    //     id = libraryEntryPath.substr(10);
    // }
    // if ((id === undefined) || (id == null)) {
    //     DebugFavTool("RemoveFavorite can't functon with invalid (" + libraryEntryPath + ") library entry path", g_DEBUG_LEVEL_ERROR);
    //     return false;
    // }

    var url = GetURL_ServerRequestFavorite("favorite_delete", "", "", "", encodeURIComponent(favoriteId), "", "");
    DebugFavTool("RemoveFavorite calling URL = " + url, g_DEBUG_LEVEL_TRACE);

    var httpReq = new HttpRequest(url);
    httpReq.Fetch();
    return true;
}

/******************************************************************
 * END   Server Class
 ******************************************************************/
