#include <fstream>
#include <set>
#define _COMMON_NO_THREADS
#include "common.h"
#include "Auth/MD5Hash.h"
#include "tools.h"
#include "MPQHelper.h"
#include "dbcfile.h"
#include "ADTFile.h"
#include "WDTFile.h"
#include "StuffExtract.h"
#include "DBCFieldData.h"
#include "MPQLocale.h"
#include "ProgressBar.h"
#include "../../Client/GUI/CM2MeshFileLoader.h"
#include "../../Client/GUI/CWMOMeshFileLoader.h"

int replaceSpaces (int i) { return i==(int)' ' ? (int)'_' : i; }

std::map<uint32,std::string> mapNames;

std::set<NameAndAlt> texNames;
std::set<NameAndAlt> modelNames;
std::set<NameAndAlt> wmoNames;
std::set<NameAndAlt> wmoGroupNames;
std::set<NameAndAlt> soundFileSet;
MPQHelper mpq;

// default config; SCPs are done always
bool doMaps=true, doSounds=false, doTextures=false, doWmos=false, doWmogroups=false, doModels=false, doMd5=true, doAutoclose=false;



int main(int argc, char *argv[])
{
    char input[200];
    printf("StuffExtract [version %u]\n",SE_VERSION);
    printf("Use -help or -? to display help about command line arguments and config.\n\n");
    ProcessCmdArgs(argc, argv);
    PrintConfig();
    if(!GetLocale())
    {
	    printf("Enter your locale (enUS, enGB, deDE, ...) or leave blank to autodetect: ");
	    fgets(input,sizeof(input),stdin);
	    char loc[5];
        input[strlen(input)-1] = 0;
	    memcpy(loc,input,4); loc[4]=0;
	    SetLocale(loc);
    }
	if(GetLocale() && FileExists(std::string("Data/")+GetLocale()+"/locale-"+GetLocale()+".MPQ"))
	{
		printf("Locale \"%s\" seems valid, starting conversion...\n",GetLocale());
        CreateDir("extractedstuff");
        CreateDir("extractedstuff/data");
		mpq.Init();
        ConvertDBC();
        if(doMaps) ExtractMaps();
        if(doTextures || doModels || doWmos || doWmogroups) ExtractMapDependencies();
        if(doSounds) ExtractSoundFiles();
		//...
		if (!doAutoclose)
            printf("\n -- finished, press enter to exit --\n");
	}
	else
	{
		printf("ERROR: Invalid locale \"%s\"! Press Enter to exit...\n",GetLocale());
	}
    if (!doAutoclose)
        fgets(input,sizeof(input),stdin);

    //while(true);
    return 0;
}

void ProcessCmdArgs(int argc, char *argv[])
{
    bool on,help=false;
    char *what;
    for(int i = 1; i < argc; i++)
    {
        if(strlen(argv[i]) > 1)
        {
            if(argv[i][0] == '-')
                on = false;
            else if(argv[i][0] == '+')
                on = true;
            else if(!stricmp(argv[i],"/?") || !stricmp(argv[i],"/help"))
            {
                help = true;
                break;
            }
            else
            {
                printf("Incorrect cmd arg: \"%s\"\n",argv[i]);
                continue;
            }

            what = argv[i]+1; // skip first byte (+/-)
            if     (!stricmp(what,"maps"))        doMaps = on;
            else if(!stricmp(what,"textures"))    doTextures = on;
            else if(!stricmp(what,"wmos"))        doWmos = on;
            else if(!stricmp(what,"wmogroups"))   doWmogroups = on;
            else if(!stricmp(what,"models"))      doModels = on;
            else if(!stricmp(what,"sounds"))      doSounds = on;
            else if(!stricmp(what,"md5"))         doMd5 = on;
            else if(!stricmp(what,"autoclose"))   doAutoclose = on;
            // autodetect or use given locale.   + or - as arg start doesnt matter here
            else if(!strnicmp(what,"locale:",7))
            {
                if(!stricmp(what+7,"auto"))
                    SetLocale(NULL);
                else
                    SetLocale(what+7);
            }
            else if(!stricmp(what,"?") || !stricmp(what,"help"))
            {
                help = true;
                break;
            }
            else
            {
                printf("Unknown cmd arg: \"%s\"\n",what);
            }
        }
    }
    // fix up wrong configs. not necessary but better for display.
    // TODO: as soon as M2 model or WMO reading is done, extract those textures to, but independent from maps!!
    if(!doMaps)
    {
        doWmos = false;
    }
    if(!doWmos)
    {
        doWmogroups = false;
    }

    if(help)
    {
        PrintHelp();
        printf("\n\n- Press any key to exit -\n");
        getchar();
        exit(0);
    }
}

void PrintConfig(void)
{
    printf("config: Do maps:      %s\n",doMaps?"yes":"no");
    printf("config: Do textures:  %s\n",doTextures?"yes":"no");
    printf("config: Do wmos:      %s\n",doWmos?"yes":"no");
    printf("config: Do wmogroups: %s\n",doWmogroups?"yes":"no");
    printf("config: Do models:    %s\n",doModels?"yes":"no");
    printf("config: Do sounds:    %s\n",doSounds?"yes":"no");
    printf("config: Calc md5:     %s\n",doMd5?"yes":"no");
    printf("config: Autoclose:    %s\n",doAutoclose?"yes":"no");
}

