
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <glob.h>
#include <limits.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "mecha.h"

#include "iplaylist.h"

#include "file_entry.h"

#include "randomlist_playlist_engine.h"

struct s_RandomlistPlaylistEngine
{
    IPlaylist interface;

    char *playlistName;

    lstTlist *fileIdList;

    int randomizedListOffset;
    int randomizedListLen;

    int *randomizedList;
};


#define GET_RANDOM(__low,__high)    (__low + (double) random() * (__high - __low + 1) / RAND_MAX)

static void randomlist_playlist_engine_destroy (IPlaylist *inst);
static const char * randomlist_playlist_engine_get_name (IPlaylist *inst);
static const char * randomlist_playlist_engine_get_type (MECHA_UNUSED_ARGUMENT IPlaylist *inst);
static int randomlist_playlist_engine_get_next (IPlaylist *inst);
static bool randomlist_playlist_engine_add_file (IPlaylist *inst, int fileId);
static bool randomlist_playlist_engine_remove_file (IPlaylist *inst, int fileId);
#if 0
static IIterator * randomlist_playlist_engine_get_list_iterator (IPlaylist *inst);
static IIterator * randomlist_playlist_engine_get_next_iterator (IPlaylist *inst);
#endif

RandomlistPlaylistEngine *
randomlist_playlist_engine_init (const char *playlistName)
{
   RandomlistPlaylistEngine *instance = (RandomlistPlaylistEngine *) malloc (sizeof (RandomlistPlaylistEngine));

   instance->interface.destroy = randomlist_playlist_engine_destroy;

   instance->interface.get_name = randomlist_playlist_engine_get_name;
   instance->interface.get_type = randomlist_playlist_engine_get_type;

   instance->interface.get_next = randomlist_playlist_engine_get_next;
   instance->interface.add_file = randomlist_playlist_engine_add_file;
   instance->interface.remove_file = randomlist_playlist_engine_remove_file;

#if 0
   instance->interface.get_list_iterator = randomlist_playlist_engine_get_list_iterator;
   instance->interface.get_next_iterator = randomlist_playlist_engine_get_next_iterator;
#endif

   instance->interface.get_list_iterator = NULL;
   instance->interface.get_next_iterator = NULL;

   instance->playlistName = strFcopy (playlistName);

   instance->fileIdList = lstFlist_create (NULL);

   instance->randomizedListOffset = 0;
   instance->randomizedListLen = 0;

   instance->randomizedList = NULL;

   return (instance);
}

static void
randomlist_playlist_engine_destroy (IPlaylist *inst)
{
    RandomlistPlaylistEngine *instance = (RandomlistPlaylistEngine *) inst;

    free (instance->playlistName);

    lstFlist_free (instance->fileIdList);

    if (instance->randomizedListLen > 0)
    {
        free (instance->randomizedList);
    }

    free (instance);
}

static const char *
randomlist_playlist_engine_get_name (IPlaylist *inst)
{
    RandomlistPlaylistEngine *instance = (RandomlistPlaylistEngine *) inst;

    return (instance->playlistName);
}

static const char *
randomlist_playlist_engine_get_type (MECHA_UNUSED_ARGUMENT IPlaylist *inst)
{
    return ("randomlist");
}

static int
randomlist_playlist_engine_get_next (IPlaylist *inst)
{
    RandomlistPlaylistEngine *instance = (RandomlistPlaylistEngine *) inst;

    if (lstFlist_size (instance->fileIdList) == 0)
    {
        return (-1);
    }

   if (instance->randomizedListLen > 0 && instance->randomizedListOffset >= instance->randomizedListLen)
   {
      logFmsg (CONT_LOG_OK, "mod/m3: Resetting randomized list.");

      instance->randomizedListLen = 0;
      instance->randomizedListOffset = 0;

      free (instance->randomizedList);

      instance->randomizedList = NULL;
   }

   if (instance->randomizedListLen == 0)
   {
       int numAssigned = 0;

       instance->randomizedListLen = lstFlist_size (instance->fileIdList);

       instance->randomizedList = (int *) malloc (sizeof (int) * instance->randomizedListLen);

       /* Copy all of the elements from the list to the array */
       int i = 0;
       for (lstTlist_elem *ep = lstFlist_head (instance->fileIdList); ep; ep = lstFlist_next (ep))
       {
           instance->randomizedList[i++] = (int) lstFlist_data (ep);
       }

       /* iterate through all elements and randomize them */
       int newPos;
       int savedData;

       for (i = 0; i < instance->randomizedListLen; i++)
       {
           newPos = GET_RANDOM (0, instance->randomizedListLen - 1);
           savedData = instance->randomizedList[newPos];
           instance->randomizedList[newPos] = instance->randomizedList[i];
           instance->randomizedList[i] = savedData;
       }

       instance->randomizedListOffset = 0;
   }

   return (instance->randomizedList[instance->randomizedListOffset++]);
}

static bool
randomlist_playlist_engine_add_file (IPlaylist *inst, int fileId)
{
    RandomlistPlaylistEngine *instance = (RandomlistPlaylistEngine *) inst;

    if (instance->randomizedListLen > 0)
    {
        instance->randomizedList = realloc (instance->randomizedList, sizeof (int) * (instance->randomizedListLen + 1));

        int ranum = GET_RANDOM (instance->randomizedListOffset, instance->randomizedListLen);

        instance->randomizedList[instance->randomizedListLen] = instance->randomizedList[ranum];
        instance->randomizedList[ranum] = fileId;

        instance->randomizedListLen++;
    }

    lstFlist_add_next (instance->fileIdList, lstFlist_tail (instance->fileIdList), (void *) fileId);

    return (true);
}

