#define CS_SYSDEF_PROVIDE_DIR // define for cssysdef.h
#include <psconfig.h>

#include "registry.h"
#include "fileutil.h"
#include "registrycreator.h"
#include "updaterconfig.h"
#include <iutil/vfs.h>
#include <iutil/stringarray.h>
#include <csutil/databuf.h>

#include "updaterglobals.h"

// "Test/String/Words" -> "Words"
void GetSlicedPathLast(const char* path,csString& mod)
{
    csString path2 = path;
    mod  = path2.Slice(path2.FindLast('/'),path2.Length() - path2.FindLast('/'));
    if(!mod || mod == path2)
        mod = path2.Slice(path2.FindLast('\\'),path2.Length() - path2.FindLast('\\'));

    mod = mod.Slice(1,mod.Length()-1);
}

// "Test/String/Words" -> "Test"
void GetSlicedPathFirst(const char* path,csString& mod)
{
    csString path2 = path;
    mod  = path2.Slice(0,path2.FindFirst('/'));
    if(!mod || mod == path2)
        mod = path2.Slice(0,path2.FindFirst('\\'));
}

// "Test/String/Words" -> "Test/String"
void GetSlicedPathFirstLast(const char* path,csString& mod)
{
    csString path2 = path;
    mod  = path2.Slice(0,path2.FindLast('/'));
    if(!mod || mod == path2)
        mod = path2.Slice(0,path2.FindLast('\\'));
}