void PrintHelp(void)
{
    printf("Usage information:\n\n");
    printf("Use + or - to turn a feature on or off.\n");
    printf("Features are:\n");
    printf("maps      - map extraction\n");
    printf("textures  - extract textures\n");
    printf("wmos      - extract map WMOs (requires maps extraction)\n");
    printf("wmogroups - extract map WMO group files (requires maps and wmos extraction)\n");
    printf("models    - extract models\n");
    printf("sounds    - extract sound files (wav/mp3)\n");
    printf("md5       - write MD5 checksum lists of extracted files\n");
    printf("autoclose - close program when done\n");
    printf("\n");
    printf("Use -locale:xxXX to set a locale. If you don't use this, you will be asked.\n");
    printf("Use -locale:auto to autodetect currently used locale.\n");
    printf("\n");
    printf("Examples:\n");
    printf("stuffextract +sounds +md5 -maps +autoclose -locale:enGB\n");
    printf("stuffextract +md5 -wmos -sounds -locale:auto -autoclose\n");
    printf("\nDefault is: +maps -sounds -textures -wmos -models +md5 -autoclose\n");
}


// be careful using this, that you supply correct format string
std::string AutoGetDataString(DBCFile::Iterator& it, const char* format, uint32 field, bool skip_null = true)
{
    if(format[field]=='i')
    {
        if((*it).getInt(field) == 0 && skip_null)
            return ""; // do not explicitly write int fields that are 0
        std::stringstream s;
        s << (*it).getInt(field);
        return s.str();
    }
    else if(format[field]=='f')
    {
        if((*it).getFloat(field) == 0 && skip_null)
            return ""; // do not explicitly write float fields that are 0
        std::stringstream s;
        s << (*it).getFloat(field);
        return s.str();
    }
    else if(format[field]=='c')
    {
        if((*it).getUChar(field) == 0 && skip_null)
            return ""; // do not explicitly write float fields that are 0
        std::stringstream s;
        s << (int)(*it).getUChar(field);
        return s.str();
    }
    else if(format[field]=='s' && (*it).getUInt(field))
    {
        return (*it).getString(field);
    }

    return "";
}


// output a formatted scp file
void OutSCP(const char *fn, SCPStorageMap& scp, std::string dbName="")
{
    std::fstream f;
    f.open(fn, std::ios_base::out);
    if(f.is_open())
    {
        if(dbName.length())
        {
            f << "#dbname=" << dbName << "\n";
        }
        for(SCPStorageMap::iterator mi = scp.begin(); mi != scp.end(); mi++)
        {
            f << "[" << mi->first << "]\n";
            for(std::list<std::string>::iterator li = mi->second.begin(); li != mi->second.end(); li++)
            {
                f << *li << "\n";
            }
            f << "\n";
        }
        f.close();
    }
    else
    {
        printf("OutSCP: unable to write '%s'\n",fn);
    }
}

void OutMD5(const char *path, MD5FileMap& fm)
{
    if(!doMd5)
        return;
    std::string fullname(path);
    fullname += "/md5.txt";
    printf("Writing MD5 file checksums to '%s'\n",fullname.c_str());
    std::fstream fh;
    fh.open(fullname.c_str(), std::ios_base::out);
    if(fh.is_open())
    {
        for(MD5FileMap::iterator i = fm.begin(); i != fm.end(); i++)
        {
            fh << i->first << "|" << toHexDump(i->second,MD5_DIGEST_LENGTH,false) << std::endl; // write file content
            delete [] i->second; // and delete previously allocated memory
        }
        fh.close();
    }
    else
    {
        printf("Couldn't output MD5 list to '%s'\n",fullname.c_str());
    }
}