static bool
randomlist_playlist_engine_remove_file (IPlaylist *inst, int fileId)
{
    RandomlistPlaylistEngine *instance = (RandomlistPlaylistEngine *) inst;
    lstTlist_elem *elem;

    for (elem = lstFlist_head (instance->fileIdList); elem; elem = lstFlist_next (elem))
    {
        int curFileId = (int) lstFlist_data (elem);

        if (curFileId == fileId)
        {
            lstFlist_remove (instance->fileIdList, elem, NULL);

            break;
        }
    }

    if (elem == NULL)
    {
        return (false);
    }

    /* 
     * remove from the list of files to play. if it already played, we don't
     * need to remove it
     */
    for (int i = instance->randomizedListOffset; i < instance->randomizedListLen; i++)
    {
        if (instance->randomizedList[i] == fileId)
        {
            instance->randomizedList[i] = instance->randomizedList[instance->randomizedListLen - 1];

            instance->randomizedListLen--;

            instance->randomizedList = realloc (instance->randomizedList, sizeof (int) * (instance->randomizedListLen));

            break;
        }
    }

    return (true);
}

#if 0
static int
randomlist_playlist_engine_contains_file (playlist *p, const char *filename)
{
    lstTlist_elem *ep;

    for (ep = lstFlist_head (instance->fileIdList); ep; ep = lstFlist_next (ep))
    {
        FileEntry *fe = (FileEntry *) lstFlist_data (ep);

        if (strcmp (filename, file_entry_get_filename (fe)) == 0)
        {
            return (1);
        }
    }

    return (0);
}

static int
randomlist_playlist_engine_contains_string (lstTlist *lst, const char *value)
{
    lstTlist_elem *ep;

    for (ep = lstFlist_head (lst); ep; ep = lstFlist_next (ep))
    {
        const char *entry = (const char *) lstFlist_data (ep);

        if (strcmp (value, entry) == 0)
        {
            return (1);
        }
    }

    return (0);
}

static int
randomlist_playlist_engine_reload_filelist (playlist *p, const char *filelist)
{
    struct stat sbuf;
    FILE *fp;
    char strbuf[NAME_MAX+1];
    lstTlist *removedFiles;
    lstTlist *addedFiles;
    lstTlist *newList;

    if (stat (filelist, &sbuf) != 0)
        return (-errno);

    if (!(fp = fopen (filelist, "r")))
    {
        logFmsg (CONT_LOG_ERROR, "couldn't open file %s", filelist);
        fclose (fp);
        return (1);
    }

    removedFiles = lstFlist_create (NULL);
    newList = lstFlist_create (free);
    addedFiles = lstFlist_create (NULL);

    while (fgets (strbuf, sizeof (strbuf), fp) != NULL)
    {
        char *nl;

        if (strlen (strbuf) > 0 && (nl = strpbrk (strbuf, "\n\r")))
        {
            *nl = 0;

            if (strbuf[0] == '#')
                continue;

            if (stat (strbuf, &sbuf) != 0)
            {
                logFmsg (CONT_LOG_ERROR, "%s: no such file or directory", strbuf);
                continue;
            }

            {
                char *fileEntryName = strFcopy (strbuf);

                lstFlist_add_next (newList, lstFlist_tail (newList), 
                        fileEntryName);

                if (randomlist_playlist_engine_contains_file (p, strbuf) == 0)
                {
                    lstFlist_add_next (addedFiles, lstFlist_tail (addedFiles), 
                            fileEntryName);
                }
            }
        }
    }

    fclose (fp);

    {
        lstTlist_elem *ep;

        for (ep = lstFlist_head (instance->fileIdList); ep; ep = lstFlist_next (ep))
        {
            FileEntry *fe = (FileEntry *) lstFlist_data (ep);

            if (randomlist_playlist_engine_contains_string (newList, 
                        file_entry_get_filename (fe)) == 0)
            {
                lstFlist_add_next (removedFiles, lstFlist_tail (removedFiles),
                        (void *) file_entry_get_filename (fe));
            }
        }
    }

    {
        lstTlist_elem *ep;

        for (ep = lstFlist_head (addedFiles); ep; ep = lstFlist_next (ep))
        {
            const char *entry = (const char *) lstFlist_data (ep);

            logFmsg (CONT_LOG_DEBUG, "m3/randomlist: %s added to playlist.", entry);

            randomlist_playlist_engine_add_file (p, file_entry_new (entry));
        }
    }

    {
        lstTlist_elem *ep;

        for (ep = lstFlist_head (removedFiles); ep; ep = lstFlist_next (ep))
        {
            const char *entry = (const char *) lstFlist_data (ep);

            logFmsg (CONT_LOG_DEBUG, "m3/randomlist: %s removed from playlist.", entry);

            randomlist_playlist_engine_remove_file (p, entry);
        }
    }

    lstFlist_free (removedFiles);
    lstFlist_free (newList);
    lstFlist_free (addedFiles);

    return (0);
}
#endif