namespace updater
{

RegistryCreator::RegistryCreator (iObjectRegistry* newobjreg)
{
    objreg = newobjreg;
    registry = NULL;
}

RegistryCreator::~RegistryCreator ()
{
}

Registry* RegistryCreator::CreateRegistry ()
{
    // Check for critical files first
    if(!psupdaterengine->GetUpdater()->GetConfig()->CheckCriticalFiles())
    {
        psupdaterengine->CriticalError("One or more critical files are missing!\n");
        return NULL;
    }

    registry = new Registry(objreg);

    srand(time(NULL));
    MD5Sum ver;
    
    // Randomize a version stamp
    int multi = rand()%100;
    unsigned int version = rand()%1000000;
    version *= multi;

    csString verStr;
    verStr = version;

    ver.Calculate((char*)verStr.GetData(),verStr.Length());
    registry->SetReposVersion(ver.Get());

    // Stat root dir first
    CreateModule(".");

    DIR* dir = opendir (".");
    if (!dir)
    {
        delete registry;
        psupdaterengine->CriticalError("Couldn't open current directory for reading.");
    }

    struct dirent* file;
    while ( (file = readdir (dir)) )
    {   
        // skip files starting with dot
        if (file->d_name[0] == '.')
            continue;
    
        FileStat* stat = StatFile(file->d_name);        
        
        if (!stat)
        {
            printf ("Warning: Couldn't stat file '%s'.\n", file->d_name);
            continue;
        }
                       
        if (stat->type == FileStat::TYPE_DIRECTORY)
        {
            // Check if we should ignore this dir or not
            if (psupdaterengine->GetUpdater()->GetConfig()->ShouldIgnoreDir(file->d_name))
            {
                printf("I - %s\n", (const char*)file->d_name );            
                continue;
            }

            CreateModule (file->d_name);
        }

        delete stat;
    }

    closedir (dir);

    return registry;
}

void RegistryCreator::CreateModule (const char* name)
   
{
    printf("Creating Module: %s\n", name );
    
    Module* mod = registry->CreateModule (name);
    
    ReadSubDir (mod, "");
}



void RegistryCreator::ReadSubDir (Module* mod, const char* directory)
{
    csString module = mod->GetName();
    csString dirname = module;
    if (dirname == "root") 
        dirname = ".";

    if (strcmp (directory, "") != 0)
    {
        dirname += "/";
        dirname += directory;
    }

    printf ("Reading Directory %s\n", (const char*) dirname);

    DIR* dir = opendir(dirname);
    if (!dir)
        psupdaterengine->CriticalError("Couldn't open directory '%s'.", (const char*) dirname);

    struct dirent* file;
    while ( (file = readdir(dir)) )
    {
        // ignore files starting with a dot.
        if (file->d_name[0] == '.')
            continue;

    
        csString path;
        path = dirname;
        path += "/";
        path += file->d_name;
    
        FileStat* stat = StatFile(path);
        if (!stat)
        {
            printf ("Warning: Couldn't stat file '%s'.\n", (const char*) path);
            continue;
        }

        if (stat->type == FileStat::TYPE_DIRECTORY && csString(mod->GetName()) != "root")
        {
            // Check if we should ignore this dir or not
            if (psupdaterengine->GetUpdater()->GetConfig()->ShouldIgnoreDir(path))
            {
                printf("I - %s\n", path.GetData());
                continue;
            }
            
            csString subdir = directory;
            if (strcmp(directory, "") != 0)
            {
                subdir += "/";
            }

            subdir += file->d_name;
            ReadSubDir (mod, subdir);
        }
        else if (stat->type == FileStat::TYPE_FILE)
        {
            // Check if we should ignore this file or not
            if (psupdaterengine->GetUpdater()->GetConfig()->ShouldIgnoreFile(path))
            {
                printf("I - %s\n", path.GetData());        
                continue;
            }                

            RegistryFile* regfile = new RegistryFile(path, 0);
    
            // Special handle if we are the root module
            csString toCopy = path;
            if (module == "root")
            {
                toCopy = toCopy.Slice(2,toCopy.Length());
                regfile->file = toCopy;
            }

            // Set some general stats
            regfile->size = stat->size;
            regfile->md5.ReadFile ("/this/" + path, false);

            csString filename = file->d_name;
            bool zip = false;
            // Check if there is a possiblilty that the file is a zip
            if (filename.Length() > 3)
            {
                if (filename.Slice( filename.Length() - 3, filename.Length()) == "zip")
                {
                    zip = true;    
                }
            }
            
            // ZIP is special
            if (zip)
                ReadZip(mod,filename,dirname); 
            else
            {
                if (psupdaterengine->GetUpdater()->GetConfig()->IsConfig(path))
                {
                    regfile->flags |= RegistryFile::FLAG_CONFIGFILE;
                }

                Sort(path,mod,regfile);
            }
        }
        delete stat;
    }
}

void RegistryCreator::ReadZip(Module* mod, const char* file, const char* path)
{
    csString zipfile = "/this/" + csString(path) + csString("/") + csString(file);
    csRef<iDataBuffer> fileBuff = psupdaterengine->GetVFS()->GetRealPath(zipfile);

    if (!psupdaterengine->GetVFS()->Mount("/zip",fileBuff->GetData() ))
    {
        printf("Couldn't mount VFS path!\n");
        return;
    }    

    csString filename;
    filename = "/storage/";
    filename += mod->GetName();
    filename += "/";
    
    if (path)
    {
        filename += path;
        filename += "/";
    }
    csString csfile = file;
    filename += csfile.Slice(0,csfile.FindLast('.'));
    filename += "_all.zip";

    psupdaterengine->GetUpdater()->CopyFile(
        zipfile,
        filename,true);

    // Fill a ZipFile struct with info to give all files in this zip
    ZipFile zipstru;
    zipstru.file = file;
    // Get Size
    size_t size;
    psupdaterengine->GetVFS()->GetFileSize(filename,size);
    zipstru.size = size;


    // Get MD5 for this zip
    if(!zipstru.md5.ReadFile(filename,false))
        printf("ERROR! COULDN'T GET MD5 SUM FOR FILE %s!\n",filename.GetData());

    // Get files and loop through them
    csRef<iStringArray> buff = psupdaterengine->GetVFS()->FindFiles("/zip/");

    for (size_t i = 0;i < buff->Length();i++)
    {
        bool dir = false;
        csString fileName = buff->Get(i);

        if (fileName.GetAt(fileName.Length()-1) == '/')
            dir = true;

        fileName = fileName.Slice(5,fileName.Length());

        if (dir)
        {
            csString subpath = buff->Get(i);
            ReadZipSubDir(mod,subpath.Slice(5,subpath.Length()),&zipstru,path);
        }
        else
        {
            if (!fileName)
                continue;

            ReadZipFile(mod,fileName,path,&zipstru);
        }
    }

    if (!psupdaterengine->GetVFS()->Unmount("/zip",fileBuff->GetData()))
    {
        printf("Couldn't unmount VFS path!\n");
        return;
    }
}



void RegistryCreator::ReadZipSubDirNames(Module* mod, const char* dir,ZipFile* zipname,const char* realpath )
{
        csRef<iStringArray> buff = psupdaterengine->GetVFS()->FindFiles("/zip/" + csString(dir));
        for (size_t i = 0;i < buff->Length();i++)
        {
            csString file = buff->Get(i);
            bool direct = false;

            if (file.GetAt(file.Length()-1) == '/')
                direct = true;

            file = file.Slice(5,file.Length());

            if (direct)
            {          
                csString subpath = buff->Get(i);            
                ReadZipSubDirNames(mod,subpath.Slice(5,subpath.Length()),zipname,realpath);
            }
            else
            {
                if (!file)
                    continue;                                        

                size_t first = file.FindFirst('/');                
                csString myFile = file.Slice( first+1, file.Length() );                                        
                psupdaterengine->GetUpdater()->CopyFile( "/zip/" + file, 
                                                         "/myzip/" + myFile,
                                                         true );                
            }                
          
        
        }

}    

void RegistryCreator::ReadZipSubDir(Module* mod, const char* dir,ZipFile* zipname,const char* realpath, int level )
{

    //printf("******* Reading SubZipDir: %s from ZIP: %s level: %d\n", dir, zipname->file.GetData(), level );
         
    // /zip is already mounted from ReadZip
    // Get files and loop through them
    csRef<iStringArray> buff = psupdaterengine->GetVFS()->FindFiles("/zip/" + csString(dir));
    
    for (size_t i = 0;i < buff->Length();i++)
    {
        bool direct = false;
        csString fileName = buff->Get(i);

        if (fileName.GetAt(fileName.Length()-1) == '/')
            direct = true;

        fileName = fileName.Slice(5,fileName.Length());

        if (direct)
        {          
            csString subpath = buff->Get(i);            
            ReadZipSubDir(mod,subpath.Slice(5,subpath.Length()),zipname,realpath, level+1);
        }
        else
        {
            if (!fileName)
                continue;
                            
            ReadZipFile(mod,fileName,realpath,zipname, level+1);
        }                
    }
    
    //printf("******* Finished Reading SubZipDir: %s %d\n", dir, level );

    
    
        
    if ( level == 0 )
    {
        csString filename("test.zip");
                
        // This will copy the file to something like /www/updater/art/models.zip/thing.spr
        psupdaterengine->GetVFS()->DeleteFile("/this/test.zip");        
        
        psupdaterengine->GetVFS()->Mount("/myzip",filename );        
        
        for (size_t i = 0;i < buff->Length();i++)
        {
            csString file = buff->Get(i);
            bool direct = false;

            if (file.GetAt(file.Length()-1) == '/')
                direct = true;

            file = file.Slice(5,file.Length());

            if (direct)
            {          
                csString subpath = buff->Get(i);            
                ReadZipSubDirNames(mod,subpath.Slice(5,subpath.Length()),zipname,realpath);
            }
            else
            {
                if (!file)
                    continue;                                        
                    
                size_t first = file.FindFirst('/');                
                csString myFile = file.Slice( first+1, file.Length() );                                        
                //printf("---> High Level File: %s Directory: %s \n", myFile.GetData(), dir );                              
                //printf("Source: /zip/%s dest /myzip/%s", file.GetData(), myFile.GetData() );
                
                
                psupdaterengine->GetUpdater()->CopyFile( "/zip/" + file, 
                                                         "/myzip/" + myFile,
                                                         true );
                psupdaterengine->GetVFS()->Sync(); 
                                                                         
            }                            
        }
                                
         
        csString directoryName = dir;                    
        if (directoryName.GetAt(directoryName.Length()-1) == '/')
            directoryName.DeleteAt(directoryName.Length()-1);
                                
        csString finalLocation;        
        finalLocation.Format("/storage/%s/%s/%s/%s.zip", mod->GetName(), realpath, zipname->file.GetData(), directoryName.GetData() );                                                                
        psupdaterengine->GetVFS()->Sync(); 

        if ( !psupdaterengine->GetUpdater()->CopyFile("/this/test.zip", finalLocation,true) )
            printf("Failed Copy!\n");
        psupdaterengine->GetVFS()->Sync(); 
            
        psupdaterengine->GetVFS()->Unmount("/myzip", "test.zip");    
                                                        
    }        

}

void RegistryCreator::ReadZipFile (Module* mod, const char* file, const char* path,ZipFile* zipname, int level )
{
    //printf("Reading: %s in %s level %d\n", file, zipname->file.GetData(), level );
    // Will set filename later
    RegistryFile* regfile = new RegistryFile("", 0);
    
    // Get file stats
    size_t filesize;
    psupdaterengine->GetVFS()->GetFileSize("/zip/" + csString(file),filesize); 
    regfile->size = filesize;

    // Get MD5 sum
    if (!regfile->md5.ReadFile ("/zip/" + csString(file), false))
    {
        printf("Couldn't calculate MD5 checksum for file %s! Skipping..\n",file);
        return;
    }

    csString ender;
    if (path)
    {
        ender = path;
        ender += "/";
    }
    ender += csString(zipname->file)  + csString("/") + csString(file);

    /**
     * parentZip = ZIP file (art/test.zip)
     * zipFile = Internal file (file.ext)
     * file = server file (art/test.zip/file.ext)
     */

    regfile->inZip = true;
    regfile->parentZip = ender.Slice(0,ender.Length() - strlen(file) - 1); //Remove filename and trailing /
    regfile->parentSize = zipname->size;
    regfile->parentMD5.Set( zipname->md5.Get());
    regfile->zipFile = file;
    regfile->file = ender;

    mod->AddRegistryFile(regfile);

    // This will copy the file to something like /www/updater/art/models.zip/thing.spr
    if ( level == 0 )
        psupdaterengine->GetUpdater()->CopyFile( "/zip/" + csString(file), "/storage/" + csString(mod->GetName()) + csString("/") + ender, true);

}

void RegistryCreator::Sort(const char* path,Module* mod, RegistryFile* regfile)
{
    Module* modToUse = mod;
    csString cspath = path;
    // Sort the path
    csString sortedPath = psupdaterengine->GetUpdater()->GetConfig()->GetSortedPath(path,mod->GetName());

    if (sortedPath != path)
    {
        csString module = sortedPath.Slice(0,sortedPath.FindFirst('/'));

        // This will get the module if it exists, or create if it doesn't
        modToUse = registry->CreateModule(module);
    }

    size_t pathS;
    if(psupdaterengine->GetUpdater()->GetConfig()->HasCustomSortDest(path))
        pathS = sortedPath.FindLast('/')+1;
    else
    {
        pathS = sortedPath.FindFirst('/')+1;
    }

    sortedPath = sortedPath.Slice(pathS,sortedPath.Length()-pathS);
    if(sortedPath.Slice(0,2) == "./")
    {
        sortedPath = sortedPath.Slice(2,sortedPath.Length()-2);
        cspath = cspath.Slice(2,cspath.Length()-2);
    }

    regfile->file = sortedPath;
    
    if(mod != modToUse || sortedPath != cspath)
    {
        printf("S - %s to %s",cspath.GetData(),sortedPath.GetData());
        if(mod != modToUse)
            printf(" (%s => %s)",mod->GetName(),modToUse->GetName());
        printf("\n");
    }

    if (psupdaterengine->GetUpdater()->CopyFile(
            "/this/" + cspath,
            "/storage/" + csString(modToUse->GetName()) + "/" + sortedPath,true)
        )
        modToUse->AddRegistryFile(regfile);

}

void RegistryCreator::UpdateRegistry(const char* registrypath,const char* filename)
{
    if(!filename)
    {
        printf("Got empty filename!\n");
        return;
    }
    if(!registrypath)
    {
        printf("Got empty registry filename!\n");
        return;
    }

    Config* config = psupdaterengine->GetUpdater()->GetConfig();

    csString file = filename; //Convert because of the csString functions
    csString mod;
    GetSlicedPathFirst(file,mod);

    csString path = registrypath;
    GetSlicedPathFirstLast(path,path);

    // Now get the sorted path (i.e /art/models.zip becomes /art/art/models.zip)
    csString sorted = config->GetSortedPath(file,mod);
    // Get the new module if the file is sorted
    GetSlicedPathFirst(sorted,mod);

    //Mount registry path
    csString registryDir;
    GetSlicedPathFirstLast(registrypath,registryDir);
    psupdaterengine->GetVFS()->Mount("/storage",registryDir+"/");

    // Load the registry
    csString registryFile;
    GetSlicedPathLast(registrypath,registryFile);
    Registry* registry = new Registry(psupdaterengine->GetObjectRegistry());
    if(!registry->Load("/storage/" + registryFile))
    {
        printf("Couldn't load registry %s!\n",registrypath);
        delete registry;
        return;
    }

    // Find the module
    Module* module = registry->GetModule(mod);
    if(!module)
    {
        printf("Couldn't find the module %s!\n",mod.GetData());
        delete registry;
        return;
    }

    uint32_t flags = 0;
    if(config->IsConfig(file))
        flags |= RegistryFile::FLAG_CONFIGFILE;

    RegistryFile* regfile = new RegistryFile(filename,flags);
    size_t size;
    psupdaterengine->GetVFS()->GetFileSize("/this/" + file,size);
    regfile->size = size;

    regfile->md5.ReadFile("/this/" + file,false);

    // This will copy the file to something like /www/updater/art/models.zip/thing.spr
    if (psupdaterengine->GetUpdater()->CopyFile(
        "/this/" + file,
        "/storage/" + sorted,
        true))
    {
        module->AddRegistryFile(regfile);
        printf("File %s added to %s\n",file.GetData(),sorted.GetData());
    }

    registry->Save("/storage/" + registryFile);

    psupdaterengine->GetVFS()->Unmount("/storage",registryDir);
}

} // end of namespace updater
