void ReadMetadata (GString line, OsuLoadInfo* Info)
{
	auto Command = line.substr(0, line.find_first_of(":")); // Lines are Information:Content
	auto Content = line.substr(line.find_first_of(":") + 1, line.length() - line.find_first_of(":"));

#ifdef VERBOSE_DEBUG
	printf("Command found: %s | Contents: %s\n", Command.c_str(), Content.c_str());
#endif

	Utility::Trim(Content);
	if (Command == "Title")
	{
		Info->OsuSong->SongName = Content;
	}else if (Command == "Artist")
	{
		Info->OsuSong->SongAuthor = Content;
	}else if (Command == "Version")
	{
		Info->Diff->Name = Content;
	}else if (Command == "TitleUnicode")
	{
		if (Content.length() > 1)
			Info->OsuSong->SongName = Content;
	}else if (Command == "ArtistUnicode")
	{
		if (Content.length() > 1)
			Info->OsuSong->SongAuthor = Content;
	}
	else if (Command == "Creator")
	{
		Info->Diff->Author = Content;
	}
}
bool ReadGeneral (GString line, OsuLoadInfo* Info)
{
	GString Command = line.substr(0, line.find_first_of(" ")); // Lines are Information:<space>Content
	GString Content = line.substr(line.find_first_of(":") + 1);

	Content = Content.substr(Content.find_first_not_of(" "));

	if (Command == "AudioFilename:")
	{
		if (Content == "virtual")
		{
			Info->Diff->IsVirtual = true;
			return true;
		}
		else
		{
#ifdef VERBOSE_DEBUG
			printf("Audio filename found: %s\n", Content.c_str());
#endif
			Utility::Trim(Content);
			Info->OsuSong->SongFilename = Content;
			Info->OsuSong->SongPreviewSource = Content;
		}
	}else if (Command == "Mode:")
	{
		Info->ReadAModeTag = true;
		if (Content != "3") // It's not a osu!mania chart, so we can't use it.
			return false;
	}else if (Command == "SampleSet:")
	{
		Utility::ToLower(Content); Utility::Trim(Content);
		Info->DefaultSampleset = Content;
	}
	else if (Command == "PreviewTime:")
	{
		if (Content != "-1")
		{
			if (Info->OsuSong->PreviewTime == 0)
				Info->OsuSong->PreviewTime = latof(Content) / 1000;
		}																							
	} else if (Command == "SpecialStyle:")
	{
		if (Content == "1")
			Info->Diff->Data->Turntable = true;
	}

	return true;
}
void ReadDifficulty (GString line, OsuLoadInfo* Info)
{
	GString Command = line.substr(0, line.find_first_of(":")); // Lines are Information:Content
	GString Content = line.substr(line.find_first_of(":") + 1, line.length() - line.find_first_of(":"));
	Utility::Trim(Content);

	// We ignore everything but the key count!
	if (Command == "CircleSize")
	{
		Info->Diff->Channels = atoi(Content.c_str());
	}else if (Command == "SliderMultiplier")
	{
		Info->SliderVelocity = latof(Content.c_str()) * 100;
	}
	else if (Command == "HPDrainRate")
	{
		Info->TimingInfo->HP = latof(Content.c_str());
	}
	else if (Command == "OverallDifficulty")
	{
		Info->TimingInfo->OD = latof(Content.c_str());
	}

}
bool LoadTracksSM(Song *Out, Difficulty *Diff, GString line)
{
	GString CommandContents = line.substr(line.find_first_of(":") + 1);

	/* Remove newlines and comments */
	CommandContents = RemoveComments(CommandContents);

	/* Split contents */
	auto Mainline = Utility::TokenSplit(CommandContents, ":");

	if (Mainline.size() < 6) // No, like HELL I'm loading this.
	{
		// The first time I found this it was because a ; was used as a separator instead of a :
		// Which means a rewrite is what probably should be done to fix that particular case.
		wprintf(L"Corrupt simfile (%d entries instead of 6)", Mainline.size());
		return false;
	}

	/* What we'll work with */
	GString NoteString = Mainline[5];
	int Keys = GetTracksByMode(Mainline[0]);

	if (!Keys)
		return false;

	Diff->Level = atoi(Mainline[3].c_str());
	Diff->Channels = Keys;
	Diff->Name = Mainline[2] + "(" + Mainline[0] + ")";

	/* Now we should have our notes within NoteGString.
	We'll split them by measure using , as a separator.*/
	auto MeasureText = Utility::TokenSplit(NoteString);

	LoadNotesSM(Out, Diff, MeasureText);

	/*
		Through here we can make a few assumptions.
		->The measures are in order from start to finish
		->Each measure has all potential playable tracks, even if that track is empty during that measure.
		->Measures are internally ordered
		*/
	return true;
}
void LoadNotes(Song* Out, Difficulty * Diff, GString line)
{
	// get the object GString (all between a colon and a semicolon.
	GString objectString = line.substr(line.find_first_of(":") + 1);
	std::vector< GString > splitvec;
	bool invert = false;

	Diff->Name = Out->SongName; // todo: change this.
	Diff->TotalNotes = Diff->TotalHolds = Diff->TotalObjects = 0;

	// Remove whitespace.
	Utility::ReplaceAll(objectString, "[\n\r]", "");

	splitvec = Utility::TokenSplit(objectString); // Separate measures!
	for(GString objectlist: splitvec) // for each measure
	{
		std::vector< GString > splitobjects;
		Measure Msr;
		invert = false;

		if ( objectlist.length() == 0 )
		{
			Diff->Measures.push_back(Msr);
			continue;
		}

		/* Mirror command. */
		if ( objectlist[0] == 'M')
		{
			invert = true;
			Utility::ReplaceAll(objectlist, "M", "");
		}

		splitobjects = Utility::TokenSplit(objectlist, "{}", true);
		size_t SoSize = 0;
		size_t CurObj = 0;

		for(GString object_description: splitobjects) // Count total valid objects
		{
			if (object_description.length() != 0)
				SoSize += 1;
		}

		for(GString object_description: splitobjects) // For all objects in measure
		{
			std::vector< GString > object_parameters;

			if (object_description.length() == 0) // we must have at least a plain "0"
				continue;

			object_parameters = Utility::TokenSplit(object_description, " :");
			if (object_parameters.size() > 0) // We got a position
			{
				int32 xpos = 0;
				float hold_duration = 0;
				int32 sound = 0;

				if (object_parameters[0].length() > 0) // does it have length?
					xpos = latof (object_parameters[0].c_str()); // assign it
				

				if (object_parameters.size() > 1) // We got a hold note parameter
				{
					if (object_parameters[1].length() > 0) // length?
						hold_duration = latof (object_parameters[1].c_str()); // load it in

					if (object_parameters.size() > 2) // We got a sound parameter
					{
						if (object_parameters[2].length() > 0) // got a valid sound?
							sound = latof (object_parameters[2].c_str()); // cast it in
					}
				}

				if (invert)
				{
					if (xpos != 0)
						xpos = PlayfieldWidth - xpos;
				}

				GameObject Temp;

				if (xpos != 0)
				{
					Temp.SetPositionX(xpos);
					Diff->TotalObjects++;

					if (hold_duration)
						Diff->TotalHolds++;
					else
						Diff->TotalNotes++;
				}
				else
				{
					/* Position 0 is a special X constant that will make the note invisible 
					as well as making it not emit any kind of judgment in-game. It's filler. */
					Temp.SetPositionX(0);
				}

				Temp.Assign(hold_duration, Diff->Measures.size(), (double)CurObj / SoSize);
				Msr.push_back(Temp);

			} // got a position
			CurObj++;
		} // foreach object in measure

		Diff->Measures.push_back(Msr);

	} // foreach measure

	Out->Difficulties.push_back(Diff); 
}
Song* NoteLoader::LoadObjectsFromFile(GString filename, GString prefix)
{
	std::ifstream filein;
	filein.open(filename.c_str(), std::ios::in);
	Song *Out = new Song();
	Difficulty *Diff = new Difficulty();

	if (!filein.is_open())
	{
		throw std::exception(Utility::Format("Unable to open %s for reading!", filename.c_str()).c_str());
	}

	Out->SongDirectory = prefix;

	// get lines separating with ; token
	GString line;
	while (filein)
	{
		std::getline(filein, line, ';'); 
		GString command = line.substr(0, line.find_first_of(":"));

#define OnCommand(x) if(command.find(#x)!=GString::npos)
		
		GString CommandContents = line.substr(line.find_first_of(":") + 1);

		// First, metadata.
		OnCommand(#NAME)
		{
			Out->SongName = CommandContents;
		}

		OnCommand(#AUTHOR)
		{
			Out->SongAuthor = CommandContents;
		}

		OnCommand(#MLEN)
		{
			std::stringstream str (CommandContents);
			str >> Out->MeasureLength;
		}

		// Then, Timing data.
		OnCommand(#BPM)
		{
			LoadTimingList(Diff->Timing, line);
		}

		OnCommand(#OFFSET)
		{
			std::stringstream str (CommandContents);
			str >> Diff->Offset;
			Diff->Offset += Configuration::GetConfigf("OffsetDC");
		}

		// Then, file info.
		OnCommand(#SONG)
		{
			Out->SongFilename = CommandContents;
		}

		OnCommand(#BACKGROUNDIMAGE)
		{
			Out->BackgroundFilename = CommandContents;
		}

		OnCommand(#LEADIN)
		{
			std::stringstream str (CommandContents);
			str >> Out->LeadInTime;
		}

		OnCommand(#SOUNDS)
		{
			vector<GString> SoundList;
			GString CmdLine = CommandContents;
			// Diff->SoundList = Utility::TokenSplit(CmdLine, ",");
		}

		// Then, the charts.
		OnCommand(#NOTES) // current command is notes?
		{
			LoadNotes(Out, Diff, line);			
			Diff = new Difficulty();
		}// command == #notes
#undef OnCommand
	}
	delete Diff; // There will always be an extra copy.

	// at this point the objects are sorted! by measure and within the measure, by fraction.
	Out->Process();
	return Out;
}