bool ConvertDBC(void)
{
    std::map<uint8,std::string> racemap; // needed to extract other dbc files correctly
    std::map<uint8,uint32> classmask; //from CharBaseInfo.dbc
    SCPStorageMap EmoteDataStorage,RaceDataStorage,SoundDataStorage,MapDataStorage,ZoneDataStorage,ItemDisplayInfoStorage,
        CreatureModelStorage,CreatureDisplayInfoStorage,NPCSoundStorage,CharSectionStorage, GameObjectDisplayInfoStorage, ChrBaseInfoStorage, LoadingScreenStorage; // will store the converted data from dbc files
    DBCFile EmotesText,EmotesTextData,EmotesTextSound,ChrRaces,SoundEntries,Map,AreaTable,ItemDisplayInfo,
        CreatureModelData,CreatureDisplayInfo,NPCSounds,CharSections,GameObjectDisplayInfo, ChrBaseInfo, LoadingScreen;
    printf("Opening DBC archive...\n");


    printf("Opening DBC files...\n");
    EmotesText.openmem(mpq.ExtractFile("DBFilesClient\\EmotesText.dbc"));
    EmotesTextData.openmem(mpq.ExtractFile("DBFilesClient\\EmotesTextData.dbc"));
    EmotesTextSound.openmem(mpq.ExtractFile("DBFilesClient\\EmotesTextSound.dbc"));
    ChrRaces.openmem(mpq.ExtractFile("DBFilesClient\\ChrRaces.dbc"));
    SoundEntries.openmem(mpq.ExtractFile("DBFilesClient\\SoundEntries.dbc"));
    Map.openmem(mpq.ExtractFile("DBFilesClient\\Map.dbc"));
    AreaTable.openmem(mpq.ExtractFile("DBFilesClient\\AreaTable.dbc"));
    ItemDisplayInfo.openmem(mpq.ExtractFile("DBFilesClient\\ItemDisplayInfo.dbc"));
    CreatureModelData.openmem(mpq.ExtractFile("DBFilesClient\\CreatureModelData.dbc"));
    CreatureDisplayInfo.openmem(mpq.ExtractFile("DBFilesClient\\CreatureDisplayInfo.dbc"));
    GameObjectDisplayInfo.openmem(mpq.ExtractFile("DBFilesClient\\GameObjectDisplayInfo.dbc"));
    NPCSounds.openmem(mpq.ExtractFile("DBFilesClient\\NPCSounds.dbc"));
    CharSections.openmem(mpq.ExtractFile("DBFilesClient\\CharSections.dbc"));
    ChrBaseInfo.openmem(mpq.ExtractFile("DBFilesClient\\CharBaseInfo.dbc"));
    LoadingScreen.openmem(mpq.ExtractFile("DBFilesClient\\LoadingScreens.dbc"));
    //...
    printf("DBC files opened.\n");
    //...
    printf("Reading data: chrbaseinfo..");
    for(DBCFile::Iterator it = ChrBaseInfo.begin(); it != ChrBaseInfo.end(); ++it)
    {
        uint32 race = (uint32)(*it).getUChar(CBI_RACE);
        uint32 cclass = (uint32)(*it).getUChar(CBI_CLASS);
        classmask[race] |= 1<<cclass;

    }

    printf("races..");
    /*for(DBCFile::Iterator it = ChrRaces.begin(); it != ChrRaces.end(); ++it)
    {
        uint32 id = (*it).getUInt(CHRRACES_RACEID);
        racemap[id] = (*it).getString(CHRRACES_NAME_GENERAL); // for later use
        for(uint32 field=CHRRACES_RACEID; field < CHARRACES_END; field++)
        {
            if(strlen(ChrRacesFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,ChrRacesFormat,field);
				if(!value.empty())
					RaceDataStorage[id].push_back(std::string(ChrRacesFieldNames[field]).append("=").append(value));
            }
        }
        if (doModels && classmask[id]) // skip nonplayable races
        {
            // corpse models

            modelNames.insert(NameAndAlt("World\\Generic\\PassiveDoodads\\DeathSkeletons\\" + racemap[id] + "MaleDeathSkeleton.m2"));
            modelNames.insert(NameAndAlt("World\\Generic\\PassiveDoodads\\DeathSkeletons\\" + racemap[id] + "FemaleDeathSkeleton.m2"));
        }

        std::stringstream temp;
        temp << classmask[id];
        RaceDataStorage[id].push_back(std::string("classmask").append("=").append(temp.str()));
    }*/

    printf("emotes..");
    for(DBCFile::Iterator it = EmotesText.begin(); it != EmotesText.end(); ++it)
    {
        uint32 em = (*it).getUInt(EMOTESTEXT_EMOTE_ID);
        EmoteDataStorage[em].push_back(std::string("name=") + (*it).getString(EMOTESTEXT_EMOTE_STRING));
        EmoteDataStorage[em].push_back(std::string("anim=") + toString( (*it).getUInt(EMOTESTEXT_ANIM)) );
        for(uint32 field=EMOTESTEXT_EMOTE_ID; field<EMOTESTEXT_END;field++)
        {
            if((*it).getInt(field) && strlen(EmotesTextFieldNames[field]))
            {
                uint32 textid;
                std::string fname;
                for(DBCFile::Iterator ix = EmotesTextData.begin(); ix != EmotesTextData.end(); ++ix)
                {
                    textid = (*ix).getUInt(EMOTESTEXTDATA_TEXTID);
                    if(textid == (*it).getUInt(field))
                    {
                        fname = EmotesTextFieldNames[field];
						for(uint8 stringpos=EMOTESTEXTDATA_STRING1; stringpos<=EMOTESTEXTDATA_STRING8; stringpos++) // we have 8 locales, so...
						{
							if((*ix).getInt(stringpos)) // find out which field is used, 0 if not used
							{
                                std::string tx = (*ix).getString(stringpos);
                                uint32 fpos=0;
                                // the following block replaces %x$s (where x is a number) with %s
                                do
                                {
                                    if(fpos+4 < tx.length() && tx[fpos]=='%' && tx[fpos+2]=='$' && tx[fpos+3]=='s' && isdigit(tx[fpos+1]))
                                    {
                                        tx.erase(fpos,4);
                                        tx.insert(fpos,"%s");
                                    }
                                    fpos++;
                                } while(fpos < tx.length());
								EmoteDataStorage[em].push_back( fname + "=" + tx );
								break;
							}
						}
						break;
                    }
                }
            }
        }
        for(DBCFile::Iterator is = EmotesTextSound.begin(); is != EmotesTextSound.end(); ++is)
        {
            if(em == (*is).getUInt(EMOTESTEXTSOUND_EMOTEID))
            {
                std::string record = "Sound";
                record += racemap[ (*is).getUInt(EMOTESTEXTSOUND_RACE) ];
                record += ((*is).getUInt(EMOTESTEXTSOUND_ISFEMALE) ? "Female" : "Male");
                record += "=";
                record += toString( (*is).getUInt(EMOTESTEXTSOUND_SOUNDID) );
                EmoteDataStorage[em].push_back(record);
            }
        }
    }

    printf("sound entries..");
    for(DBCFile::Iterator it = SoundEntries.begin(); it != SoundEntries.end(); ++it)
    {
        std::string path = (*it).getString(SOUNDENTRY_PATH); // required to fill up the filename storage
        path += "\\"; // use backslash because mpq uses backslash too
        uint32 id = (*it).getUInt(SOUNDENTRY_SOUNDID);
        for(uint32 field=SOUNDENTRY_SOUNDID; field < SOUNDENTRY_END; field++)
        {
            if(strlen(SoundEntriesFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,SoundEntriesFormat,field);
                if(value.size()) // only store if a file exists in that field
                {
                    SoundDataStorage[id].push_back(std::string(SoundEntriesFieldNames[field]) + "=" + value);

                    // fill up file storage. not necessary if we dont want to extract sounds
                    if(doSounds && field >= SOUNDENTRY_FILE_1 && field <= SOUNDENTRY_FILE_10)
                        soundFileSet.insert(NameAndAlt(path + value));
                }
            }
        }
    }

    printf("map info..");
    /*for(DBCFile::Iterator it = Map.begin(); it != Map.end(); ++it)
    {
        mapNames[it->getInt(MAP_ID)] = it->getString(MAP_NAME_GENERAL);
        uint32 id = it->getUInt(MAP_ID);
        for(uint32 field=MAP_ID; field < MAP_END; field++)
        {
            if(strlen(MapFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,MapFormat,field);
                if(value.size()) // only store if not null
                    MapDataStorage[id].push_back(std::string(MapFieldNames[field]) + "=" + value);
            }
        }
    }*/

    printf("loadingscreen..");
    for(DBCFile::Iterator it = LoadingScreen.begin(); it != LoadingScreen.end(); ++it)
    {
        uint32 id = it->getUInt(LOADINGSCREEN_ID);
        for(uint32 field=LOADINGSCREEN_ID; field < LOADINGSCREEN_END; field++)
        {
            if(strlen(LoadingScreenFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,LoadingScreenFormat,field);
                if(value.size()) // only store if not null
                    LoadingScreenStorage[id].push_back(std::string(LoadingScreenFieldNames[field]) + "=" + value);
            }
        }
    }

    printf("zonedata..");
    for(DBCFile::Iterator it = AreaTable.begin(); it != AreaTable.end(); ++it)
    {
        uint32 id = it->getUInt(MAP_ID);
        for(uint32 field=AREATABLE_ID; field < AREATABLE_END; field++)
        {
            if(strlen(AreaTableFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,AreaTableFormat,field,false);
                if(value.size()) // only store if not null
                    ZoneDataStorage[id].push_back(std::string(AreaTableFieldNames[field]) + "=" + value);
            }
        }
    }

    printf("itemdisplayinfo..");
    for(DBCFile::Iterator it = ItemDisplayInfo.begin(); it != ItemDisplayInfo.end(); ++it)
    {
        uint32 id = it->getUInt(ITEMDISPLAYINFO_ID);
        for(uint32 field=ITEMDISPLAYINFO_ID; field < ITEMDISPLAYINFO_END; field++)
        {
            if(strlen(ItemDisplayInfoFieldNames[field]))
            {
                // TODO: need to get
                std::string value = AutoGetDataString(it,ItemDisplayInfoFormat,field);
                if(value.size()) // only store if not null
                    ItemDisplayInfoStorage[id].push_back(std::string(ItemDisplayInfoFieldNames[field]) + "=" + value);
            }
        }
    }

    printf("creaturemodeldata..");
    for(DBCFile::Iterator it = CreatureModelData.begin(); it != CreatureModelData.end(); ++it)
    {
        uint32 id = it->getUInt(CREATUREMODELDATA_ID);
        for(uint32 field=CREATUREMODELDATA_ID; field < CREATUREMODELDATA_END; field++)
        {
            if(strlen(CreatureModelDataFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,CreatureModelDataFormat,field);
                if(value.size()) // only store if not null
                {
                    if(doModels)
                        modelNames.insert(NameAndAlt(value)); // we need to extract model later, store it
                    std::string fn = _PathToFileName(value);
                    if(stricmp(fn.c_str()+fn.length()-4, "mdx"))
                    {
                        fn = fn.substr(0,fn.length()-2) + "2";
                        value = value.substr(0,value.length()-2) + "2";
                    }
                    CreatureModelStorage[id].push_back(std::string(CreatureModelDataFieldNames[field]) + "=" + fn);
                    CreatureModelStorage[id].push_back("mpqfilename=" + value);
                }
            }
        }
    }

    printf("creaturedisplayinfo..");
    for(DBCFile::Iterator it = CreatureDisplayInfo.begin(); it != CreatureDisplayInfo.end(); ++it)
    {
        uint32 id = it->getUInt(CREATUREDISPLAYINFO_ID);
        uint32 modelid = it->getUInt(CREATUREDISPLAYINFO_MODEL);

        for(uint32 field=CREATUREDISPLAYINFO_ID; field < CREATUREDISPLAYINFO_END; field++)
        {
            if(strlen(CreatureDisplayInfoFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,CreatureDisplayInfoFormat,field);
                if(value.size()) // only store if not null
                {
                    if(doTextures && field >= CREATUREDISPLAYINFO_NAME1 && field <= CREATUREDISPLAYINFO_NAME3)
                    {
                        // lookup for model path
                        DBCFile::Iterator itm = CreatureModelData.begin();
                        for(; itm != CreatureDisplayInfo.end() && itm->getUInt(CREATUREMODELDATA_ID) != modelid;) ++itm;

                        std::string str = itm->getString(CREATUREMODELDATA_FILE);
                        uint32 pathend = str.find_last_of("/\\");
                        if(pathend != std::string::npos) // replace model with texture name
                            str = str.substr(0, pathend);
                        str += "\\";
                        str += value;
                        str += ".blp";
                        texNames.insert(NameAndAlt(str));

                        value = NormalizeFilename(str);
                    }

                    CreatureDisplayInfoStorage[id].push_back(std::string(CreatureDisplayInfoFieldNames[field]) + "=" + value);
                }
            }
        }
    }

    printf("gameobjectdisplayinfo..");
    for(DBCFile::Iterator it = GameObjectDisplayInfo.begin(); it != GameObjectDisplayInfo.end(); ++it)
    {
        uint32 id = it->getUInt(GAMEOBJECTDISPLAYINFO_ID);

        for(uint32 field=GAMEOBJECTDISPLAYINFO_ID; field < GAMEOBJECTDISPLAYINFO_END; field++)
        {
            if(strlen(GameObjectDisplayInfoFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,GameObjectDisplayInfoFormat,field);
                if(value.size()) // only store if not null
                {
                    // TODO: add check for wmo model files ?
                    if(doModels && stricmp(value.c_str()+value.length()-4,".wmo"))
                        modelNames.insert(NameAndAlt(value)); // we need to extract model later, store it
                    else if (doWmos && !stricmp(value.c_str()+value.length()-4,".wmo"))
                        wmoNames.insert(NameAndAlt(value)); //this is a WMO

                    //Interestingly, some of the files referenced here have MDL extension - WTF?
                    std::string fn = _PathToFileName(value);
                    if(!stricmp(fn.c_str()+fn.length()-3, "mdx") || !stricmp(fn.c_str()+fn.length()-3, "mdl"))
                    {
                        fn = fn.substr(0,fn.length()-2) + "2";
                        value = value.substr(0,value.length()-2) + "2";
                    }
                    else
                        printf("This should be a WMO: %s\n",fn.c_str());
                    GameObjectDisplayInfoStorage[id].push_back(std::string(GameObjectDisplayInfoFieldNames[field]) + "=" + fn);
                    GameObjectDisplayInfoStorage[id].push_back("mpqfilename=" + value);

                    std::string texture = value.substr(0,value.length()-3) + "blp";
                    if (mpq.FileExists((char*)texture.c_str()))
                    {
                        if(doTextures)
                            texNames.insert(NameAndAlt(texture));
                        GameObjectDisplayInfoStorage[id].push_back("texture=" + texture);
                    }
                }
            }
        }
    }

    printf("npcsounds..");
    for(DBCFile::Iterator it = NPCSounds.begin(); it != NPCSounds.end(); ++it)
    {
        uint32 id = it->getUInt(NPCSOUNDS_ID);
        for(uint32 field=NPCSOUNDS_ID; field < NPCSOUNDS_END; field++)
        {
            if(strlen(NPCSoundsFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,NPCSoundsFormat,field);
                if(value.size()) // only store if not null
                    NPCSoundStorage[id].push_back(std::string(NPCSoundsFieldNames[field]) + "=" + value);
            }
        }
    }

    printf("charsections..");
    for(DBCFile::Iterator it = CharSections.begin(); it != CharSections.end(); ++it)
    {
        uint32 id = it->getUInt(CHARSECTIONS_ID);
        for(uint32 field=CHARSECTIONS_ID; field < CHARSECTIONS_END; field++)
        {
            if(strlen(CharSectionsFieldNames[field]))
            {
                std::string value = AutoGetDataString(it,CharSectionsFormat,field);
                if(value.size()) // only store if not null
                {
                    // ok we have a little problem here:
                    // some textures used for different races have the same file name, but we are storing them all
                    // in one directory. Texture path format is: "Character\<race>\<texture>
                    // so we have to use good names to store all textures without overwriting each other
                    if(field >= CHARSECTIONS_TEXTURE1 && field <= CHARSECTIONS_TEXTURE3)
                    {
                        /*char buf[100];
                        sprintf(buf,"charsection_%u_%u_%u_%u_%u_%u.blp",
                            it->getUInt(CHARSECTIONS_RACE_ID),
                            it->getUInt(CHARSECTIONS_GENDER),
                            it->getUInt(CHARSECTIONS_TYPE),
                            it->getUInt(CHARSECTIONS_SECTION),
                            it->getUInt(CHARSECTIONS_COLOR),
                            field - CHARSECTIONS_TEXTURE1); // texture ID */
                        texNames.insert(NameAndAlt(value));
                    }
                    CharSectionStorage[id].push_back(std::string(CharSectionsFieldNames[field]) + "=" + NormalizeFilename(value));
                }
            }
        }
    }


    //...
    printf("DONE!\n");
    //...

    CreateDir("extractedstuff/data/scp");

    printf("Writing SCP files:\n");
    printf("emote.."); OutSCP(SCPDIR "/emote.scp",EmoteDataStorage, "emote");
    printf("race.."); OutSCP(SCPDIR "/race.scp",RaceDataStorage, "race");
    printf("sound.."); OutSCP(SCPDIR "/sound.scp",SoundDataStorage, "sound");
    printf("map.."); OutSCP(SCPDIR "/map.scp",MapDataStorage, "map");
    printf("loadingscreens.."); OutSCP(SCPDIR "/loadingscreens.scp",LoadingScreenStorage, "loadingscreens");
    printf("area.."); OutSCP(SCPDIR "/zone.scp",ZoneDataStorage, "zone");
    printf("itemdisplayinfo.."); OutSCP(SCPDIR "/itemdisplayinfo.scp",ItemDisplayInfoStorage, "itemdisplayinfo");
    printf("creaturemodeldata.."); OutSCP(SCPDIR "/creaturemodeldata.scp",CreatureModelStorage,"creaturemodeldata");
    printf("creaturedisplayinfo.."); OutSCP(SCPDIR "/creaturedisplayinfo.scp",CreatureDisplayInfoStorage,"creaturedisplayinfo");
    printf("gameobjectdisplayinfo.."); OutSCP(SCPDIR "/gameobjectdisplayinfo.scp",GameObjectDisplayInfoStorage,"gameobjectdisplayinfo");
    printf("npcsound.."); OutSCP(SCPDIR "/npcsound.scp",NPCSoundStorage,"npcsound");
    printf("charsections.."); OutSCP(SCPDIR "/charsections.scp",CharSectionStorage,"charsections");
    //...
    printf("DONE!\n");

	// wait for all container destructors to finish
	printf("DBC files converted, cleaning up...\n");

    return true;
}

