using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Object = UnityEngine.Object;

public class TM_LeaderboardEntry
{
    public string      strName;
    public int         iScore;
    public List<int> intValues;
    public List<string> strValues;

    //Position on Leaderboard
    public int iRank;

    public System.DateTime time;

}

public enum ELeaderboardDataState
{
    uninitialized = 0,    //You have not called Initialize yet.
    no_data_ready,        //No data is available yet.
    waiting_for_data,     //You have requested data, and it isn't back yet.
    player_rank_unknown,  //We're currently getting the player's position, so hold up until we do
    ready,                //There is data! You can has.
    request_failed,       //uh oh! Offline?
}

public abstract class TM_LeaderboardManager : MonoBehaviour
{
    //Which set of data was most recently requested
    protected enum ERecentlyRequestedLeaderboardData
    {
        score_range = 0,
        near_player,
        friends,
    }

    //Where the player lives on the full leaderboard for the game.
    protected int iPlayerRank;

    //What did we just ask for?
    protected ERecentlyRequestedLeaderboardData recentlyRequestedDataType;

    //What is our status? This is often pinged from the outside to see if
    //we're ready to provide leaderboard data to the UI.
    protected ELeaderboardDataState dataState;

    //Must be called before any data is collected.
    //The playerID is different for each type of leaderboard
    //Dreamlo requires a string
    //Steam requires an int ID
    public abstract void Initialize(System.Object playerID);

    //Useful if you need to adjust the ID after initialization
    public abstract void ChangePlayerID(System.Object playerID);

    //Leaderboard data is cached, but if you send up a new entry
    //or request a different range of values, the caches are tossed.
    protected List<TM_LeaderboardEntry> requestedTopScoresBlock;
    protected List<TM_LeaderboardEntry> requestedNearPlayerBlock;
    protected List<TM_LeaderboardEntry> requestedFriendsBlock;

    protected int iCachedMinRank;
    protected int iCachedMaxRank;

    protected int iCachedNearPlayerAbove;
    protected int iCachedNearPlayerBelow;

    protected int iCachedFriendsMax;

    //Lets you know what condition the leaderboard is in
    public ELeaderboardDataState GetDataState()
    {
        return dataState;
    }

    //Returns a read-only copy of the data you requested last.
    public virtual IList<TM_LeaderboardEntry> GetLoadedLeaderboardEntries()
    {
        //If we have no playerID yet, complain
        if (dataState == ELeaderboardDataState.uninitialized)
        {
            Debug.LogWarning("TMLeaderboard: Can't ask for loaded entries before initialization. Please call Initialize first.");
            return null;
        }

        if (dataState != ELeaderboardDataState.ready)
        {
            Debug.LogWarning("TMLeaderboard: Data not ready. Use GetDataState to check for readiness.");
            return null;
        }

        switch (recentlyRequestedDataType)
        {
            case ERecentlyRequestedLeaderboardData.score_range:
                return requestedTopScoresBlock.AsReadOnly();
            case ERecentlyRequestedLeaderboardData.near_player:
                return requestedNearPlayerBlock.AsReadOnly();
            case ERecentlyRequestedLeaderboardData.friends:
                return requestedFriendsBlock.AsReadOnly();
            default:
                //How exciting!
                throw new ArgumentOutOfRangeException();
        }
    }

    //Requests a list of entries from min to max. Fills requestedTopScoresBlock
    //If you want to get the very first entry, minRank should be 1, not 0.
    public virtual void RequestLeaderboardEntries(int minRank, int maxRank)
    {
        //If we have no playerID yet, complain
        if (dataState == ELeaderboardDataState.uninitialized)
        {
            Debug.LogWarning("TMLeaderboard: Data request made before initialization. Please call Initialize first.");
            return;
        }
        recentlyRequestedDataType = ERecentlyRequestedLeaderboardData.score_range;

        //If our data is still fresh, we don't need to ask the internet.
        if (minRank == iCachedMinRank &&
            maxRank == iCachedMaxRank &&
            requestedTopScoresBlock != null &&
            requestedTopScoresBlock.Count > 0)
        {
            dataState = ELeaderboardDataState.ready;
            return;
        }

        iCachedMaxRank = maxRank;
        iCachedMinRank = minRank;

        //if not, we'll grab the data in the inherited version of this function.
        RequestLeaderboardEntries_Internal(minRank, maxRank);
    }

    protected abstract void RequestLeaderboardEntries_Internal(int minRank, int maxRank);

    //Requests a list of entries near the player. Fills requestedNearPlayerBlock
    //Above and below determine where the player will sit in the returned list. For example you may want to see the
    //three scores above the player, and six below.
    public virtual void RequestLeaderboardEntriesNearPlayer(int numAbove, int numBelow)
    {
        //If we have no playerID yet, complain
        if (dataState == ELeaderboardDataState.uninitialized)
        {
            Debug.LogWarning("TMLeaderboard: Data request made before initialization. Please call Initialize first.");
            return;
        }
        recentlyRequestedDataType = ERecentlyRequestedLeaderboardData.near_player;

        //If our data is still fresh, we don't need to ask the internet.
        if (numAbove == iCachedNearPlayerAbove &&
            numBelow == iCachedNearPlayerBelow &&
            requestedNearPlayerBlock != null)
        {
            dataState = ELeaderboardDataState.ready;
            return;
        }

        iCachedNearPlayerAbove = numAbove;
        iCachedNearPlayerBelow = numBelow;

        //if not, we'll grab the data in the inherited version of this function.
        RequestLeaderboardEntriesNearPlayer_Internal(numAbove, numBelow);
    }

    protected abstract void RequestLeaderboardEntriesNearPlayer_Internal(int numAbove, int numBelow);

    //For leaderboards that have friend based lookups. Currently Steam does, Dreamlo does not.
    //Fills requestedFriendsBlock
    public virtual void RequestLeaderboardEntriesFriendsOfPlayer(int numEntries)
    {
        //If we have no playerID yet, complain
        if (dataState == ELeaderboardDataState.uninitialized)
        {
            Debug.LogWarning("TMLeaderboard: Data request made before initialization. Please call Initialize first.");
            return;
        }

        //If our data is still fresh, we don't need to ask the internet.
        if (numEntries == iCachedFriendsMax &&
            requestedFriendsBlock != null)
        {
            recentlyRequestedDataType = ERecentlyRequestedLeaderboardData.friends;
            dataState = ELeaderboardDataState.ready;
            return;
        }

        iCachedFriendsMax = numEntries;

        //if not, we'll grab the data in the inherited version of this function.
        RequestLeaderboardEntriesFriendsOfPlayer_Internal(numEntries);
    }

    protected abstract void RequestLeaderboardEntriesFriendsOfPlayer_Internal(int numEntries);

    //Sends a new entry up to the Leaderboard. Since you assigned the ID during initialization,
    //you don't need to send it here. This also clears the cache.
    public virtual void SendNewEntry(int iScore, List<int> listIntValues = null, List<string> listStringValues = null)
    {
        ClearCachedValues();

        //Sending implementation executed in the base class
    }

    protected void ClearCachedValues()
    {
        requestedTopScoresBlock = null;
        requestedNearPlayerBlock = null;
        requestedFriendsBlock = null;

        iCachedMinRank = -1;
        iCachedMaxRank = -1;

        iCachedNearPlayerAbove = -1;
        iCachedNearPlayerBelow = -1;

        iCachedFriendsMax = -1;
    }

}
