/*
 * updaterconfig.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 "updaterglobals.h"

#include "updaterconfig.h"
#include "download.h"

  /*
   node = node to get info from
   defv = DEFault Value
   returns the string
  */

csString LoadVar(iDocumentNode* node, csString defv)
{
    if (!node)
    {
        printf("Warning: Using default value");
        return defv;
    }

    csString str = node->GetContentsValue();
    if (str == "")
    {
        printf("Warning: Using default value for %s\n" , node->GetValue() );
        return defv;
    }

    return str;
}

namespace updater
{

Config::Config (iDocumentNode* node)
{
    config = node;
    skipVerCheckThisSession = false;
}
    
Config::~Config ()
{
}

bool Config::Initialize()
{
    activeMirror = 0;
    arrayMirror = 0;

    // Parse config and add missing xml elements
    if (!config || strcmp(config->GetValue(), "config") != 0)
        psupdaterengine->CriticalError("Invalid XML config node! updaterconfig.xml is corrupted!");

    //Get temp file
    tempfile = LoadVar( config->GetNode ("temp") , "download.tmp");

    // Get whole_zip setting
    iDocumentNode* node = config->GetNode ("whole_zips");
    if(node)
        wholeZips = node->GetContentsValueAsInt() ? true : false;
    else
    {
        printf("Warning, using default value for whole_zips (false)\n");
        wholeZips = false;
    }

    // Get zip_sync setting
    node = config->GetNode ("zip_sync");
    if(node)
        zipSync = node->GetContentsValueAsInt();
    else
    {
        printf("Warning, using default value for zip_sync (0)\n");
        zipSync = 0;
    }

    // % whole zip setting
    node = config->GetNode ("precent_wholezip");
    if(node)
        zipPre = node->GetContentsValueAsInt();
    else
    {
        printf("Warning, using default value for precent_wholezip (100)\n");
        zipPre = 100;
    }
    // Get updater_MD5 setting
    node = config->GetNode ("updater_MD5");
    if(node)
        updaterMD5 = node->GetContentsValueAsInt() ? true : false;
    else
    {
        printf("Warning, using default value for updater_MD5 (true)\n");
        updaterMD5 = true;
    }

    // Get Windows updater path
    node = config->GetNode ("updaterWin");
    if(node)
        updaterWin = node->GetContentsValue();

    // Get the Mac updater path        
    node = config->GetNode ("updaterMac");
    if(node)
        updaterMac = node->GetContentsValue();

    // Get the linux updater path        
    node = config->GetNode ("updaterLinux");
    if(node)
        updaterLinux = node->GetContentsValue();
    
    if(updaterWin == "" || updaterMac == "" || updaterLinux == "")
        printf("Warning, can't find paths for win or mac or linux updater application! (Server only)\n");

    //Get config files
    csRef<iDocumentNode> configsNode = config->GetNode ("configs");
    if (configsNode != NULL)
    {
        csRef<iDocumentNodeIterator> node = configsNode->GetNodes();
        while(node->HasNext())
        {
            csRef<iDocumentNode> cnode = node->Next();

            if (!strcmp(cnode->GetValue(),"config"))
            {
                const char* ext = cnode->GetAttributeValue("ext");
                if (ext)
                {
                    configExt.Push(csString(ext));
                }

                const char* file = cnode->GetAttributeValue("file");
                if (file)
                {
                    configFiles.Push(csString(file));
                }

                continue;
            }
        }
    }
    else
    {
        Error1("Config extensions node (<configs>) not found (Server only)\n");
    }

    //Get the sortings files
    csRef<iDocumentNode> sortNode = config->GetNode ("sortings");
    if (sortNode != NULL)
    {
        csRef<iDocumentNodeIterator> node = sortNode->GetNodes();
        while(node->HasNext())
        {
            csRef<iDocumentNode> snode = node->Next();

            if (!strcmp(snode->GetValue(),"sort"))
            {
                SortEntry* sortEntry = new SortEntry;

                const char* ext = snode->GetAttributeValue("ext");
                if (ext)
                {
                    sortEntry->file = false;
                    sortEntry->ext= ext;
                }

                const char* file = snode->GetAttributeValue("file");
                if (file)
                {
                    sortEntry->file = true;
                    sortEntry->path = file;

                    // Rename file?
                    const char* dest_file = snode->GetAttributeValue("dest_file");
                    if (dest_file)
                        sortEntry->destFile = dest_file;
                }

                const char* to = snode->GetAttributeValue("to");
                if (to)
                {
                    sortEntry->to = to;
                }

                sortings.Push(sortEntry);

                continue;
            }
        }
    }
    else
    {
        Error1("File sort rules node (<sortings>) not found (Server only)\n");
    }


    //Get mirrors
    activeMirror = (unsigned int)-1;

    csRef<iDocumentNode> mirrorNode = config->GetNode ("mirrors");
    if (mirrorNode != NULL)
    {
        csRef<iDocumentNodeIterator> node = mirrorNode->GetNodes();
        while(node->HasNext())
        {
            csRef<iDocumentNode> mnode = node->Next();

            if (!strcmp(mnode->GetValue(),"mirror"))
            {
                Mirror* mirror = new Mirror;
                mirror->id               = mnode->GetAttributeValueAsInt("id");
                if(mirror->id == -1)
                {
                    Error1("Attempted to use Last resort feature, although this isn't finished. Skipping\n");
                    delete mirror;
                    continue;
                }
                mirror->name             = mnode->GetAttributeValue("name");
                mirror->baseURL          = mnode->GetAttributeValue("url");
                mirror->repositoryFile   = mnode->GetAttributeValue("repository");
                mirror->versionFile      = mnode->GetAttributeValue("versioninfo");
                mirror->filesDirectory   = mnode->GetAttributeValue("files");
                mirror->passphrase       = mnode->GetAttributeValue("passphrase");
                mirror->noVersion        = mnode->GetAttributeValueAsBool("noversion");

                if (mirror->repositoryFile.IsEmpty()) mirror->repositoryFile = "repository.zip";
                if (mirror->versionFile.IsEmpty()) mirror->versionFile = "version.dat";

                mirrors.Push(mirror);
                continue;
            }
        }
    }
    else
    {
        Error1("Mirror node not found (CRITICAL ERROR!)\n");
    }

    if(activeMirror == -1)
    {
        Notify1(LOG_ANY, "Randomizing mirrors.. ");
        srand(time(NULL));
        // if only one server then use it
        if (mirrors.Length()==1)
            activeMirror = 1;
        // else randomize between mirrors-1. skip last resort, always last mirror
        else
           activeMirror = (unsigned int)(rand() % (mirrors.Length()-1))+1;
        Notify2(LOG_ANY,"Using mirror %d\n",activeMirror);

        // Mirror SHOULD exist
        if(!GetMirror())
        {
            psupdaterengine->CriticalError("Check numbering on mirrors, jump occurred");
            return false;
        }
    }

    //Get proxy settings
    csRef<iDocumentNode> proxynode = config->GetNode ("proxy");
    if (proxynode != NULL)
    {
        //Proxy active?
        csRef<iDocumentNode> node = proxynode->GetNode ("active");
        if (node != NULL)
            proxy.active = node->GetContentsValueAsInt() ? true : false;

        //Get proxy host
        node = proxynode->GetNode ("host");
        if (node != NULL)
            proxy.host = node->GetContentsValue();

        //Get proxy port
        node = proxynode->GetNode ("port");
        if (node != NULL)
            proxy.port = node->GetContentsValueAsInt();
    }
    else
    {
        printf("Proxy node not found (Not needed if not used)\n");
    }

    //Get omitings
    csRef<iDocumentNode> omitNode = config->GetNode ("omit");
    if (omitNode != NULL)
    {
        csRef<iDocumentNodeIterator> node = omitNode->GetNodes();
        while(node->HasNext())
        {
            csRef<iDocumentNode> onode = node->Next();

            if (!strcmp(onode->GetValue(),"file"))
            {
                fileOmits.Push(onode->GetAttributeValue("path"));
                continue;
            }

            if (!strcmp(onode->GetValue(),"dir"))
            {
                dirOmits.Push(onode->GetAttributeValue("path"));
                continue;
            }
        }
    }
    else
    {
        printf("File omitting rules node (<omit>) not found (Server only)\n");
    }

    //Get system files
    csRef<iDocumentNode> sysNode = config->GetNode ("system");
    if (sysNode != NULL)
    {
        csRef<iDocumentNodeIterator> node = sysNode->GetNodes();
        while(node->HasNext())
        {
            csRef<iDocumentNode> snode = node->Next();

            if (!strcmp(snode->GetValue(),"file"))
            {
                systemFiles.Push(snode->GetAttributeValue("path"));
                continue;
            }
        }
    }
    else
    {
        printf("System file rules (<system>) not found! Without this the updater may remove it's own critical files! Please fix!\n");
    }
    
    //Get linux system files
    csRef<iDocumentNode> linuxSysNode = config->GetNode ("linux_system");
    if (linuxSysNode != NULL)
    {
        csRef<iDocumentNodeIterator> node = linuxSysNode->GetNodes();
        while(node->HasNext())
        {
            csRef<iDocumentNode> snode = node->Next();

            if (!strcmp(snode->GetValue(),"file"))
            {
                linuxSystemFiles.Push(snode->GetAttributeValue("path"));
                continue;
            }
        }
    }
    

    //Get critical files
    csRef<iDocumentNode> crtNode = config->GetNode ("critical");
    if (crtNode != NULL)
    {
        csRef<iDocumentNodeIterator> node = crtNode->GetNodes();
        while(node->HasNext())
        {
            csRef<iDocumentNode> cnode = node->Next();

            if (!strcmp(cnode->GetValue(),"file"))
            {
                criticalFiles.Push(cnode->GetAttributeValue("path"));
                continue;
            }
        }
    }
    else
    {
        printf("Critical files (<critical>) not found (Server only)\n");
    }

    if (mirrors.Length() == 0)
    {
        psupdaterengine->OutToScreen("No mirrors found! Can't continue!");
        return false;
    }

    if (activeMirror == 0 && mirrors.Length() > 0)
    {
        SetMirror(1);
    }

    return true;
}

void Config::SetBackupMode(bool value)
{
    csRef<iDocumentNode> node = config->GetNode("backup");
    if (!node)
        return;

    node->RemoveNodes();
    node->CreateNodeBefore(CS_NODE_TEXT)->SetValueAsInt(value);
}

bool Config::MakeBackup() const
{
    csRef<iDocumentNode> node = config->GetNode("backup");
    return node && node->GetContentsValueAsInt();
}

void Config::ModuleSetup (csArray<csString>& modules) const
{
    modules.Empty();

    csRef<iDocumentNode> modulenode = config->GetNode ("modules");
    if (!modulenode)
        return;

    csRef<iDocumentNodeIterator> it = modulenode->GetNodes();
    while (it->HasNext())
    {
        csRef<iDocumentNode> node = it->Next();
        if (node->GetType() != CS_NODE_ELEMENT)
            continue;
        if (strcmp (node->GetValue(), "module") != 0)
        {
            fprintf (stderr, "Warning: Expected module node got '%s'.\n",
                    node->GetValue());
            continue;
        }
        
        const char* module = node->GetContentsValue();
        if (module)
            modules.Push(module);
    }

    if (GetIncludeOS())
        modules.Push(GetOSModule());
}

void Config::SetIncludeOS(bool value)
{
    csRef<iDocumentNode> autonode = config->GetNode("autoinclude_os");
    if (!autonode)
        return;

    autonode->RemoveNodes();
    autonode->CreateNodeBefore(CS_NODE_TEXT)->SetValueAsInt(value);

    psupdaterengine->GetUpdater()->RefreshModules();
}

bool Config::GetIncludeOS() const
{
    csRef<iDocumentNode> autonode = config->GetNode("autoinclude_os");
    return autonode && autonode->GetContentsValueAsInt();
}

bool Config::IsUsingModule(const char* name)
{
    csRef<iDocumentNode> modulenode = config->GetNode("modules");
    if (!modulenode)
        return false;

    csRef<iDocumentNodeIterator> it = modulenode->GetNodes();
    while (it->HasNext())
    {
        csRef<iDocumentNode> node = it->Next();
        if (node->GetType() != CS_NODE_ELEMENT)
            continue;
        if (strcmp (node->GetValue(), "module") != 0)
        {
            fprintf (stderr, "Warning: Expected module node got '%s'.\n",
                    node->GetValue());
            continue;
        }
        
        csString module(node->GetContentsValue());
        if (module == name)
            return true;
    }

    return false;
}

void Config::AddModule(const char* name)
{
    csRef<iDocumentNode> modulenode = config->GetNode("modules");
    if (!modulenode)
        return;

    // Check if it's already included first
    if (IsUsingModule(name))
        return;

    // Add the new node
    csRef<iDocumentNode> node = modulenode->CreateNodeBefore(CS_NODE_ELEMENT);
    node->SetValue("module");
    node->CreateNodeBefore(CS_NODE_TEXT)->SetValue(name);

    psupdaterengine->GetUpdater()->RefreshModules();
}

void Config::RemoveModule(const char* name)
{
    csRef<iDocumentNode> modulenode = config->GetNode("modules");
    if (!modulenode)
        return;

    csRef<iDocumentNodeIterator> it = modulenode->GetNodes();
    while (it->HasNext())
    {
        csRef<iDocumentNode> node = it->Next();
        if (node->GetType() != CS_NODE_ELEMENT || strcmp(node->GetValue(), "module") != 0)
            continue;

        csString module(node->GetContentsValue());
        if (module == name)
        {
            csRef<iDocumentNode> parent = node->GetParent();
            parent->RemoveNode(node);
        }
    }

    psupdaterengine->GetUpdater()->RefreshModules();
}

void Config::SetModuleUse(const char* name, bool use)
{
    if (use)
        AddModule(name);
    else
        RemoveModule(name);
}

void Config::SetProxy(bool active, const char* host, int port)
{
    proxy.active = active;
    proxy.host = host;
    proxy.port = port;

    csRef<iDocumentNode> proxynode = config->GetNode("proxy");
    if (proxynode)
    {
        proxynode->RemoveNodes();

        csRef<iDocumentNode> node1 = proxynode->CreateNodeBefore(CS_NODE_ELEMENT);
        node1->SetValue("active");
        node1->CreateNodeBefore(CS_NODE_TEXT)->SetValueAsInt(active);

        proxynode->CreateNodeBefore(CS_NODE_COMMENT)->SetValue(" Set to 1 for activation ");

        csRef<iDocumentNode> node2 = proxynode->CreateNodeBefore(CS_NODE_ELEMENT);
        node2->SetValue("host");
        node2->CreateNodeBefore(CS_NODE_TEXT)->SetValue(host);

        proxynode->CreateNodeBefore(CS_NODE_COMMENT)->SetValue(" The host WITHOUT http:// or any other prefix ");

        csRef<iDocumentNode> node3 = proxynode->CreateNodeBefore(CS_NODE_ELEMENT);
        node3->SetValue("port");
        node3->CreateNodeBefore(CS_NODE_TEXT)->SetValueAsInt(port);

        proxynode->CreateNodeBefore(CS_NODE_COMMENT)->SetValue(" The port to use ");
    }
    else
    {
        printf("Proxy node not found; cannot save.\n");
    }

    psupdaterengine->GetUpdater()->GetDownloader()->SetProxy( active, host, port );
}

const char* Config::GetOSModule()
{
#ifdef CS_PLATFORM_WIN32
    return "win32";
#elif defined CS_PLATFORM_MACOSX
    return "macosx";
#elif defined CS_PLATFORM_UNIX && (SIZEOF_LONG == 4)
    return "linux32";
#elif defined CS_PLATFORM_UNIX && (SIZEOF_LONG == 8)
    return "linux64";
#else
    return "other_os";
#endif
}

const char* Config::GetOSName()
{
#ifdef CS_PLATFORM_WIN32
    return "Windows";
#elif defined CS_PLATFORM_MACOSX
    return "Mac OS X";
#elif defined CS_PLATFORM_UNIX && (SIZEOF_LONG == 4)
    return "32bit Linux";
#elif defined CS_PLATFORM_UNIX && (SIZEOF_LONG == 8)
    return "64bit Linux";
#else
    return "Unknown OS";
#endif
}


bool Config::ShouldIgnoreFile(const char* file)
{
    // Check if we should ignore this file or not
    for (size_t i = 0; i < fileOmits.Length();i++)
    {
        csString str = fileOmits.Get(i);
        if (str == file)
        {
            return true;
        }
        else
        {
            if (str.GetAt(0) == '*')
            {
                //If we have a * as the first char, see if the string contains str
                if (strstr(file,str.Slice(1,str.Length())))
                {
                    return true;
                }
            }
        }
    }

    return false;
}

bool Config::ShouldIgnoreDir(const char* dir)
{
    // Check if we should ignore this file or not
    for (size_t i = 0; i < dirOmits.Length();i++)
    {
        csString str = dirOmits.Get(i);
        if (str == dir)
        {
            return true;
        }
        else
        {
            if (str.GetAt(0) == '*')
            {
                //If we have a * as the first char, see if the string contains str
                if (strstr(dir,str.Slice(1,str.Length())))
                {
                    return true;
                }
            }
        }
    }

    return false;
}

Mirror* Config::GetMirror(unsigned int id)
{
    for (size_t i = 0;i < mirrors.Length(); i++)
    {
        Mirror* mirror = mirrors.Get(i);
        if (mirror->id == id)
            return mirror;
    }
    return NULL;
}

Mirror* Config::GetMirror()
{
    return GetMirror(activeMirror);
}

csString Config::GetCurrentRepositoryURL()
{
    Mirror* mirror = GetMirror();
    csString url;
    url = mirror->baseURL;
    url += mirror->repositoryFile;

    return url;
}

csString Config::GetCurrentVersionURL()
{
    Mirror* mirror = GetMirror();
    csString url;
    url = mirror->baseURL;
    url += mirror->versionFile;

    return url;
}

csString Config::GetCurrentFilesDirectory()
{
    Mirror* mirror = GetMirror();
    csString url;
    url = mirror->baseURL;
    url += mirror->filesDirectory;

    // We assume no trailing / symbol.
    if(url.GetAt(url.Length() - 1) == '/')
        url.Truncate(url.Length() - 1);

    return url;
}

void Config::SetNextMirror()
{
    size_t newArrM = arrayMirror;

    if (mirrors.Length() == 0)
        return;

    if ( (unsigned int)mirrors.Length() < (newArrM +1))
        newArrM = 1;
    else
        newArrM++;

    Mirror* mirror = mirrors.Get(newArrM-1);
    arrayMirror  = newArrM;
    activeMirror = mirror->id;
}

void Config::SetMirror(unsigned int id)
{
    Mirror* mirror = NULL;

    size_t i;
    for (i = 0;i < mirrors.Length(); i++)
    {
        mirror = mirrors.Get(i);
        if (mirror->id == id)
            break;
    }

    if (!mirror)
    {
        printf("Tried to set non-existing mirror!\n");
        return;
    }

    arrayMirror = i;
    activeMirror = id;    
}

void Config::ResetFailures()
{
    for (size_t i = 0;i < mirrors.Length();i++)
    {
        Mirror* mirror = mirrors.Get(i);
        mirror->working = true;
    }
}

bool Config::SetNextWorkingMirror()
{
    size_t newArrM = arrayMirror;

    for (size_t i = 0; i < mirrors.Length();i++)
    {
        if(mirrors.Get(i)->GetID() == (unsigned int)-1)
            continue;

        if ((unsigned int)mirrors.Length() < (newArrM +1))
            newArrM = 1;
        else
            newArrM++;

        Mirror* mirror = mirrors.Get(newArrM-1);
        if (mirror->working)
        {
            arrayMirror     = newArrM;
            activeMirror    = mirror->id;
            return true;
        }
    }

    // Didn't find any working mirror
    return false;
}

bool Config::IsConfig(const char* file)
{
    //First see if the ext is right
    csString filename = file;
    csString ext = filename.Slice(filename.FindLast('.') +1,filename.Length());

    size_t i;
    for(i = 0;i < configExt.Length();i++)
    {
        if (ext == configExt[i])
            return true;
    }

    // Check if the file is in the config file list
    for (i = 0; i < configFiles.Length();i++)
    {
        csString str = configFiles.Get(i);
        if (str == filename)
        {
            return true;
        }
        else
        {
            if (str.GetAt(0) == '*')
            {
                //If we have a * as the first char, see if the string contains str
                if (strstr(filename,str.Slice(1,str.Length())))
                {
                    return true;
                }
            }
        }
    }

    return false;

}

const csString Config::GetSortedPath(const char* path,const char* currentModule)
{
    csString pathStr = path;
    csString filename = pathStr.Slice(pathStr.FindLast('/') +1,pathStr.Length());
    csString pathOnly = pathStr.Slice(0,pathStr.Length() - filename.Length());

    csString ext = filename.Slice(filename.FindLast('.') +1,filename.Length());
    
    for (size_t i = 0; i < sortings.Length(); i++)
    {
        SortEntry* entry = sortings[i];

        if (entry->file)
        {
            if (entry->path == path)
            {
                if(entry->destFile != "")
                    return entry->to + "/" + pathOnly + entry->destFile;
                else
                    return entry->to + "/" + pathStr;
            }
        }
        else
        {
            if (strstr(path,entry->ext))
                return entry->to + "/" + pathStr;
        }
    }

    return csString(currentModule) + "/" + path;
    
}

bool Config::HasCustomSortDest(const char* path)
{
    for (size_t i = 0; i < sortings.Length(); i++)
    {
        SortEntry* entry = sortings[i];

        if (entry->file)
        {
            if (entry->path == path)
            {
                if(entry->destFile != "")
                    return true;
            }
        }
    }
    return false;
}

bool Config::CheckCriticalFiles()
{
    bool missing = false;

    for (size_t i = 0; i < criticalFiles.Length();i++)
    {
        csString file = criticalFiles.Get(i);
        if(!fopen(file,"r"))
        {
            printf("WARNING! Critical file %s not found!\n",file.GetData());
            missing = true;
        }
    }

    return !missing;
}

} // end of namespace updater