void ExtractMaps(void)
{
    printf("\nExtracting maps...\n");
    char namebuf[200];
    char outbuf[2000];
    uint32 extr,extrtotal=0;
    MD5FileMap md5map;
    CreateDir("extractedstuff/data/maps");
    for(std::map<uint32,std::string>::iterator it = mapNames.begin(); it != mapNames.end(); it++)
    {
        // extract the WDT file that stores tile information
        char wdt_name[300], wdt_out[300];
        sprintf(wdt_name,"World\\Maps\\%s\\%s.wdt",it->second.c_str(),it->second.c_str());
        sprintf(wdt_out,MAPSDIR"/%lu.wdt",it->first);
        const ByteBuffer& wdt_bb = mpq.ExtractFile(wdt_name);
        std::fstream wdt_fh;
        wdt_fh.open(wdt_out, std::ios_base::out|std::ios_base::binary);
        if(!wdt_fh.is_open())
        {
            printf("\nERROR: Map extraction failed: could not save file %s\n",wdt_out);
            return;
        }
        if (wdt_bb.size())
            wdt_fh.write((char*)wdt_bb.contents(),wdt_bb.size());
        wdt_fh.close();

        printf("Extracted WDT '%s'\n",wdt_name);

        // then extract all ADT files
        extr=0;
        for(uint32 x=0; x<64; x++)
        {
            for(uint32 y=0;y<64; y++)
            {
                uint32 olddeps;
                uint32 depdiff;
                sprintf(namebuf,"World\\Maps\\%s\\%s_%lu_%lu.adt",it->second.c_str(),it->second.c_str(),x,y);
                sprintf(outbuf,MAPSDIR"/%lu_%lu_%lu.adt",it->first,x,y);
                if(mpq.FileExists(namebuf))
                {
                    const ByteBuffer& bb = mpq.ExtractFile(namebuf);
                    if(bb.size())
                    {
                        std::fstream fh;
                        //printf("Extracting map [ %s ]\n",outbuf);
                        fh.open(outbuf, std::ios_base::out|std::ios_base::binary);
                        if(!fh.is_open())
                        {
                            printf("\nERROR: Map extraction failed: could not save file %s\n",outbuf);
                            return;
                        }
                        fh.write((char*)bb.contents(),bb.size());
                        fh.flush();
                        fh.close();
                        olddeps = texNames.size() + modelNames.size() + wmoNames.size();

                        if(doTextures) ADT_FillTextureData(bb.contents(),texNames);
                        if(doModels)   ADT_FillModelData(bb.contents(),modelNames);
                        if(doWmos)     ADT_FillWMOData(bb.contents(),wmoNames);

                        depdiff = texNames.size() + modelNames.size() + wmoNames.size() - olddeps;
                        if(doMd5)
                        {
                            MD5Hash h;
                            h.Update((uint8*)bb.contents(), bb.size());
                            h.Finalize();
                            uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH];
                            md5map[_PathToFileName(outbuf)] = md5ptr;
                            memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH);
                        }
                        extr++;
                        printf("[%lu:%lu] %s; %lu new deps.\n",extr,it->first,namebuf,depdiff);
                    }
                }
            }
        }
        extrtotal+=extr;
        printf("\n");
    }

    printf("\nDONE - %lu maps extracted, %u total dependencies.\n",extrtotal, texNames.size() + modelNames.size() + wmoNames.size());
    OutMD5(MAPSDIR,md5map);
}

