// Copyright (C) 2003-2008 Dolphin Project.

// This program 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, version 2.0.

// This program 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 2.0 for more details.

// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/

// Official SVN repository and contact information can be found at
// http://code.google.com/p/dolphin-emu/


//////////////////////////////////////////////////////////////////////////////////////////
// Include
// ŻŻŻŻŻŻŻŻŻŻ
#include <iostream>
#include <vector>
#include <string>
#include <windows.h>

#include "Common.h"	// Common
#include "IniFile.h"
#include "Log.h"

#include "PowerPC/PowerPc.h" // Core

#include "../../../../Source/Core/DiscIO/Src/FileSystemGCWii.h" // This file has #include "Filesystem.h"
#include "../../../../Source/Core/DiscIO/Src/VolumeCreator.h"

#include "../../Player/Src/PlayerExport.h" // Local player
//////////////////////////////////



//////////////////////////////////////////////////////////////////////////////////////////
// Declarations and definitions
// ŻŻŻŻŻŻŻŻŻŻ
namespace MusicMod
{

struct MyFilesStructure
{
		std::string path;
		int offset; // Is int enough, how high does offset go?
};

std::vector <MyFilesStructure> MyFiles;
void StructSort (std::vector <MyFilesStructure> &MyFiles);


// Playback
std::string CurrentFile;
std::string CurrentPlayFile;
std::string CurrentPlayFilePath;
std::string unique_gameid;

std::string MusicPath;

DiscIO::CFileSystemGCWii* my_pFileSystem;

int WritingFile = false;
bool dllloaded = false;

extern bool bShowConsole; // Externally define
extern int GlobalVolume;
//////////////////////////////////


// =======================================================================================
// Supported music files
// ---------------------------------------------------------------------------------------
bool CheckFileEnding(std::string FileName)
{
	if (  
		   (FileName.find(".adp") != std::string::npos) // 1080 Avalanche, Crash Bandicoot etc
		|| (FileName.find(".afc") != std::string::npos) // Zelda WW
		|| (FileName.find(".ast") != std::string::npos) // Zelda TP, Mario Kart
		|| (FileName.find(".dsp") != std::string::npos) // Metroid Prime
		|| (FileName.find(".hps") != std::string::npos) // SSB Melee
		)
		return true;

	return false;
}
// =======================================================================================


// =======================================================================================
// A function to sort the filelist table after offset
// ------------------------
void StructSort (std::vector <MyFilesStructure> &MyFiles)
{
	MyFilesStructure temp;

	//NOTICE_LOG(AUDIO,"StructSort > Begin\n");

     for(int i = 0; i < MyFiles.size() - 1; i++)
     {
          for (int j = i + 1; j < MyFiles.size(); j++)
          {
				if (MyFiles[ i ].offset > MyFiles[ j ].offset)  //comparing cost 
               {
                     temp = MyFiles[ i ]; // Swapping entire struct
                     MyFiles[ i ] = MyFiles[ j ];
                     MyFiles[ j ] = temp;
               }
          }
     }


	for (long i=1; i<(long)MyFiles.size(); ++i)
	{
		std::cout << i << " " << MyFiles[i].path.c_str() << "#" << MyFiles[i].offset << "\n";
	}

	//NOTICE_LOG(AUDIO,"StructSort > Done\n");
}
// ============================


// =======================================================================================
/* Run these things once when Dolphin starts */
// ------------------------
void ShowConsole(bool Show)
{
	if (Show)
	{
		/* What we do here is run StartConsoleWin() in Common directly after each
		   other first in the exe then in the DLL, sometimes this would give me a rampant memory
		   usage increase until the exe crashed at 700 MB memory usage or something like that.
		   For that reason I'm trying to sleep for a moment between them here. */
		Sleep(100);
		Player_Console(true);
	}
	else
	{
		Player_Console(false);
	}
//	Console::Open(100, 2000, "MusicMod", true); // Give room for 2000 rows
}

void Init()
{
	// These things below will not need to be updated after a new game is started
	if (dllloaded) return;

	// ---------------------------------------
	// Load config
	// ---------------------
	IniFile file;
	file.Load("Plainamp.ini");
	file.Get("Interface", "ShowConsole", &MusicMod::bShowConsole, false);
	file.Get("Plainamp", "Volume", &MusicMod::GlobalVolume, 125);	
	// -------

	// ---------------------------------------
	// Make a debugging window
	// ---------------------
	//if(MusicMod::bShowConsole) ShowConsole();

	// Write version
	#ifdef _M_X64
		NOTICE_LOG(AUDIO,"64 bit version\n");
	#else
		NOTICE_LOG(AUDIO,"32 bit version\n");
	#endif
	// -----------

	// Set volume
	Player_Volume(MusicMod::GlobalVolume);

	// Show DLL status
	Player_Main(MusicMod::bShowConsole);
	//play_file("c:\\demo36_02.ast");
	//NOTICE_LOG(AUDIO,"DLL loaded\n");

	dllloaded = true; // Do this once
}
// ============================


// =======================================================================================
/* Function: Read the GC file system when a game is booted
   Called from: BootManager.cpp */
// ------------------------
void Main(std::string FileName)
{
	//
	DiscIO::IVolume* pVolume = DiscIO::CreateVolumeFromFilename(FileName);
	//
	my_pFileSystem = new DiscIO::CFileSystemGCWii(pVolume);
	// Check that it worked
	if (my_pFileSystem->m_FileInfoVector.size() == 0) NOTICE_LOG(AUDIO, "Volume creation failed")

	/* We have to sort the files according to offset so that out scan in Blob.cpp works.
	   Because StructSort() only works for MyFilesStructure I copy the offset and filenames
	   to a new vetor first. */	
	MyFiles.resize(my_pFileSystem->m_FileInfoVector.size()); // Set size
	for (size_t i = 0; i < my_pFileSystem->m_FileInfoVector.size(); i++)
	{
		MyFiles.at(i).offset = my_pFileSystem->m_FileInfoVector.at(i).m_Offset;
		MyFiles.at(i).path = my_pFileSystem->m_FileInfoVector.at(i).m_FullPath;
	}		

	// Sort the files by offset
	StructSort(MyFiles);

	// ---------------------------------------------------------------------------------------
	// Make Music directory
	// -------------------------
	LPSECURITY_ATTRIBUTES attr;
	attr = NULL;
	MusicPath = "Music\\";
	NOTICE_LOG(AUDIO,"Created a Music directory\n");
	CreateDirectory(MusicPath.c_str(), attr);
	// ----------------------------------------------------------------------------------------
}


// =======================================================================================
// Check if we should play this file
// ---------------------------------------------------------------------------------------
void CheckFile(std::string File, int FileNumber)
{
	// Do nothing if we found the same file again
	if (CurrentFile == File) return;

	// Check if it's a music file
	if (CheckFileEnding(File.c_str()))
	{
		/* Don't restart the playback if we find the same music file again. If the game is playing
		   a streaming music file it may read from it once in a while, after it has read other
		   files in between, if did not do this check we would restart the playback in those cases */
		if (CurrentPlayFile == File) return;

		// Notify the user
		NOTICE_LOG(AUDIO,"\n >>> (%i/%i) Match %s\n", FileNumber, MyFiles.size(), File.c_str());

		 // Save the matched file
		CurrentPlayFile = File;

		// ---------------------------------------------------------------------------------------
		// We will now save the file to the PC hard drive
		// ------------------
		// Get the filename
		std::size_t pointer = File.find_last_of("/");
		std::string fragment = File.substr (0, (pointer-0));
		int compare = File.length() - fragment.length(); // Get the length of the filename
		fragment = File.substr ((pointer+1), compare); // Now we have the filename
		
		// Create the target file path
		std::string FilePath = (MusicPath + fragment);

		WritingFile = true; // Avoid detecting the file we are writing
		NOTICE_LOG(AUDIO, "Writing '%s' to '%s'", File.c_str(), FilePath.c_str());
		if (!my_pFileSystem->ExportFile(File.c_str(), FilePath.c_str()))
			NOTICE_LOG(AUDIO, "ERROR: ExportFile failed");
		WritingFile = false;
		// ------------------------------------------------------
		
		// ---------------------------------------------------------------------------------------
		// Play the file we found
		// ------------------
		if(dllloaded)
		{
			Player_Play((char*)FilePath.c_str()); // retype it from const char* to char*
		} else {
			NOTICE_LOG(AUDIO, "Warning > Music DLL is not loaded");
		}
		// ------------------------------------------------------

		// ---------------------------------------------------------------------------------------
		// Remove the last file, if any
		// ------------------
		if(CurrentPlayFilePath.length() > 0)
		{
			if(!remove(CurrentPlayFilePath.c_str()))
			{
				NOTICE_LOG(AUDIO,"The program failed to remove '%s'", CurrentPlayFilePath.c_str());
			} else {
				NOTICE_LOG(AUDIO,"The program removed '%s'", CurrentPlayFilePath.c_str());
			}
		}
		// ------------------------------------------------------

		// ---------------------------------------------------------------------------------------
		// Save the current playing file
		// ------------------
		CurrentPlayFilePath = FilePath; // Save the filename so we can remove it later
	}

	// Tell the user about the files we ignored
	NOTICE_LOG(AUDIO,"(%i/%i) Ignored '%s'\n", FileNumber, MyFiles.size(), File.c_str());
	
	// Update the current file
	CurrentFile = File;
}


//////////////////////////////////////////////////////////////////////////////////////////
// Find the current filename for a certain offset on the GC fileystem
// ŻŻŻŻŻŻŻŻŻŻŻŻŻ
void FindFilename(u64 offset, u64 size)
{
	// =======================================================================================
	/* Only do this test:
			1. After boot, not on ISO scan
			2. Not if offset = 0.
			3. Not when WritingFile. We will lead to calls back to here (Read) and it will mess
			   upp the scanning */
	if(PowerPC::GetState() == PowerPC::CPUState::CPU_RUNNING && offset != 0 && !WritingFile)
	{
		//////////////////////////////////////////////////////////////////////////////////////////
		/* Get the filename. Here we go through all files until we come to the file that has
		   the matching offset. Before MyFiles has data this loop will go nowhere. We have to
		   specify (MyFiles.size() - 1) because we will be reading MyFiles.at(i + 1).
		   MyFiles.size() is the amount of files on the disc, and the last file is 
		   MyFiles.at(MyFiles.size() - 1) */
		// ---------------------------------------------------------------------------------------
		for (int i = 0; i < (int)(MyFiles.size() - 1); ++i)
		{
			// Log
			//NOTICE_LOG(AUDIO, ">>> Comparing %s [#%i, Size: %i, Location: %u] with %u", MyFiles[i].path.c_str(), i, size, MyFiles[i].offset, offset);

			// ---------------------------------------------------------------------------------------
			/* If we assume that myoffset is the begginning of every file this works. 
			   Suppose there are three files
					1 is 0 to 149
					2 is 150 to 170
					3 is 171 to 200
			   If the offset is 160 the game is reading file number two, for example
					myoffset = 150: (myoffset >= offset) == false
					myoffset = 171: (myoffset >= offset) == true, break

			  However if the offset is 195 the game is reading the last file and we will get
					myoffset = 171: (myoffset >= offset) == false
			  we therefore need to add the condition (offset > MyFiles[MyFiles.size() - 1].offset)
			  to. */
			if (MyFiles[i + 1].offset >= offset || offset > MyFiles[MyFiles.size() - 1].offset)
			{
				// Now we know that the game is reading from MyFiles[i].path
				CheckFile(MyFiles[i].path, i);

				// Stop checking
				break;
			}

			// If the file check failed
			if (i == (int)(MyFiles.size() - 1) - 1)
			{
				NOTICE_LOG(AUDIO, "ERROR: GC filename search failed");
			}
		}
	} // This ends the entire filescan
	// =======================================================================================
}
/////////////////////////////////


} // end of namespace
