/*
 * registry.cpp by Matthias Braun <matze@braunis.de>
 *
 * Copyright (C) 2002 Atomic Blue (info@planeshift.it, http://www.atomicblue.org) 
 *
 *
 * 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 of the License)
 * 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 for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */
#include <psconfig.h>

#include <csutil/util.h>
#include <csutil/xmltiny.h>
#include <csutil/scfstr.h>
#include <csutil/databuf.h>
#include <iutil/objreg.h>
#include <iutil/vfs.h>
#include "registry.h"
#include "sortarray.h"
#include "updaterconfig.h"

#include "updaterglobals.h"

namespace updater
{

Registry::Registry (iObjectRegistry* newobjreg)
{
    objreg = newobjreg;
    vfs = psupdaterengine->GetVFS();
    if (!vfs)
        psupdaterengine->CriticalError("Couldn't find vfs plugin!\n");

    xml = psupdaterengine->GetDocumentSystem();
    if (!xml)
        psupdaterengine->CriticalError("Error creating xml document system!");
}

Registry::~Registry ()
{
    vfs = NULL;
}

bool Registry::Load (const char* filename)
{
    csRef<iDataBuffer> buf = vfs->ReadFile (filename);
    
    if (!buf || !buf->GetSize())
    {
        if (csString(filename) == "/this/updatercurrent.xml")
            printf("Couldn't load cached file list. If this is the first time you run, there is nothing to worry about!\n");
        else
            printf("Error loading registry file '%s'\n", filename);

        return false;
    }
    
    
    csRef<iDocument> doc = xml->CreateDocument();
    const char* error = doc->Parse (buf);
    if (error)
    {
        printf("Couldn't parse xml: '%s'", error);
        return false;
    }
    
    csRef<iDocumentNode> root = doc->GetRoot ();
    if (!root)
    {
        printf("Error when getting top registry node!");
        return false;
    }

    csRef<iDocumentNode> topnode = root->GetNode ("registry");
    if (!topnode)
    {
        printf("Couldn't find registry topnode!");
        return false;
    }
    
    // read list of modules
    csRef<iDocumentNodeIterator> it = topnode->GetNodes ();
    while (it->HasNext ())
    {
          csRef<iDocumentNode> node = it->Next();
           if (node->GetType() != CS_NODE_ELEMENT)
            continue;

        if (!strcmp(node->GetValue(),"information"))
        {
            version = node->GetAttributeValue("version");
            updaterVersion = node->GetAttributeValueAsInt("updater_version");
            updaterWin = node->GetAttributeValue("updater_win");
            updaterMac = node->GetAttributeValue("updater_mac");
            updaterLinux = node->GetAttributeValue("updater_linux");
            md5Win = node->GetAttributeValue("md5_win");
            md5Mac = node->GetAttributeValue("md5_mac");
            md5Linux = node->GetAttributeValue("md5_linux");
            continue;
        }
        
        if (strcmp(node->GetValue(), "module") != 0)
        {
            fprintf (stderr, "Warning expected module, got: '%s'\n",
                node->GetValue());
            continue;
        }

        // XXX: Read conditon?
        const char* name = node->GetAttributeValue ("name");
        if (!name)
        {
            fprintf (stderr, "WARNING: directory node without name!\n");
            continue;
        }

        bool used = node->GetAttributeValueAsBool ("used");

        Module* mod = new Module(name);
        mod->SetUsed(used);

        size_t modindex = modules.Push (mod);
        ParseModule (node, modules[modindex]);
    }
    return true;
}

void Registry::ParseModule (iDocumentNode* node, Module* objectlist)
{
    csRef<iDocumentNodeIterator> it = node->GetNodes ();
    while (it->HasNext ())
    {

        csRef<iDocumentNode> node = it->Next();
        if (node->GetType() != CS_NODE_ELEMENT)
            continue;

        if (!strcmp (node->GetValue(),"zip"))
        {

            //Do zip
            csString zip = node->GetAttributeValue("file");
            csString zipMd5 = node->GetAttributeValue("md5");

            unsigned long zipSize = 0;
            if(node->GetAttributeValue("size"))
                zipSize = atol(node->GetAttributeValue("size"));

            csRef<iDocumentNodeIterator> it2 = node->GetNodes ();
            while (it2->HasNext ())
            {
                csRef<iDocumentNode> node2 = it2->Next();       

                //Add file?
                if (!strcmp(node2->GetValue(),"add"))
                {
                    RegistryFile* object = new RegistryFile(node2->GetAttributeValue ("realfile"), 0);

                    object->zipFile = node2->GetAttributeValue ("zipfile");
                    object->parentZip = zip;
                    if(zipMd5.Length() == 32)
                        object->parentMD5.Set(zipMd5);

                    object->parentSize = zipSize;
                    object->inZip = true;
                    object->size = node2->GetAttributeValueAsInt ("size");

                    const char* md5 = node2->GetAttributeValue ("md5");
                    if (md5)
                        object->md5.Set(md5);

                    objectlist->AddRegistryFile (object);    
                }

                //Remove file?
                if (!strcmp(node2->GetValue(),"remove"))
                {
                    RegistryFile* object = new RegistryFile(node2->GetAttributeValue ("file"), 0);

                    object->parentZip = zip;
                    object->inZip = true;
                    object->removeFromZip = true;

                    objectlist->AddRegistryFile (object);    
                }


            }

            continue;

        }
        else if (strcmp (node->GetValue(), "file") != 0)
        {
            fprintf (stderr, "Warning: Expected 'file' node got '%s'.\n",
                node->GetValue());
            continue;
        }

        const char* path = node->GetAttributeValue ("path");
        if (!path)
        {
            fprintf (stderr, "Warning: Expected filename for file.\n");
            continue;
        }
            
        // reading flags
        uint32_t flags = 0;
        if (node->GetAttributeValueAsInt ("executable") == 1)
            flags |= RegistryFile::FLAG_EXECUTABLE;
        if (node->GetAttributeValueAsInt ("textfile") == 1)
            flags |= RegistryFile::FLAG_TEXTFILE;
        if (node->GetAttributeValueAsInt ("config") == 1)
            flags |= RegistryFile::FLAG_CONFIGFILE;
        const char* link = node->GetAttributeValue ("link");
        if (link)
            flags |= RegistryFile::FLAG_LINK;

        RegistryFile* object = new RegistryFile(path, flags);

        object->size = node->GetAttributeValueAsInt ("size");

        const char* md5 = node->GetAttributeValue ("md5");
        if (md5)
            object->md5.Set(md5);

        objectlist->AddRegistryFile (object);
    }

//    SortArray (objectlist);
}

void Registry::Save (const char* filename)
{
    csRef<iDocument> doc = xml->CreateDocument();
    csRef<iDocumentNode> root = doc->CreateRoot ();

    csRef<iDocumentNode> topnode = root->CreateNodeBefore (CS_NODE_ELEMENT);
    topnode->SetValue ("registry");

    csRef<iDocumentNode> infonode =
        topnode->CreateNodeBefore (CS_NODE_ELEMENT);

    infonode->SetValue ("information");
    infonode->SetAttribute("version",version);
    infonode->SetAttributeAsInt("updater_version",UPDATER_VERSION);

    if(psupdaterengine->GetCreateMode())
    {
        MD5Sum updaterWin,updaterMac,updaterLinux;
        if(updaterWin.ReadFile(csString("/this/") + psupdaterengine->GetUpdater()->GetConfig()->GetUpdaterWinPath(),false))
        {
            infonode->SetAttribute("updater_win",psupdaterengine->GetUpdater()->GetConfig()->GetUpdaterWinPath());
            infonode->SetAttribute("md5_win",updaterWin.Get());
        }
        else
            printf("Couldn't hash updater Win application\n");

        if(updaterMac.ReadFile(csString("/this/") + psupdaterengine->GetUpdater()->GetConfig()->GetUpdaterMacPath(),false))
        {
            infonode->SetAttribute("updater_mac",psupdaterengine->GetUpdater()->GetConfig()->GetUpdaterMacPath());
            infonode->SetAttribute("md5_mac",updaterWin.Get());
        }
        else
            printf("Couldn't hash updater Mac application\n");

        if(updaterLinux.ReadFile(csString("/this/") + psupdaterengine->GetUpdater()->GetConfig()->GetUpdaterLinuxPath(),false))
        {
            infonode->SetAttribute("updater_linux",psupdaterengine->GetUpdater()->GetConfig()->GetUpdaterLinuxPath());
            infonode->SetAttribute("md5_linux",updaterLinux.Get());
        }
        else
            printf("Couldn't hash updater Linux application\n");
                        
    }

    if (psupdaterengine->GetCreateMode())
    {
        //Copy module
        csString newUpdater = "updater";
        newUpdater += UPDATER_VERSION;
        newUpdater += ".zip";

        if (!vfs->Mount("/zip",newUpdater ))
        {
            printf("Couldn't mount VFS path!\n");
        }

        for(size_t i = 0;i < psupdaterengine->GetUpdater()->GetConfig()->systemFiles.Length();i++)
        {
            if(!psupdaterengine->GetUpdater()->CopyFile(
                "/this/" + psupdaterengine->GetUpdater()->GetConfig()->systemFiles.Get(i),
                "/zip/" + psupdaterengine->GetUpdater()->GetConfig()->systemFiles.Get(i),
                true
                ))
            {
                printf("Couldn't copy file into system zip!\n");
            }
        }
        
        if (!vfs->Sync())
        {
            printf("Couldn't Sync VFS path!\n");
        } 

        if (!vfs->Unmount( "/zip",newUpdater ))
        {
            printf("Couldn't unmount VFS path!\n");
        } 

        psupdaterengine->GetUpdater()->CopyFile("/this/" + newUpdater,"/storage/" + newUpdater,true);
        vfs->DeleteFile("/this/" + newUpdater);
    }

    csArray<csString> modulesXml;
    psupdaterengine->GetUpdater()->GetConfig()->ModuleSetup(modulesXml);

    // Save all modules
    for (size_t i=0;i<modules.Length();i++)
    {
        csRef<iDocumentNode> modulenode = topnode->CreateNodeBefore (CS_NODE_ELEMENT);

        modulenode->SetValue ("module");
        modulenode->SetAttribute ("name", modules[i]->GetName());
        
        if(!psupdaterengine->GetCreateMode() && modulesXml.Find(modules[i]->GetName()) != csArrayItemNotFound)
            modulenode->SetAttributeAsInt ("used", true);

        WriteModule (modulenode, modules[i]);
    }

    const char* error = doc->Write (vfs, filename);
    if (error)
        printf("When writing '%s': %s, check read only setting\n", filename, error);
}

void Registry::WriteModule (iDocumentNode* node,Module* mod)
{
    for (unsigned int i=0;i<mod->GetRegistryLength();i++)
    {    
        RegistryFile* file = mod->GetRegistryFile(i) ;
        if (file->inZip)
        {

            csRef<iDocumentNode> zipnode = 
                node->CreateNodeBefore (CS_NODE_ELEMENT);           
            zipnode->SetValue ("zip");

            zipnode->SetAttribute("file",file->parentZip);
            zipnode->SetAttributeAsInt("size",(int)file->parentSize);
            zipnode->SetAttribute("md5",file->parentMD5.Get());

            csString zipfile = file->parentZip;
            csString oldzipfile = zipfile;

            while(zipfile == oldzipfile)
            {
                csRef<iDocumentNode> filenode = 
                    zipnode->CreateNodeBefore (CS_NODE_ELEMENT);           
                filenode->SetValue ("add");
                filenode->SetAttribute ("zipfile", file->zipFile);
                
                filenode->SetAttribute ("realfile", file->file); 

                filenode->SetAttributeAsInt ("size",(int) file->size);
                if (file->md5.Get())
                    filenode->SetAttribute ("md5", file->md5.Get());
                

                i++;
                if (i >= mod->GetRegistryLength())
                    break;

                file = mod->GetRegistryFile(i);
                zipfile = file->parentZip;

            }
            i--; // Do this to not skip the last file

        }
        else
        {
            csRef<iDocumentNode> filenode = 
                node->CreateNodeBefore (CS_NODE_ELEMENT);
            filenode->SetValue ("file");
            filenode->SetAttribute ("path", file->file);
            filenode->SetAttributeAsInt ("size", (int)file->size);
            if (file->md5.Get())
                filenode->SetAttribute ("md5", file->md5.Get());
    
            if (file->flags & RegistryFile::FLAG_EXECUTABLE)
                filenode->SetAttribute ("executable", "1");
            if (file->flags & RegistryFile::FLAG_TEXTFILE)
                filenode->SetAttribute ("textfile", "1");
            if (file->flags & RegistryFile::FLAG_LINK)
                filenode->SetAttribute ("link", file->link);
            if (file->flags & RegistryFile::FLAG_CONFIGFILE)
                filenode->SetAttribute ("config", "1");
        }
    }
}

Module* Registry::GetModule (const char* modulename)
{
    for (size_t i=0;i<modules.Length();i++)
    {
    if (strcmp(modules[i]->GetName(), modulename) == 0)
        return modules[i];
    }

    return NULL;
}

Module* Registry::CreateModule (const char* modulename)
{
    // Check if we already have one module with that name
    Module* existing = GetModule(modulename);
    if (existing)
        return existing;


    Module* mod = new Module (modulename);

    size_t idx = modules.Push (mod);
    return modules[idx];
}

bool Registry::operator == (Registry* other) const
{
    // registries are sorted so just comparing
    for (size_t m=0;m<modules.Length();m++)
    {
    const char* module = modules[m]->GetName();
    Module* othermod = other->GetModule (module);

    if (!othermod || modules[m]->GetRegistryLength() != othermod->GetRegistryLength())
        return false;
    
    for (unsigned int f=0;f<modules[m]->GetRegistryLength();f++)
    {
        if (! (modules[m]->GetRegistryFile(f) == othermod->GetRegistryFile(f) ) )
        return false;
    }
    }

    return true;
}

const char* Registry::GetUpdaterMD5()
{
#ifdef CS_PLATFORM_WIN32
    return md5Win;
#endif
#ifdef CS_PLATFORM_MACOSX
    return md5Mac;
#else
    return NULL;
#endif
}

const char* Registry::GetUpdaterPath()
{
#ifdef CS_PLATFORM_WIN32
    return updaterWin.GetData();
#endif
#ifdef CS_PLATFORM_MACOSX
    return updaterMac.GetData();
#else
    return NULL;
#endif
}

//---------------------------------------------------------------------------

Module::Module (const char* newname)
{
    name = csStrNew(newname);
}

Module::~Module ()
{
    delete[] name;
}

const char* Module::GetName () const
{
    if (csString(name) == ".")
        return "root";

    return name;
}

RegistryFile* Module::GetRegistryFile(size_t id)
{
    return list.Get(id);
}

} // end of namespace updater