void ExtractMapDependencies(void)
{
    barGoLink *bar;
    printf("\nExtracting map dependencies...\n\n");
    printf("- Preparing to read MPQ arcives...\n");
    std::string path = "extractedstuff/data";
    std::string pathtex = path + "/texture";
    std::string pathmodel = path + "/model";
    std::string pathwmo = path + "/wmo";
    std::string mpqfn,realfn,altfn;
    MD5FileMap md5Tex, md5Wmo, md5Wmogroup, md5Model;
    CreateDir(pathtex.c_str());
    CreateDir(pathmodel.c_str());
    CreateDir(pathwmo.c_str());
    uint32 wmosdone=0,texdone=0,mdone=0;

    if(doWmos)
    {
        printf("Extracting %u WMOS...\n",wmoNames.size());

        bar = new barGoLink(wmoNames.size(),true);
        for(std::set<NameAndAlt>::iterator i = wmoNames.begin(); i != wmoNames.end(); i++)
        {
            bar->step();
            mpqfn = i->name;
            altfn = i->alt;
            if(altfn.empty())
                altfn = mpqfn;
            if(!mpq.FileExists((char*)mpqfn.c_str()))
                continue;
            realfn = pathwmo + "/" + NormalizeFilename(_PathToFileName(altfn));
            std::fstream fh;
            fh.open(realfn.c_str(),std::ios_base::out | std::ios_base::binary);
            if(fh.is_open())
            {
                const ByteBuffer& bb = mpq.ExtractFile((char*)mpqfn.c_str());
                fh.write((const char*)bb.contents(),bb.size());
                //Extract number of group files, Texture file names and M2s from WMO
                if(doWmogroups || doTextures || doModels) WMO_Parse_Data(bb,mpqfn.c_str(),doWmogroups,doTextures,doModels);
                if(doMd5)
                {
                    MD5Hash h;
                    h.Update((uint8*)bb.contents(), bb.size());
                    h.Finalize();
                    uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH];
                    md5Wmo[_PathToFileName(realfn)] = md5ptr;
                    memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH);
                }
                wmosdone++;
            }
            else
                printf("Could not write WMO %s\n",realfn.c_str());
            fh.close();
        }
        printf("\n");
        if(wmoNames.size())
            OutMD5((char*)pathwmo.c_str(),md5Wmo);
        delete bar;
    }

    if(doWmogroups)
    {
        printf("Extracting WMO Group Files...\n");
        bar = new barGoLink(wmoGroupNames.size(),true);
        for(std::set<NameAndAlt>::iterator i = wmoGroupNames.begin(); i != wmoGroupNames.end(); i++)
        {
            bar->step();
            mpqfn = i->name;
            altfn = i->alt;
            if(altfn.empty())
                altfn = mpqfn;
            if(!mpq.FileExists((char*)mpqfn.c_str()))
                continue;
            realfn = pathwmo + "/" + NormalizeFilename(_PathToFileName(altfn));
            std::fstream fh;
            fh.open(realfn.c_str(),std::ios_base::out | std::ios_base::binary);
            if(fh.is_open())
            {
                const ByteBuffer& bb = mpq.ExtractFile((char*)mpqfn.c_str());
                fh.write((const char*)bb.contents(),bb.size());
                if(doMd5)
                {
                    MD5Hash h;
                    h.Update((uint8*)bb.contents(), bb.size());
                    h.Finalize();
                    uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH];
                    md5Wmogroup[_PathToFileName(realfn)] = md5ptr;
                    memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH);
                }
                wmosdone++;
            }
            else
                printf("Could not write WMO %s\n",realfn.c_str());
            fh.close();
        }
        printf("\n");
        if(wmoGroupNames.size())
            OutMD5((char*)pathwmo.c_str(),md5Wmogroup);
        delete bar;
    }




    if(doModels)
    {
        printf("Extracting models...\n");
        bar = new barGoLink(modelNames.size(),true);
        for(std::set<NameAndAlt>::iterator i = modelNames.begin(); i != modelNames.end(); i++)
        {
            bar->step();
            mpqfn = i->name;
            // no idea what bliz intended by this. the ADT files refer to .mdx models,
            // however there are only .m2 files in the MPQ archives.
            // so we just need to check if there is a .m2 file instead of the .mdx file, and load that one.
            if(!mpq.FileExists((char*)mpqfn.c_str()))
            {
                std::string alt = mpqfn.substr(0,mpqfn.length()-2) + "2";
                if(!mpq.FileExists((char*)alt.c_str()))
                {
                    printf("Failed to extract model: '%s'\n",alt.c_str());
                    continue;
                }
                else
                {
                    mpqfn = alt;
                }
            }
            altfn = i->alt;
            if(altfn.empty())
                altfn = mpqfn;
            realfn = pathmodel + "/" + NormalizeFilename(_PathToFileName(altfn));
            std::fstream fh;
            fh.open(realfn.c_str(),std::ios_base::out | std::ios_base::binary);
            if(fh.is_open())
            {
                ByteBuffer bb = mpq.ExtractFile((char*)mpqfn.c_str());
                fh.write((const char*)bb.contents(),bb.size());

                if(doMd5)
                {
                    MD5Hash h;
                    h.Update((uint8*)bb.contents(), bb.size());
                    h.Finalize();
                    uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH];
                    md5Model[_PathToFileName(realfn)] = md5ptr;
                    memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH);
                }
                mdone++;

                // model ok, now extract skins
                // for now first skin is all what we need
                std::string copy = mpqfn;
                std::transform(copy.begin(), copy.end(), copy.begin(), tolower);
                if (copy.find(".wmo") == std::string::npos)
                {
                    if (doTextures)
                        FetchTexturesFromModel(bb);

                    std::string skin = mpqfn.substr(0,mpqfn.length()-3) + "00.skin";
                    std::string skinrealfn = pathmodel + "/" + NormalizeFilename(_PathToFileName(skin));
                    if (mpq.FileExists((char*)skin.c_str()))
                    {
                        std::fstream fhs;
                        fhs.open(skinrealfn.c_str(),std::ios_base::out | std::ios_base::binary);
                        if(fhs.is_open())
                        {
                            ByteBuffer bbs = mpq.ExtractFile((char*)skin.c_str());
                            fhs.write((const char*)bbs.contents(),bbs.size());
                        }
                        else
                            printf("Could not write skin %s\n",skinrealfn.c_str());

                        fhs.close();
                    }
                    else
                        printf("Could not open skin %s\n",skin.c_str());
                }
            }
            else
                printf("Could not write model %s\n",realfn.c_str());
            fh.close();
        }
        printf("\n");
        if(modelNames.size())
            OutMD5((char*)pathmodel.c_str(),md5Model);
        delete bar;
    }

    if(doTextures)
    {
        printf("Extracting textures...\n");
        bar = new barGoLink(texNames.size(), true);
        for(std::set<NameAndAlt>::iterator i = texNames.begin(); i != texNames.end(); i++)
        {
            bar->step();
            mpqfn = i->name;
            altfn = i->alt;
            if(altfn.empty())
                altfn = mpqfn;
            if(!mpq.FileExists((char*)mpqfn.c_str()))
                continue;

            // prepare lowercased and "underlined" path for file
            std::string copy = NormalizeFilename(mpqfn);
            if (copy.find_first_of("/") != std::string::npos)
            {
                std::string copy2 = copy.c_str();
                char* tok = strtok((char*)copy2.c_str(),"/");
                std::string fullpath = pathtex;
                while (tok && !strstr(tok, ".blp"))
                {
                    fullpath += "/";
                    fullpath += tok;
                    CreateDir(fullpath.c_str());
                    tok = strtok(NULL, "/");
                }
            }

            realfn = pathtex + "/" + copy; //_PathToFileName(altfn);
            std::fstream fh;
            fh.open(realfn.c_str(),std::ios_base::out | std::ios_base::binary);
            if(fh.is_open())
            {
                const ByteBuffer& bb = mpq.ExtractFile((char*)mpqfn.c_str());
                fh.write((const char*)bb.contents(),bb.size());
                if(doMd5)
                {
                    MD5Hash h;
                    h.Update((uint8*)bb.contents(), bb.size());
                    h.Finalize();
                    uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH];
                    md5Tex[_PathToFileName(realfn)] = md5ptr;
                    memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH);
                }
                texdone++;
            }
            else
                printf("Could not write texture %s\n",realfn.c_str());
            fh.close();
        }
        printf("\n");
        if(texNames.size())
            OutMD5((char*)pathtex.c_str(),md5Tex);
        delete bar;
    }


}

