/*
	Rogue Trader.
	Copyright (c) 2007, Darragh Coy.
	All rights reserved.
	Original work(s), source code & author(s) details available at: http://www.darraghcoy.com

	Redistribution and use in source and binary forms, with or without modification, are permitted 
	provided that the following conditions are met:

		* 	Redistributions of source code must retain the above copyright notice, this list of 
			conditions and the following disclaimer.
		* 	Redistributions in binary form must reproduce the above copyright notice, this list of 
			conditions and the following disclaimer in the documentation.
		* 	Neither the name of the author nor the names of contributors may be used to endorse or 
			promote products derived from this software without specific prior written permission.
		* 	The use of any third party libraries, modules or data files provided with the source 
			form of the program must follow and respect the individual licensing terms of these 
			components. You do not have permission to modify or circumvent the licensing terms for 
			provided third party components unless the third party licenses specifically allow this. 
			If you do not agree to the terms of provided third party components then you do not have 
			permission to use them in your derived work(s).
		* 	The terms of this license do not apply to separate (but related) distributions if supplied 
			by the author(s), only to the contents of this source distribution, derived works, and the 
			binaries produced by either modified or unmodified versions of the source code. The terms 
			of this license do not also apply to any third party libraries or components provided with 
			the source distribution. Such third party elements are licensed under their own separate 
			agreements.

	THIS SOFTWARE IS PROVIDED BY DARRAGH COY ''AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, 
	BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
	ARE DISCLAIMED. IN NO EVENT SHALL DARRAGH COY BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
	EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS 
	OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY 
	OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
	IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################
//
//
// Class Name:   MusicSystem
// Purpose:      Static class that provides a simple mechanism to manage and play
//               MIDI music files.
//
//##############################################################################
//##############################################################################
//##############################################################################
//##############################################################################

#include "MusicSystem.h"

// Include FMOD but disable these warnings from it

#pragma warning(disable:4099)

#include <fmod.h>

#pragma warning(default:4099)

//------------------------------------------------------------------------------
// Static variable initialisation
//------------------------------------------------------------------------------

// The playlist

list<MusicSystem::MidiFile*> * MusicSystem::playlist = 0;

// Save whether the music system initialised successfully here:

bool MusicSystem::initialised = false;

// Pointer to currently playing music stream:

FMUSIC_MODULE * MusicSystem::musicStream = 0;

// Whether music is enabled or not

bool MusicSystem::musicEnabled = true;

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::initialise()
Purpose:  Sets up direct music. Assumes COM objects have been initialised.
          Should be called on program startup.
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::initialise()
{
	// Abort if already initialized:
	
	if ( initialised == false )	
	{
		// Initialise FMOD: Try to initialise the FMOD sound system:

		initialised = FSOUND_Init
		(
			44100,								// mixing rate
			1,									// only need 1 channel
			FSOUND_INIT_USEDEFAULTMIDISYNTH     // flags: force software midi synth
			
		) != 0;	
	    
		// Create the play list and populate

		createPlaylist();
	}
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::createPlaylist()
Purpose:  Creates the music systems playlist and populates it with songs, if there
          are any.
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::createPlaylist()
{
    // If the music list already exists then clear it first:

    if ( playlist != 0 ) clearPlaylist();

    // Create the playlist list structure:

    playlist = new list<MidiFile*>();

    // Save found file information here:

    WIN32_FIND_DATAA findData = {0};

    // Save found file handle here:

    HANDLE foundFile = 0;

    // Search for the first file with the .mid extension in the music directory:

    foundFile = FindFirstFileA( "mus\\*.mid" , &findData );

    // If there was no file found then abort search

    if ( foundFile == INVALID_HANDLE_VALUE ) return;

    // Otherwise add the file to the playlist:

    MidiFile * midiFile = new MidiFile; midiFile->playCount = 0;

    // Create a wide character copy of the midi file string:

    for ( int i = strlen(findData.cFileName) - 1 ; i >= 0 ; i-- )
    {
        midiFile->name[i] = findData.cFileName[i];
    }

    midiFile->name[ strlen(findData.cFileName) ] = 0;

    // Put it into the play list

    playlist->push_back(midiFile);

    // Keep searching for more midi files to add to the playlist

    while ( FindNextFileA( foundFile , &findData ) == TRUE )
    {
        // Add the file to the playlist:

        midiFile = new MidiFile; midiFile->playCount = 0;

        // Create a wide character copy of the midi file string:

        for ( int i = strlen(findData.cFileName) - 1 ; i >= 0 ; i-- )
        {
            midiFile->name[i] = findData.cFileName[i];
        }

        midiFile->name[ strlen(findData.cFileName) ] = 0;

        // Put it into the play list

        playlist->push_back(midiFile);
    }

    // Finally close the search handle with windows:

    FindClose(foundFile);
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::clearPlaylist()
Purpose:  Clears all of the songs in the play list
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::clearPlaylist()
{
    // Abort if there is no playlist:

    if ( playlist == 0 ) return;

    // Get iterators at start and end of playlist

    list<MidiFile*>::iterator cur = playlist->begin  ();
    list<MidiFile*>::iterator end = playlist->end    ();

    // Delete all the items in the play list:

    while ( cur != end )
    {
        delete ( *cur ); cur++;
    }

    // Delete the playlist itself

    delete playlist; playlist = 0;
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::shutdown()
Purpose:  Shuts down the music system. should be called on program end.
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::shutdown()
{
    // Only shut-down FMOD if music was initialised properly

    if ( initialised )
    {
        // Close down the currently playing sound stream if playing:

        if ( musicStream )
        {
            stopMusic();
        }

        // Close down the FMOD sound system:

        FSOUND_Close();
      
        // Set the intialised flag to false:

        initialised = false;
    }

	// Clear all the items in the playlist:

	clearPlaylist();
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::playMusic()
Purpose:  Picks a random track from the play list and begins playing it
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::playMusic()
{
	//--------------------------------------------------------------------------
    // Abort if the music system is not set up
    //--------------------------------------------------------------------------

    if ( initialised == false || playlist == 0 ) return;
    
    // Don't do anything either if music is off:
    
    if ( musicEnabled == false ) return;

    // Stop any currently playing music

    stopMusic();

    // Abort if there is no songs in the music list or music list does not exist:

    if ( playlist == 0 ) return; if ( playlist->size() == 0 ) return;

    //--------------------------------------------------------------------------
    // Pick a song to play
    //--------------------------------------------------------------------------

    // Get the least number of times a song was played by going through the list

    unsigned leastPlays = 0xffffffff;

    list< MidiFile * >::iterator cur = playlist->begin();
    list< MidiFile * >::iterator end = playlist->end();

    while ( cur != end )
    {
        // Get the current song in the list:

        MidiFile * song = * cur;

        // See if it has been played less times than the least plays

        if ( song->playCount < leastPlays  ) leastPlays = song->playCount;

        // Move onto the next song in the list:

        cur++;
    }

	//--------------------------------------------------------------------------
    // Now make a temporary list with all songs equal to that of the minimum playcount:
    // these will be candidate songs to be played.
	//--------------------------------------------------------------------------
	
    list< MidiFile * > candidateSongs;

    cur = playlist->begin();
    end = playlist->end();

    while ( cur != end )
    {
        // Get the current song in the list:

        MidiFile * song = * cur;

        // See if the current song has the same playcount as the lowest playcount

        if ( song->playCount == leastPlays )
        {
            // add this song into the candidate list of songs to play

            candidateSongs.push_back(song);
        }

        // Move onto the next song in the list:

        cur++;
    }

    // Randomnly decide which of the candidate songs to play:

    unsigned song = mlRandomInt() % candidateSongs.size();

    // Get this song:

    cur = playlist->begin(); while ( song ){ song--; cur++; }

    MidiFile * playSong = * cur;

    // Increment the number of times this song has been played

    playSong->playCount = playSong->playCount + 1;

    //--------------------------------------------------------------------------
    // Play the song
    //--------------------------------------------------------------------------
    
    // Makeup the full path to the song:
    
    std::string full_path = "mus\\"; full_path += playSong->name;
    
    // Play it:
    
    playFile( full_path.c_str() );
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::stopMusic()
Purpose:  Stops the currently playing music.
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::stopMusic()
{
    // Abort if music system is not initialised

    if ( initialised == false ) return;

    // Close down the currently playing sound stream if playing:

    if ( musicStream )
    {
        // Stop the music stream first;

        FMUSIC_StopSong(musicStream);

        // Close the music stream: may fail.

        if ( FMUSIC_FreeSong(musicStream) )
        {
            // Stream has closed. Set the music stream to null.

            musicStream = 0; return;
        }
        else
        {
            // Failed to shutdown music.

            return;
        }
    }
    else
    {
        // No music to stop. return

        return;
    }
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::playFile()
Purpose:  Plays the specified midi file
Params:   none
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::playFile( const char * fileName )
{
    // Abort if music system is not initialised

    if ( initialised == false ) return;
    
    // Don't do anything if music is disabled:
    
    if ( musicEnabled == false ) return;

    // If no file was given return:

    if ( fileName == 0 || strlen(fileName) == 0 ) return;

    // If there is a music file playing then stop it:

    if ( musicStream ) stopMusic();

    // Try to open the file as a music stream:

    musicStream = FMUSIC_LoadSong( fileName );

    // Play the stream if it was opened:

    if ( musicStream )
    {
        // Loop the midi song:

        FMUSIC_SetLooping( musicStream, 1);
        
        // Opened the music stream, now try to play it:

        FMUSIC_PlaySong( musicStream );        
        
        // Set volume:
        
        FMUSIC_SetMasterVolume( musicStream , 150 );        
    }
}

/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::isMusicEnabled()
Purpose:  Tells if music is enabled or not
Params:   none
Returns:  True if music is enabled

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/
        
bool MusicSystem::isMusicEnabled()
{
	return musicEnabled;
}
        
/*@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@

Name:     MusicSystem::setMusicEnabled()
Purpose:  Enables/disables music
Params:   Whether music is to be enabled or not
Returns:  none

@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@*/

void MusicSystem::setMusicEnabled(bool enabled)
{
	musicEnabled = enabled;
	
	if (!musicEnabled)
	{
		// Stop music if music is not enabled
	
		stopMusic();
	}
}
