/**
 * @author Michał Sobiecki grupa J1I2
 * 
 * Praca Domowa ZPR - przykład użycia kontenera std::map
 * 
 * zabezpieczenie przed powieleniem zasobów oparte na std::map
 */

#include <string>
#include <map>
#include <iostream>

/**
 * Klasa zajmuje się udostępnianiem zasobów w postaci tekstur graficznych.
 * Jest to jedynie szkielet, który w rzeczywistości nie wczytuje tekstur,
 * jednak celem kodu jest pokazanie idei w jaki sposób można prosto i szybko zrealizować taki system.
 * W celu zaciągnięcia tego kodu do działania z rzeczywistymi teksturami wystarczy zamienić kod funkcji load() 
 * dodając np instrukcje z biblioteki DevIL oraz OpenGL, wykraczało to jednak poza ramy tego zadania.
 * Manager może być również użyty do zasobów dowolnego innego typu po niewielkich przeróbkach.
 */
class TextureManager {
public:
	/// Struktura danych przechowująca potrzebne informacje o wczytanej teksturze.
	struct Texture {
		/// ID_ tekstury zwracany przez sterownik karty graficznej po mapowaniu w pamięci
		unsigned int ID_;
		/// szerokość tekstury wyrażona w tekselach
		unsigned int width_;
		/// wysokość tekstury wyrażona w tekselach
		unsigned int height_;
		/// ilość bitów przypadających na jeden teksel
		unsigned int bpp_;
	};

private:
	/// Kontener asocjacyjny przechowujący informacje o wczytanych teksturach.
	/// Jako klucz danej tekstury przechowywana jest ścieżka do pliku z którego została wczytana,
	/// co zapewnia unikalność klucza.
	std::map<std::string, Texture> loaded_;

	// konstruktory zostały ukryte, ponieważ Manager realizowany jest jako singleton
	TextureManager() {}
	TextureManager(const TextureManager& m) {}
public:
	/// metoda zwraca instancję singletonu TextureManager
	static TextureManager& get() {
		static TextureManager TM;
		return TM;
	}

	/**
	 * Zadaniem funkcji jest ładowanie tekstury z pliku.
	 * W przypadku, gdy tekstura już raz była załadowana,
	 * w celu oszczędności pamięci karty graficznej,
	 * funkcja zwróci identyfikator tekstury załadowanej wcześniej.
	 * @param name ścieżka do pliku w którym znajduje się tekstura
	 * @return identyfikator tekstury
	 * @see Texture::ID_
	 */
	unsigned int load(const std::string& name);

	/**
	 * Sprawdza czy zasób został już załadowany
	 * @param name ścieżka do pliku w którym znajduje się tekstura
	 * @return true jeśli tekstura już jest załadowana
	 */
	bool isLoaded(const std::string& name);

	/**
	 * Zwalnia zasób (usuwa teksturę z mapy)
	 * W przypadku gdy tekstura nie istnieje nic nie robi.
	 * @param name ścieżka tekstury do usunięcia
	 */
	void free(const std::string& name);

	/**
	 * @return liczba załadowanych tekstur
	 */
	unsigned int numberOfResources();
};

unsigned int TextureManager::load(const std::string& name) {
	std::map<std::string, Texture>::iterator i;
	i = loaded_.find(name);
		
	if(i != loaded_.end())
		return i->second.ID_;	// tekstura juz raz została wczytana

	// sekcja wczytywania obrazka, można zrealizować np poprzez bibliotekę DevIL
	// tutaj ustawiane są przykładowe dane nie wczytywane z pliku.
	Texture tex;
	tex.width_ = 64;	// 64 texele szerokości
	tex.height_ = 64;	// 64 texele wysokości
	tex.bpp_ = 32;		// 32 bity na texel

	// następnie powinno nastąpić mapowanie tekstury do pamięci karty graficznej,
	// mapowanie takie zwraca (np. w OpenGL) identyfikator tekstury w postaci liczby całkowitej nieujemnej (GLuint)
	// identyfikatora można następnie użyć (glBind(<typ tekstury>, ID)) w celu określenia, że tekstura ma być rysowana na ekranie
	// dla ułatwienia proces ten jest zastąpiony zwykłą inkrementacją identyfikatora
	// każda nowo wczytana tekstura dostanie ID o jeden wyższy niż poprzednia, pierwsza dostanie 0.
	static unsigned int nextID = 0;
	tex.ID_ = nextID;
	nextID++;

	// dodanie tekstury do mapy
	loaded_[name] = tex;

	return tex.ID_;
}

bool TextureManager::isLoaded(const std::string& name) {
	// count zwraca ilosc elementow o kluczu podanym jako parametr (1 lub 0)
	if(loaded_.count(name) > 0)
		return true;

	return false;
}

void TextureManager::free(const std::string& name) {
	std::map<std::string, Texture>::iterator i;
	i = loaded_.find(name);
		
	if(i == loaded_.end())
		return;

	// usuniecie elementu wskazywanego przez iterator
	loaded_.erase(i);
}

unsigned int TextureManager::numberOfResources() {
	// std::map::size() zwraca ilosc elementow w mapie.
	return static_cast<unsigned int>(loaded_.size());
}

int main() {
	TextureManager& TM = TextureManager::get();

	std::cout << "TM.numberOfResources() = " << TM.numberOfResources() << std::endl;
	std::cout << "TM.load(\"tex1.jpg\") = " << TM.load("tex1.jpg") << std::endl;
	std::cout << "TM.load(\"tex2.jpg\") = " << TM.load("tex2.jpg") << std::endl;
	std::cout << "TM.load(\"tex1.jpg\") = " << TM.load("tex1.jpg") << std::endl;
	std::cout << "TM.load(\"tex2.jpg\") = " << TM.load("tex2.jpg") << std::endl;
	std::cout << "TM.load(\"tex3.jpg\") = " << TM.load("tex3.jpg") << std::endl;
	std::cout << "TM.load(\"tex4.jpg\") = " << TM.load("tex4.jpg") << std::endl;
	std::cout << "TM.load(\"tex4.jpg\") = " << TM.load("tex4.jpg") << std::endl;
	std::cout << "TM.load(\"tex2.jpg\") = " << TM.load("tex2.jpg") << std::endl;
	std::cout << "TM.numberOfResources() = " << TM.numberOfResources() << std::endl;
	std::cout << "TM.free(\"tex1.jpg\") = " << std::endl;
	TM.free("tex1.jpg");
	std::cout << "TM.numberOfResources() = " << TM.numberOfResources() << std::endl;
	std::cout << "TM.load(\"tex1.jpg\") = " << TM.load("tex1.jpg") << std::endl;
	if(TM.isLoaded("tex1.jpg"))
		std::cout << "tex1 is loaded\n";
	if(TM.isLoaded("tex5.jpg"))
		std::cout << "tex5 is loaded\n";

	return 0;
}

// WYNIK DZIALANIA PROGRAMU:

//TM.numberOfResources() = 0
//TM.load("tex1.jpg") = 0
//TM.load("tex2.jpg") = 1
//TM.load("tex1.jpg") = 0
//TM.load("tex2.jpg") = 1
//TM.load("tex3.jpg") = 2
//TM.load("tex4.jpg") = 3
//TM.load("tex4.jpg") = 3
//TM.load("tex2.jpg") = 1
//TM.numberOfResources() = 4
//TM.free("tex1.jpg") =
//TM.numberOfResources() = 3
//TM.load("tex1.jpg") = 4
//tex1 is loaded