void ExtractSoundFiles(void)
{
    MD5FileMap md5data;
    uint32 done = 0;
    printf("\nExtracting game audio files, %u found in DBC...\n",soundFileSet.size());
    CreateDir(SOUNDDIR);
    std::string outfn, altfn;
    barGoLink bar(soundFileSet.size(),true);
    for(std::set<NameAndAlt>::iterator i = soundFileSet.begin(); i != soundFileSet.end(); i++)
    {
        bar.step();
        if(!mpq.FileExists((char*)i->name.c_str()))
        {
            DEBUG( printf("MPQ: File not found: '%s'\n",i->name.c_str()) );
            continue;
        }
        altfn = i->alt.empty() ? _PathToFileName(i->name) : i->alt;

        outfn = std::string(SOUNDDIR) + "/" + NormalizeFilename(altfn);
        std::fstream fh;
        fh.open(outfn.c_str(), std::ios_base::out | std::ios_base::binary);
        if(fh.is_open())
        {
            const ByteBuffer& bb = mpq.ExtractFile((char*)i->name.c_str());
            if(bb.size())
            {
                fh.write((const char*)bb.contents(),bb.size());
                if(doMd5)
                {
                    MD5Hash h;
                    h.Update((uint8*)bb.contents(), bb.size());
                    h.Finalize();
                    uint8 *md5ptr = new uint8[MD5_DIGEST_LENGTH];
                    md5data[altfn] = md5ptr;
                    memcpy(md5ptr, h.GetDigest(), MD5_DIGEST_LENGTH);
                }
                done++;
            }
        }
        else
        {
            printf("Could not write sound file '%s'\n",outfn.c_str());
        }
        fh.close();
    }
    OutMD5(SOUNDDIR,md5data);
    printf("\n");
}

