/*Copyright (C) 2010-2020 Duncan Deveaux

This file is part of Hikou no mizu.

Hikou no mizu is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Hikou no mizu is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Hikou no mizu.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "PlaylistPlayer.hpp"

namespace PlaylistPlayer
{
    namespace
    {
        std::map<unsigned int, PlaylistData> playlists;
        std::vector<pthread_t> threads;

        pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
        
        //Launches a notification for threadID's playlist current song
        //threadID must point to a valid and running thread
        //playlists access must be secured (using mutexes) on call.
        void playlistNotify(unsigned int threadID)
        {
            if (playlists[threadID].notifier != NULL && playlists[threadID].notifierMutex != NULL)
            {
                SongData songData = playlists[threadID].playlist.getData();
                
                pthread_mutex_lock(playlists[threadID].notifierMutex);
                playlists[threadID].notifier->notify(songData.author, songData.title);
                
                pthread_mutex_unlock(playlists[threadID].notifierMutex);
            }
        }
        
        void *launchThread(void *data)
        {
            unsigned int threadID = *(static_cast<int*>(data));
            delete static_cast<int*>(data);
            
            pthread_mutex_lock(&mutex);
            
            std::cout << "[Playlist] Thread " << threadID;
            std::cout << " launched for playlist #" << threadID << " playback" << std::endl;
            
            //Reference to the playlist, std::map specification guarantees
            //that it should be stable
            Playlist &playlist = playlists[threadID].playlist;
            playlist.nextSong();
            
            //First notification : for the playlist's start
            playlistNotify(threadID);
            
            float initial_volume = playlist.getVolume(); //Initial volume of the playlist
            
            pthread_mutex_unlock(&mutex);
            
            while (1)
            {
                pthread_mutex_lock(&mutex);
                
                //Update the internally playing music
                playlist.update();
                
                if (playlist.songEnded())
                {
                    std::cout << "[Playlist] Thread " << threadID << " : ";
                    std::cout << "switching to next song" << std::endl;
                    playlist.nextSong();
                    
                    //Graphics notifier handling
                    playlistNotify(threadID);
                }
                
                //Next song
                if (playlists[threadID].next_song)
                {
                    //Start next song
                    playlist.nextSong();
                    
                    //Reset the flag
                    playlists[threadID].next_song = false;
                    
                    //Graphics notifier handling
                    playlistNotify(threadID);
                }
                
                //Force close
                if (playlists[threadID].close_force)
                {
                    std::cout << "[Playlist] Exiting Thread " << threadID << std::endl;
                    pthread_mutex_unlock(&mutex);
                    
                    break;
                }
                
                //Fade close
                if (playlists[threadID].close_fade)
                {
                    float volume = playlist.getVolume() - (initial_volume / 100); //Decrease volume by 1%
                    if (volume > 0)
                        playlist.setVolume(volume);
                    else
                    {
                        std::cout << "[Playlist] Exiting Thread " << threadID << " (fade out)" << std::endl;
                        pthread_mutex_unlock(&mutex);
                        
                        break;
                    }
                }
                
                pthread_mutex_unlock(&mutex);
                
                Timer::sleep(10);
            }
            
            //Erase the playlist
            pthread_mutex_lock(&mutex);
            
            playlist.destroy(); //Destroy the allocated music
            playlists.erase(threadID);
            
            pthread_mutex_unlock(&mutex);
            
            return NULL;
        }
    }
    
    bool startPlaylist(const Playlist &playlist, unsigned int requestedId, PlaylistNotifier *notifier, pthread_mutex_t *notifierMutex)
    {
        if (!isPlaying(requestedId))
        {
            //Register data
            PlaylistData data(playlist);
            pthread_t thread;
            
            pthread_mutex_lock(&mutex);
            
            playlists[requestedId] = data; //requestedId was not already in playlists
            
                //Notifier handling
            if (notifier != NULL && notifierMutex != NULL)
            {
                playlists[requestedId].notifier = notifier; //Set the graphical notifier
                playlists[requestedId].notifierMutex = notifierMutex; //The mutex to conciliate drawing (main thread)
                                                                      //and notifying (the PlaylistPlayer thread)
            }
            
            pthread_mutex_unlock(&mutex);
            
            //Create & register thread
            int *pt_id = new int(requestedId);
            
            pthread_create(&thread, NULL, launchThread, pt_id);
            threads.push_back(thread);
            
            return true;
        }
        
        return false;
    }
    
    void stopPlaylist(unsigned int playlistId)
    {
        pthread_mutex_lock(&mutex);
        
        std::map<unsigned int, PlaylistData>::iterator it;
        for (it = playlists.begin(); it != playlists.end(); it++)
        {
            if (it->first == playlistId)
                it->second.close_fade = true; //Flag to stop the playlist
        }
        
        pthread_mutex_unlock(&mutex);
    }
    
    void playlistNextSong(unsigned int playlistId)
    {
        pthread_mutex_lock(&mutex);
        
        //Search for the right playlist
        std::map<unsigned int, PlaylistData>::iterator it;
        for (it = playlists.begin(); it != playlists.end(); it++)
        {
            if (it->first == playlistId)
                it->second.next_song = true; //Flag to launch the next music
        }
        
        pthread_mutex_unlock(&mutex);
    }
    
    void playlistSetNotifier(unsigned int playlistId, PlaylistNotifier *notifier, pthread_mutex_t *notifierMutex)
    {
        pthread_mutex_lock(&mutex);
        
        //Search for the right playlist
        std::map<unsigned int, PlaylistData>::iterator it;
        for (it = playlists.begin(); it != playlists.end(); it++)
        {
            if (it->first == playlistId)
            {
                it->second.notifier = notifier; //Set the graphical notifier
                it->second.notifierMutex = notifierMutex; //The mutex to conciliate drawing (main thread)
                                                          //and notifying (the PlaylistPlayer thread)
            }
        }
        
        pthread_mutex_unlock(&mutex);
    }

    bool isPlaying(unsigned int playlistId)
    {
        pthread_mutex_lock(&mutex);
        
        std::map<unsigned int, PlaylistData>::iterator it;
        for (it = playlists.begin(); it != playlists.end(); it++)
        {
            if (it->first == playlistId)
            {
                pthread_mutex_unlock(&mutex);
                return true;
            }
        }
        
        pthread_mutex_unlock(&mutex);
        return false;
    }
    
    void destroy()
    {
        //Stop remaining threads
        pthread_mutex_lock(&mutex);
        
        std::map<unsigned int, PlaylistData>::iterator it;
        for (it = playlists.begin(); it != playlists.end(); it++)
            it->second.close_force = true; //Flag to stop the playlist (stop immediately)
        
        pthread_mutex_unlock(&mutex);
        
        //Wait for remaining threads to exit
        for (unsigned int i = 0; i < threads.size(); i++)
            pthread_join(threads[i], NULL);
        
        //Clear playlists & threads
        threads.clear();
        playlists.clear();
        
        //Destroy the allocated mutex
        pthread_mutex_destroy(&mutex);
    }
}