void WMO_Parse_Data(ByteBuffer bb, const char* _filename, bool groups, bool textures, bool models)
{
    bb.rpos(20); //Skip MVER chunk and header of MHDR
    irr::scene::RootHeader header;
    if (bb.size() < sizeof(header))
        return;
    bb.read((uint8*)&header, sizeof(header));
    if(groups)
    {
        std::string filename=_filename;
        for(uint32 i=0; i<header.nGroups; i++)
        {
            char grpfilename[255];
            sprintf(grpfilename,"%s_%03lu.wmo",filename.substr(0,filename.length()-4).c_str(),i);
            wmoGroupNames.insert(NameAndAlt(grpfilename));
        }

    }
    if(models || textures)
    {
        uint32 size;
        uint8 _cc[5];
        uint8 *fourcc = &_cc[0];
        fourcc[4]=0;

        while(bb.rpos()<bb.size())
        {
            bb.read((uint8*)fourcc,4);
            bb.read((uint8*)&size,4);
            if(!strcmp((char*)fourcc,"XTOM") && textures)
            {
                std::string temp;
                char c;
                uint32 read=0;
                while(read<size)
                {
                    bb.read((uint8*)&c,sizeof(char));
                    if(c=='\x0' && temp.size()>0)
                    {
                        texNames.insert(NameAndAlt(temp));
                        temp.clear();
                    }
                    else if(c!=0)
                        temp += c;
                    read++;
                }
            }
            else if(!strcmp((char*)fourcc,"NDOM") && models)
            {
                std::string temp;
                char c;
                uint32 read=0;
                while(read<size)
                {
                    bb.read((uint8*)&c,sizeof(char));
                    if(c=='\x0' && temp.size()>0)
                    {
                        modelNames.insert(NameAndAlt(temp));
                        temp.clear();
                    }
                    else if(c!=0)
                        temp += c;
                    read++;
                }
            }
            else
                bb.rpos(bb.rpos()+size);
        }
    }

}
void ADT_ExportStringSetByOffset(const uint8* data, uint32 off, std::set<NameAndAlt>& st,const char* stop)
{
    data += ((uint32*)data)[off]; // seek to correct absolute offset
    data += 28; // move ptr to real start of data
    uint32 offset=0;
    std::string s;
    char c;
    while(memcmp(data+offset,stop,4))
    {
        c = data[offset];
        if(!c)
        {
            if(s.length())
            {
                DEBUG(printf("DEP: %s\n",s.c_str()));
                st.insert(NameAndAlt(s));
                s.clear();
            }
        }
        else
            s += c;
        offset++;
    }
}

void ADT_FillTextureData(const uint8* data,std::set<NameAndAlt>& st)
{
    ADT_ExportStringSetByOffset(data,OFFSET_TEXTURES,st,"XDMM");
}

void ADT_FillWMOData(const uint8* data,std::set<NameAndAlt>& st)
{
    ADT_ExportStringSetByOffset(data,OFFSET_WMOS,st,"DIWM");
}

void ADT_FillModelData(const uint8* data,std::set<NameAndAlt>& st)
{
    ADT_ExportStringSetByOffset(data,OFFSET_MODELS,st,"DIMM");
}

void FetchTexturesFromModel(ByteBuffer bb)
{
    bb.rpos(0);
    irr::scene::ModelHeader header;
    if (bb.size() < sizeof(header))
        return;

    bb.read((uint8*)&header, sizeof(header));

    if (header.version != 0x108) {
        printf("Not M2 model file!");
        return;
    }

    irr::core::array<irr::scene::TextureDefinition> M2MTextureDef;
    M2MTextureDef.clear();
    irr::scene::TextureDefinition tempM2TexDef;

    bb.rpos(header.Textures.ofs);
    for(irr::u32 i=0;i<header.Textures.num;i++)
    {
        bb.read((uint8*)&tempM2TexDef,sizeof(irr::scene::TextureDefinition));
        M2MTextureDef.push_back(tempM2TexDef);
    }

    std::string tempTexFileName="";
    for(irr::u32 i=0; i<M2MTextureDef.size(); i++)
    {
        bb.rpos(M2MTextureDef[i].texFileOfs);
        tempTexFileName.resize(M2MTextureDef[i].texFileLen+1);
        bb.read((uint8*)&tempTexFileName[0],M2MTextureDef[i].texFileLen);

        if (tempTexFileName.empty())
            continue;
        // printf(tempTexFileName.c_str()); // for debug
        texNames.insert(NameAndAlt(tempTexFileName));
    }

}
