/////////////////////////////////////////////////////////////////////////////
// Name:        packagewxp.cpp
// Purpose:     wxPackageXMLDescriptor
// Author:      Francesco Montorsi
// Modified by:
// Created:     2006-06-29
// RCS-ID:      $Id: packagewxp.cpp,v 1.15 2007/01/28 15:56:11 frm Exp $
// Copyright:   (c) Francesco Montorsi
// Licence:     wxWidgets licence
/////////////////////////////////////////////////////////////////////////////


// For compilers that support precompilation, includes "wx/wx.h".
#include "wx/wxprec.h"

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#ifndef WX_PRECOMP
#include "wx/wx.h"
#endif

// includes
#include <wx/fileconf.h>
#include <wx/mstream.h>
#include <wx/apptrait.h>

#include "wxp/packagewxp.h"

// RTTI
IMPLEMENT_DYNAMIC_CLASS(wxPackageXMLDescriptor, wxPackageXML)

// global objects
wxPackageXMLDescriptor wxEmptyPackageXMLDescriptor;


// ----------------------------------------------------------------------------
// helper save functions for wxPackageXMLDescriptor::ResetEnumeration
// ----------------------------------------------------------------------------

void FormatAuthor(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    n->AddProperty(wxT("name"), pkg.GetAuthors().Item(i));
    if (!pkg.GetAuthorsHref().Item(i).IsEmpty())
        n->AddProperty(wxT("href"), pkg.GetAuthorsHref().Item(i));
}

void FormatMaintainer(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    n->AddProperty(wxT("name"), pkg.GetMaintainers().Item(i));
    if (!pkg.GetMaintainersHref().Item(i).IsEmpty())
        n->AddProperty(wxT("href"), pkg.GetMaintainersHref().Item(i));
}

void FormatMirror(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    n->AddProperty(wxT("href"), pkg.GetDownloadHref(i));
}

void FormatRef(wxXmlNode *n, const wxPackageDataWithRef &ref)
{
    n->AddProperty(wxT("href"), ref.GetHref());
    n->AddProperty(wxT("localref"), ref.GetLocalRef());
}

void FormatDoc(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    FormatRef(n, pkg.GetDocumentation(i));
}

void FormatScreenshot(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    FormatRef(n, pkg.GetScreenshot(i));
}

void FormatSupportedPort(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    wxPortId id = (wxPortId)pkg.GetSupportedPorts().Item(i);
    n->AddProperty(wxT("name"), wxPlatformInfo::GetPortIdName(id, false));
    n->AddProperty(wxT("versions"), pkg.GetSupportedVersions().Item(i));
}

void FormatTestedPort(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    wxPortId id = (wxPortId)pkg.GetTestedPorts().Item(i);
    n->AddProperty(wxT("name"), wxPlatformInfo::GetPortIdName(id, false));
    n->AddProperty(wxT("versions"), pkg.GetTestedVersions().Item(i));
}

void FormatRequiredDependency(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    static wxPackageDependencyArray arr;

    if (i == 0)
        arr = pkg.GetDependencies().GetDependenciesOfType(wxPDT_REQUIRED);

    n->AddProperty(wxT("name"), arr[i].GetName());
    n->AddProperty(wxT("versions"), arr[i].GetVersion().GetAsString());
}

void FormatSuggestedDependency(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    static wxPackageDependencyArray arr;

    if (i == 0)
        arr = pkg.GetDependencies().GetDependenciesOfType(wxPDT_SUGGESTED);

    n->AddProperty(wxT("name"), arr[i].GetName());
    n->AddProperty(wxT("versions"), arr[i].GetVersion().GetAsString());
}

void FormatExclude(wxXmlNode *n, size_t i, const wxPackageInfo &pkg)
{
    static size_t files, dirs, patterns;
    
    if (i == 0)
    {
        files = pkg.GetExcluded(wxPEM_FILE).GetCount();
        dirs = pkg.GetExcluded(wxPEM_DIRECTORY).GetCount();
        patterns = pkg.GetExcluded(wxPEM_PATTERN).GetCount();
    }

    // create first the excluded FILE list
    if (i < files)
    {
        wxString f = pkg.GetExcluded(wxPEM_FILE).Item(i);
        n->AddProperty(wxT("file"), wxT("file:///") + f);
    }
    else if (i < files+dirs)    // then the DIRECTORY list
    {
        wxString d = pkg.GetExcluded(wxPEM_DIRECTORY).Item(i-files);
        n->AddProperty(wxT("directory"), wxT("file:///") + d);
    }
    else        // last the PATTERN list
    {
        wxString p = pkg.GetExcluded(wxPEM_PATTERN).Item(i-files-dirs);
        n->AddProperty(wxT("pattern"), p);
    }
}




// ----------------------------------------------------------------------------
// wxPackageXMLDescriptor - save API
// ----------------------------------------------------------------------------

void SetProp(wxXmlNode *node, const wxString &name, const wxString &val)
{
    /*wxXmlProperty *p = node->GetProperties();
    while (p)
    {
        if (p->GetName() == name)
        {
            p->SetValue(val);
            return;
        }

        p = p->GetNext();
    }
    
    NOT NECESSARY SINCE REMOVEPROPS WAS USED ON THE TREE
    */

    if (val.IsEmpty())
        return;     // do not add empty properties

    // the property is missing - create it !
    node->AddProperty(name, val);
}

void wxPackageXMLDescriptor::SetRefProp(wxXmlNode *node, const wxPackageDataWithRef &ref)
{
    SetProp(node, wxT("href"), ref.GetHref());
    SetProp(node, wxT("localref"), ref.GetLocalRef());
}

void wxPackageXMLDescriptor::SetNodeContent(wxXmlNode *node, const wxString &text)
{
    wxXmlNode *p = node->GetChildren();
    bool found = false;

    while (p)
    {
        if (p->GetType() == wxXML_TEXT_NODE ||
            p->GetType() == wxXML_CDATA_SECTION_NODE)
        {
            if (!found)
            {
                p->SetContent(text);
                found = true;
            }
            else
            {
                // remove all other TEXT & CDATA nodes
                wxXmlNode *next = p->GetNext();
                node->RemoveChild(p);
                p = next;

                if (!p)
                    return;
            }
        }

        p = p->GetNext();
    }

    if (found) return;

    // no text/cdata child found?
    // create it ourselves
    if (!text.IsEmpty())
    {
        wxXmlNode *content = new wxXmlNode(wxXML_TEXT_NODE, wxEmptyString, text);
        node->AddChild(content);
    }
}

void wxPackageXMLDescriptor::RemoveAllChildrenFrom(wxXmlNode *node, const wxString &name)
{
    wxXmlNode *p = node->GetChildren();
    while (p)
    {
        if (name.IsEmpty() ||
            p->GetName() == name)
        {
            wxXmlNode *next = p->GetNext();
            node->RemoveChild(p);
            delete p;
            p = next;
        }
        else
            p = p->GetNext();
    }
}

void wxPackageXMLDescriptor::RemoveAllPropertiesFrom(wxXmlNode *node, const wxString &name)
{
    wxXmlProperty *p = node->GetProperties();
    while (p)
    {
        if (name.IsEmpty() ||
            p->GetName() == name)
        {
            wxXmlProperty *next = p->GetNext();
            delete p;
            p = next;
        }
        else
            p = p->GetNext();
    }
    node->SetProperties(NULL);

    // recursively remove all properties
    wxXmlNode *pn = node->GetChildren();
    while (pn)
    {
        RemoveAllPropertiesFrom(pn);
        pn = pn->GetNext();
    }
}

void wxPackageXMLDescriptor::ResetEnumeration(wxXmlNode *parent,
                                              int parentIndentLevel,
                                              const wxString &nodename,
                                              size_t max,
                                              FormatNodeFnc f,
                                              const wxPackageInfo &pkg,
                                              bool eraseChildren)
{
    if (eraseChildren)
        RemoveAllChildrenFrom(parent);

    for (size_t i=0; i < max; i++)
    {
        wxXmlNode *n = new wxXmlNode(wxXML_ELEMENT_NODE, nodename);

        f(n, i, pkg);

        AddChildIndentation(parent, parentIndentLevel+1);
        parent->AddChild(n);
    }

    if (max > 0)
        AddChildIndentation(parent, parentIndentLevel);
}

void wxPackageXMLDescriptor::CreateSiblingIfMissing(wxXmlNode *prev, wxXmlNode **node,
                                                    const wxString &name)
{
    wxASSERT(node && prev);

    if (*node)
        return; // the sibling already exists

    // the sibling is missing, create it
    *node = new wxXmlNode(wxXML_ELEMENT_NODE, name);
    (*node)->SetParent(prev->GetParent());

    // add indentation for this child, before appending it to the sibling list
    int level = prev->GetDepth();
    wxXmlNode *indent = AddSiblingIndentation(prev, level);
    wxXmlNode *oldnext = indent->GetNext();
    indent->SetNext(*node);
    (*node)->SetNext(oldnext);

    // add indentation inside the just-created node, if necessary
    if (level == 1)         // don't put e.g. a newline inside the <id> tag
        AddChildIndentation(*node, level);
}

void wxPackageXMLDescriptor::CreateChildIfMissing(wxXmlNode *parent, wxXmlNode *prev,
                                                  wxXmlNode **node, const wxString &name,
                                                  int level)
{
    wxASSERT(node && parent);

    if (*node)
        return; // the child already exists

    // the child is missing, create it
    *node = new wxXmlNode(wxXML_ELEMENT_NODE, name);
    (*node)->SetParent(parent);
    parent->InsertChild(*node, prev);

    // add indentation both inside the new node (if necessary) and just before it
    if (level == 1)             // don't put e.g. a newline inside the <id> tag
        AddChildIndentation(*node, level);
    InsertChildIndentation(parent, *node, level);
}

void wxPackageXMLDescriptor::CreateMissingNodes()
{
    if (!GetRoot())
    {
        // create root first
        SetRoot(new wxXmlNode(wxXML_ELEMENT_NODE, wxT("wxpackage")));
        AddChildIndentation(GetRoot(), 0);
    }

    // create main section nodes if missing
    CreateChildIfMissing(GetRoot(), NULL, &m_pBasics, wxT("basics"), 1);
    CreateSiblingIfMissing(m_pBasics, &m_pPackaging, wxT("packaging"));
    CreateSiblingIfMissing(m_pPackaging, &m_pLicensing, wxT("licensing"));
    CreateSiblingIfMissing(m_pLicensing, &m_pDetails, wxT("details"));
    CreateSiblingIfMissing(m_pDetails, &m_pDep, wxT("dependencies"));
    CreateSiblingIfMissing(m_pDep, &m_pBuildSystem, wxT("build-system"));

    // BASIC data
    CreateChildIfMissing(m_pBasics, NULL, &m_pId, wxT("id"), 2);
    CreateSiblingIfMissing(m_pId, &m_pDescription, wxT("description"));
    CreateSiblingIfMissing(m_pDescription, &m_pKeywords, wxT("keywords"));
    CreateSiblingIfMissing(m_pKeywords, &m_pCategory, wxT("category"));
    CreateSiblingIfMissing(m_pCategory, &m_pWebsite, wxT("website"));
    CreateSiblingIfMissing(m_pWebsite, &m_pCreationDate, wxT("creation-date"));

    // PACKAGING data
    CreateChildIfMissing(m_pPackaging, NULL, &m_pRoot, wxT("root"), 2);
    CreateSiblingIfMissing(m_pRoot, &m_pFileName, wxT("filename"));
    CreateSiblingIfMissing(m_pFileName, &m_pContents, wxT("contents"));
    CreateSiblingIfMissing(m_pContents, &m_pCompress, wxT("compress"));
    CreateSiblingIfMissing(m_pCompress, &m_pUpload, wxT("upload"));

    // LICENSING data
    CreateChildIfMissing(m_pLicensing, NULL, &m_pLicense, wxT("license"), 2);
    CreateSiblingIfMissing(m_pLicense, &m_pOrganization, wxT("organization"));
    CreateSiblingIfMissing(m_pOrganization, &m_pCost, wxT("cost"));
    CreateSiblingIfMissing(m_pCost, &m_pCredits, wxT("credits"));
    CreateSiblingIfMissing(m_pCredits, &m_pCopyright, wxT("copyright"));
    CreateSiblingIfMissing(m_pCopyright, &m_pAuthors, wxT("authors"));
    CreateSiblingIfMissing(m_pAuthors, &m_pMaintainers, wxT("maintainers"));

    // DETAILS data
    CreateChildIfMissing(m_pDetails, NULL, &m_pStatus, wxT("status"), 2);
    CreateSiblingIfMissing(m_pStatus, &m_pProgLanguage, wxT("proglanguage"));
    CreateSiblingIfMissing(m_pProgLanguage, &m_pSamples, wxT("samples"));
    CreateSiblingIfMissing(m_pSamples, &m_pDownloads, wxT("download"));
    CreateSiblingIfMissing(m_pDownloads, &m_pLogo, wxT("logo"));
    CreateSiblingIfMissing(m_pLogo, &m_pRSS, wxT("rss"));
    CreateSiblingIfMissing(m_pRSS, &m_pScreenshots, wxT("screenshots"));
    CreateSiblingIfMissing(m_pScreenshots, &m_pDocs, wxT("documentation"));

    // DEPENDENCIES data
    CreateChildIfMissing(m_pDep, NULL, &m_pSupportedPorts, wxT("supported-ports"), 2);
    CreateSiblingIfMissing(m_pSupportedPorts, &m_pTestedPorts, wxT("tested-ports"));
    CreateSiblingIfMissing(m_pTestedPorts, &m_pDependencies[wxPDT_REQUIRED],
                           wxT("dependency"));
    CreateSiblingIfMissing(m_pDependencies[wxPDT_REQUIRED],
                           &m_pDependencies[wxPDT_SUGGESTED], wxT("dependency"));

    // BUILD SYSTEM data
    CreateChildIfMissing(m_pBuildSystem, NULL, &m_pFormats, wxT("formats"), 2);
    CreateSiblingIfMissing(m_pFormats, &m_pOptions, wxT("options"));
    CreateSiblingIfMissing(m_pOptions, &m_pStages, wxT("stages"));
}

void wxPackageXMLDescriptor::SetPackageInfo(const wxPackageInfo &pkg)
{
    // NOTE: we don't want a
    //          wxASSERT(pkg.IsOk());
    //       here as we want to allow the user to save also invalid wxPackageInfo
    //       object (this is useful when e.g. the user wants to stop working on
    //       a WXP file and resume later)...

    CreateMissingNodes();

    // recursively remove all properties from the nodes of this document:
    // this way properties which have not been recognized when loading the
    // document are removed; SetProp() will regenerate the valid ones.
    RemoveAllPropertiesFrom(GetRoot());

    // the version of this WXP file
    SetProp(GetRoot(), wxT("version"), WXP_VERSION_STRING);

    // BASIC data
    SetProp(m_pId, wxT("name"), pkg.GetName());
    SetProp(m_pId, wxT("version"), pkg.GetVersion());
    SetNodeContent(m_pDescription, pkg.GetDescription());
    SetNodeContent(m_pKeywords, pkg.GetKeywordsStr());
    SetProp(m_pCategory, wxT("primary"), 
            pkg.GetPrimaryCategory() == wxPC_INVALID ? 
                wxT("invalid") : pkg.GetPrimaryCategoryStr());
    SetProp(m_pCategory, wxT("secondary"), 
            pkg.GetSecondaryCategory() == wxPC_INVALID ? 
                wxT("invalid") : pkg.GetSecondaryCategoryStr());
    SetProp(m_pWebsite, wxT("href"), pkg.GetWebsite());
    SetNodeContent(m_pCreationDate, pkg.GetCreationDate().Format(wxT("%d %B %Y")));

    // PACKAGING data
    SetNodeContent(m_pRoot, pkg.GetRelativeRoot());
    SetNodeContent(m_pUpload, pkg.GetUploadDestination());
    SetNodeContent(m_pCompress, pkg.GetCompressionModeStr());
    SetNodeContent(m_pFileName, pkg.GetOutputFileName());

    size_t total = 0;
    total += pkg.GetExcluded(wxPEM_FILE).GetCount();
    total += pkg.GetExcluded(wxPEM_DIRECTORY).GetCount();
    total += pkg.GetExcluded(wxPEM_PATTERN).GetCount();
    ResetEnumeration(m_pContents, 2, wxT("exclude"),
                     total, FormatExclude, pkg);

    // LICENSING data
    SetProp(m_pLicense, wxT("name"), pkg.GetLicenseRef().GetData());
    SetProp(m_pLicense, wxT("type"),
                pkg.GetLicenseType() == wxPLT_INVALID ? 
                    wxT("invalid") : pkg.GetLicenseTypeStr());
    SetRefProp(m_pLicense, pkg.GetLicenseRef());
    SetProp(m_pOrganization, wxT("name"), pkg.GetOrganization());
    SetProp(m_pOrganization, wxT("href"), pkg.GetOrganizationHref());
    SetNodeContent(m_pCost, pkg.GetCost());
    SetNodeContent(m_pCredits, pkg.GetCredits());
    SetNodeContent(m_pCopyright, pkg.GetCopyright());

    ResetEnumeration(m_pAuthors, 2, wxT("author"), pkg.GetAuthors().GetCount(),
                     FormatAuthor, pkg);
    ResetEnumeration(m_pMaintainers, 2, wxT("maintainer"), pkg.GetMaintainers().GetCount(),
                     FormatMaintainer, pkg);

    // DETAILS data
    SetNodeContent(m_pStatus,
                    pkg.GetDevelopmentStatus() == wxPDS_INVALID ? 
                        wxT("invalid") : pkg.GetDevelopmentStatusStr());
    SetNodeContent(m_pProgLanguage, 
                    pkg.GetProgLanguage() == wxPPL_INVALID ? 
                        wxT("invalid") : pkg.GetProgLanguageStr());
    SetProp(m_pRSS, wxT("href"), pkg.GetRSSHref());
    SetRefProp(m_pLogo, pkg.GetLogoRef());
    SetProp(m_pSamples, wxT("number"),
            wxString::Format(wxT("%d"), pkg.GetNumberOfSamples()));

    ResetEnumeration(m_pDownloads, 2, wxT("mirror"), pkg.GetDownloadHref().GetCount(),
                     FormatMirror, pkg);
    ResetEnumeration(m_pScreenshots, 2, wxT("image"), pkg.GetScreenshots().GetCount(),
                     FormatScreenshot, pkg);
    ResetEnumeration(m_pDocs, 2, wxT("doc"), pkg.GetDocumentation().GetCount(),
                     FormatDoc, pkg);

    // DEPENDENCIES data
    if (pkg.GetSupportedPortFlag() == wxGetFullPortIdList() &&
        pkg.HasSameSupportedPortVersions())
    {
        // it's easier to write a <wxport name="any" /> tag
        RemoveAllChildrenFrom(m_pSupportedPorts);

        wxXmlNode *n = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("wxport"));
        n->AddProperty(wxT("name"), wxT("any"));
        n->AddProperty(wxT("versions"), pkg.GetSupportedVersions().Item(0));

        AddChildIndentation(m_pSupportedPorts, 3);
        m_pSupportedPorts->AddChild(n);
        AddChildIndentation(m_pSupportedPorts, 2);
    }
    else
    {
        ResetEnumeration(m_pSupportedPorts, 2, wxT("wxport"), 
                         pkg.GetSupportedPorts().GetCount(), FormatSupportedPort, pkg);
    }

    if (pkg.GetTestedPortFlag() == wxGetFullPortIdList() &&
        pkg.HasSameTestedPortVersions())
    {
        // it's easier to write a <wxport name="any" /> tag
        RemoveAllChildrenFrom(m_pTestedPorts);

        wxXmlNode *n = new wxXmlNode(wxXML_ELEMENT_NODE, wxT("wxport"));
        n->AddProperty(wxT("name"), wxT("any"));
        n->AddProperty(wxT("versions"), pkg.GetSupportedVersions().Item(0));

        AddChildIndentation(m_pTestedPorts, 3);
        m_pTestedPorts->AddChild(n);
        AddChildIndentation(m_pTestedPorts, 2);
    }
    else
    {
        ResetEnumeration(m_pTestedPorts, 2, wxT("wxport"), 
                         pkg.GetTestedPorts().GetCount(), FormatTestedPort, pkg);
    }

    SetProp(m_pDependencies[wxPDT_REQUIRED], wxT("type"), wxT("required"));
    ResetEnumeration(m_pDependencies[wxPDT_REQUIRED], 2, wxT("id"),
                     pkg.GetDependencies().GetDependenciesOfType(wxPDT_REQUIRED).GetCount(),
                     FormatRequiredDependency, pkg);

    SetProp(m_pDependencies[wxPDT_SUGGESTED], wxT("type"), wxT("suggested"));
    ResetEnumeration(m_pDependencies[wxPDT_SUGGESTED], 2, wxT("id"),
                     pkg.GetDependencies().GetDependenciesOfType(wxPDT_SUGGESTED).GetCount(),
                     FormatSuggestedDependency, pkg);

    // BUILD SYSTEM data

    SetProp(m_pBuildSystem, wxT("type"), 
                pkg.GetBuildSystemType() == wxPBST_INVALID ? 
                    wxT("invalid") : pkg.GetBuildSystemTypeStr().Lower());
    SetProp(m_pBuildSystem, wxT("localref"), pkg.GetBuildSystemHref());
    SetNodeContent(m_pFormats,
            wxPackageCompilerFormatList2String(pkg.GetSupportedBuildSysFormats()));

    // remove from the option array all option presets in use
	wxArrayString usedpresets = pkg.GetUsedOptionPresets();
    wxPackageCommandOptionArray opt = pkg.GetBuildSysOptions();
    for (size_t i=0; i < usedpresets.GetCount(); i++)
        opt.Remove(wxPresetXMLDescriptor::s_hashmapOptions[usedpresets[i]]);

    // save build system options
    SetProp(m_pOptions, wxT("presets"), wxArray2String(usedpresets));
    RemoveAllChildrenFrom(m_pOptions);

    // write the new if+option list
    //AddChildIndentation(m_pOptions, 3);
    wxIfNodeListManager mngr(this, m_pOptions);
    mngr.SaveOptions(opt, 3);
    AddChildIndentation(m_pOptions, 2);

    // save stages
    RemoveAllChildrenFrom(m_pStages);

    // recreate target lists
    for (size_t i=0; i < wxPBSS_MAX; i++)
    {
        wxPackageBuildSystemStage s = wxIdx2PackageBuildSystemStage(i);

        wxXmlNode *p = new wxXmlNode(wxXML_ELEMENT_NODE,
                                     wxPackageBuildSystemStage2String(s).Lower());
        p->AddProperty(wxT("targets"), pkg.GetCmdForStage(s).GetTargetsStr());

        AddChildIndentation(m_pStages, 3);
        m_pStages->AddChild(p);
    }

    AddChildIndentation(m_pStages, 2);

    // save commands
    wxStringHashMap vars;
    wxString preset = pkg.GetUsedCommandPreset(vars);
    if (!preset.IsEmpty())
    {
        // use the command preset
        wxXmlNode *n = new wxXmlNode(wxXML_ELEMENT_NODE, preset);

        wxStringHashMap::iterator it;
        for( it = vars.begin(); it != vars.end(); ++it )
            n->AddProperty(it->first, it->second);

        AddChildIndentation(m_pStages, 3);
        m_pStages->AddChild(n);
        AddChildIndentation(m_pStages, 2);
    }
    else
    {
        // write our list of commands for each stage
        for (size_t i=0; i < wxPBSS_MAX; i++)
        {
            wxPackageBuildSystemStage s = wxIdx2PackageBuildSystemStage(i);
            wxPackageCommandArray cmd = pkg.GetCmdForStage(s);
            wxString name = wxPackageBuildSystemStage2String(s).Lower();

            if (cmd.GetCount() == 0)
                continue;

            AddChildIndentation(m_pStages, 3);
            m_pStages->AddChild(new wxXmlNode(wxXML_COMMENT_NODE, wxEmptyString,
                                wxString::Format(wxT(" %s stage commands "), name.c_str())));

            for (size_t j=0; j<cmd.GetCount(); j++)
                cmd[j].GetCondition().SetStages(s);

            wxIfNodeListManager mngr(this, m_pStages);
            mngr.SaveCommands(cmd, 3);
            AddChildIndentation(m_pStages, 2);
        }
    }
}



// ----------------------------------------------------------------------------
// wxPackageXMLDescriptor - load API
// ----------------------------------------------------------------------------

wxString wxPackageXMLDescriptor::GetHrefProp(const wxXmlNode *p) const
{
    wxString ref = p->GetPropVal(wxT("href"), wxEmptyString);

    // for security reasons, avoid loading of files with unknown protocols
    if (!ref.StartsWith(wxT("http://")))
        return wxEmptyString;
    return ref;
}

wxString wxPackageXMLDescriptor::GetLocalRefProp(const wxXmlNode *p) const
{
    wxString ref = p->GetPropVal(wxT("localref"), wxEmptyString);

    // for security reasons, avoid loading of files with unknown protocols
    // or that do not refer to the root of the package (i.e. using only file://).
    if (!ref.StartsWith(wxT("file:///")))
        return wxEmptyString;
    return ref;
}

bool wxPackageXMLDescriptor::Parse()
{ 
    // parse this package
    if (!GetRoot() ||
        GetRoot()->GetName() != wxT("wxpackage"))
        return false;

    // is the version ok?
    wxVersion ver = GetRoot()->GetPropVal(wxT("version"), WXP_VERSION_STRING);
    if (ver != wxVersion(WXP_VERSION_STRING))
    {
        wxLogError(wxT("Invalid package version '%s'; cannot load it."), 
                   ver.GetAsString().c_str());
        return false;
    }

    // DON'T DO VALIDITY CHECK ON LOADED STUFF FROM NOW ON
    // IT'S THE TASK OF wxPackageInfo::IsOk() !!
    //
    // In fact, we want to be able to load also invalid WXP files
    // (just think to e.g. incomplete package descriptors saved by the user 
    //  using the Packager to complete them later).

    wxXmlNode *child = GetRoot()->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("basics"))
        {
            m_pBasics = child;
            if (!LoadBasics(child, m_info))
                return false;

            // say the name of the package we are loading now that we know it
            wxLogDebug(wxT("Parsing %s package..."), m_info.GetName().c_str());
        }
        else if (child->GetName() == wxT("packaging"))
        {
            m_pPackaging = child;
            if (!LoadPackaging(child, m_info))
                return false;
        }
        else if (child->GetName() == wxT("licensing"))
        {
            m_pLicensing = child;
            if (!LoadLicensing(child, m_info))
                return false;
        }
        else if (child->GetName() == wxT("details"))
        {
            m_pDetails = child;
            if (!LoadDetails(child, m_info))
                return false;
        }
        else if (child->GetName() == wxT("dependencies"))
        {
            m_pDep = child;
            if (!LoadDependencies(child, m_info))
                return false;
        }
        else if (child->GetName() == wxT("build-system"))
        {
            m_pBuildSystem = child;
            if (!LoadBuildSystem(child, m_info))
                return false;
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    // NB: do NOT execute any substitution here: we want to remember
    //     all the $(key) values to eventually save them later back
    //     in the WXP !

    return true;
}

bool wxPackageXMLDescriptor::LoadBasics(const wxXmlNode *n, wxPackageInfo &p)
{
    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("id"))
        {
            p.SetName(child->GetPropVal(wxT("name"), wxEmptyString));
            p.SetVersion(child->GetPropVal(wxT("version"), wxEmptyString));

            m_pId = child;
        }
        else if (child->GetName() == wxT("description"))
        {
            p.SetDescription(GetStrippedNodeContent(child));
            m_pDescription = child;
        }
        else if (child->GetName() == wxT("keywords"))
        {
            p.SetKeywords(GetStrippedNodeContent(child));
            m_pKeywords = child;
        }
        else if (child->GetName() == wxT("category"))
        {
            p.SetPrimaryCategoryStr(
                child->GetPropVal(wxT("primary"), wxEmptyString));
            p.SetSecondaryCategoryStr(
                child->GetPropVal(wxT("secondary"), wxEmptyString));
            m_pCategory = child;
        }
        else if (child->GetName() == wxT("website"))
        {
            p.SetWebsite(GetHrefProp(child));
            m_pWebsite = child;
        }
        else if (child->GetName() == wxT("creation-date"))
        {
            p.SetCreationDate(GetStrippedNodeContent(child));
            m_pCreationDate = child;
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    return true;
}

bool wxPackageXMLDescriptor::LoadContentsTag(const wxXmlNode *tag,
                                             wxArrayString *dirs,
                                             wxArrayString *files,
                                             wxArrayString *patterns)
{
    while (tag)
    {
        if (tag->GetName() == wxT("exclude"))
        {
            wxArrayString *arr = NULL;
            wxString str;

            if (tag->HasProp(wxT("directory")))
            {
                str = tag->GetPropVal(wxT("directory"), wxEmptyString);
                arr = dirs;
            }
            else if (tag->HasProp(wxT("file")))
            {
                str = tag->GetPropVal(wxT("file"), wxEmptyString);
                arr = files;
            }
            else if (tag->HasProp(wxT("pattern")))
            {
                str = tag->GetPropVal(wxT("pattern"), wxEmptyString);
                arr = patterns;
            }        

            const wxString prefix = wxT("file:///");
            if (str.StartsWith(prefix))
                str.Remove(0, prefix.Len());

            if (arr) arr->Add(str);
        }
        else
            HandleUnknownTag((wxXmlNode*)tag);

        tag = tag->GetNext();
    }

    return true;
}

bool wxPackageXMLDescriptor::LoadPackaging(const wxXmlNode *n, wxPackageInfo &p)
{
    wxArrayString dirs, files, patterns;

    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("root"))
        {
            wxString root = GetStrippedNodeContent(child);

            // only the '.' and the '/' characters should be part of the relative root!
            bool isok = true;
            for (size_t i=0; i<root.Len(); i++)
                if (root[i] != wxT('.') && root[i] != wxT('/'))
                    isok = false;

            if (isok)
                p.SetRoot(root);
            m_pRoot = child;
        }
        else if (child->GetName() == wxT("contents"))
        {
            if (LoadContentsTag(child->GetChildren(), &dirs, &files, &patterns))
            {
                p.SetExcluded(wxPEM_DIRECTORY, dirs);
                p.SetExcluded(wxPEM_FILE, files);
                p.SetExcluded(wxPEM_PATTERN, patterns);
            }
            m_pContents = child;
        }
        else if (child->GetName() == wxT("compress"))
        {
            p.SetCompressModeStr(GetStrippedNodeContent(child));
            m_pCompress = child;
        }
        else if (child->GetName() == wxT("filename"))
        {
            p.SetFileName(GetStrippedNodeContent(child));
            m_pFileName = child;
        }
        else if (child->GetName() == wxT("upload"))
        {
            p.SetUploadDestination(GetStrippedNodeContent(child));
            m_pUpload = child;
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    return true;
}

bool wxPackageXMLDescriptor::LoadLicensing(const wxXmlNode *n, wxPackageInfo &p)
{
    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("license"))
        {
            wxPackageDataWithRef license(
                    child->GetPropVal(wxT("name"), wxT("wxWidgets license")),
                    GetHrefProp(child),
                    GetLocalRefProp(child));

            p.SetLicenseRef(license);
            p.SetLicenseTypeStr(child->GetPropVal(wxT("type"), wxT("Open Source")));

            m_pLicense = child;
        }
        else if (child->GetName() == wxT("organization"))
        {
            p.SetOrganization(child->GetPropVal(wxT("name"), wxEmptyString),
                              GetHrefProp(child));

            m_pOrganization = child;
        }
        else if (child->GetName() == wxT("cost"))
        {
            p.SetCost(GetStrippedNodeContent(child));
            m_pCost = child;
        }
        else if (child->GetName() == wxT("credits"))
        {
            p.SetCredits(GetStrippedNodeContent(child));
            m_pCredits = child;
        }
        else if (child->GetName() == wxT("copyright"))
        {
            p.SetCopyright(GetStrippedNodeContent(child));
            m_pCopyright = child;
        }
        else if (child->GetName() == wxT("authors"))
        {
            wxXmlNode *author = child->GetChildren();
            while (author)
            {
                if (author->GetName() == wxT("author") && author->HasProp(wxT("name")))
                {
                    // NB: do not use GetHrefProp() here since it would discard
                    //     email addresses !
                    p.AddAuthor(author->GetPropVal(wxT("name"), wxEmptyString),
                                author->GetPropVal(wxT("href"), wxEmptyString));
                }

                author = author->GetNext();
            }

            m_pAuthors = child;
        }
        else if (child->GetName() == wxT("maintainers"))
        {
            wxXmlNode *maint = child->GetChildren();
            while (maint)
            {
                if (maint->GetName() == wxT("maintainer") && maint->HasProp(wxT("name")))
                {
                    // NB: do not use GetHrefProp() here since it would discard
                    //     email addresses !
                    p.AddMaintainer(maint->GetPropVal(wxT("name"), wxEmptyString),
                                    maint->GetPropVal(wxT("href"), wxEmptyString));
                }

                maint = maint->GetNext();
            }

            m_pMaintainers = child;
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    return true;
}

bool wxPackageXMLDescriptor::LoadDetails(const wxXmlNode *n, wxPackageInfo &p)
{
    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("status"))
        {
            p.SetDevelopmentStatusStr(GetStrippedNodeContent(child));
            m_pStatus = child;
        }
        else if (child->GetName() == wxT("proglanguage"))
        {
            p.SetProgLanguageStr(GetStrippedNodeContent(child));
            m_pProgLanguage = child;
        }
        else if (child->GetName() == wxT("rss"))
        {
            p.SetRSSHref(GetHrefProp(child));
            m_pRSS = child;
        }
        else if (child->GetName() == wxT("logo"))
        {
            p.SetLogo(GetHrefProp(child),
                    GetLocalRefProp(child));
            m_pLogo = child;
        }
        else if (child->GetName() == wxT("samples"))
        {
            wxString str = child->GetPropVal(wxT("number"), wxT("0"));
            unsigned long n;
            if (str.ToULong(&n))
                p.SetNumberOfSamples(n);

            m_pSamples = child;
        }
        else if (child->GetName() == wxT("download"))
        {
            wxXmlNode *mirror = child->GetChildren();
            while (mirror)
            {
                if (mirror->GetName() == wxT("mirror") && mirror->HasProp(wxT("href")))
                    p.AddMirror(GetHrefProp(mirror));

                mirror = mirror->GetNext();
            }

            m_pDownloads = child;
        }
        else if (child->GetName() == wxT("documentation"))
        {
            wxXmlNode *doc = child->GetChildren();
            while (doc)
            {
                if (doc->GetName() == wxT("doc"))
                {
                    p.AddDoc(doc->GetPropVal(wxT("type"), wxEmptyString),
                            GetHrefProp(doc),
                            GetLocalRefProp(doc));
                }
                else
                    HandleUnknownTag(doc);

                doc = doc->GetNext();
            }

            m_pDocs = child;
        }
        else if (child->GetName() == wxT("screenshots"))
        {
            wxXmlNode *screen = child->GetChildren();
            while (screen)
            {
                if (screen->GetName() == wxT("image") &&
                    screen->HasProp(wxT("href")))
                {
                    p.AddScreenshot(GetHrefProp(screen),
                                    GetLocalRefProp(screen));
                }
                else
                    HandleUnknownTag(screen);

                screen = screen->GetNext();
            }

            m_pScreenshots = child;
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    return true;
}

bool wxPackageXMLDescriptor::LoadWxportTags(const wxXmlNode *port,
                                            const wxVersionRange &defrange,
                                            wxArrayInt *ports,
                                            wxVersionRangeArray *ver)
{
    while (port)
    {
        if (port->GetName() == wxT("wxport") && port->HasProp(wxT("name")))
        {
            wxString verstr = port->GetPropVal(wxT("versions"), wxEmptyString);
            wxString portname = port->GetPropVal(wxT("name"), wxEmptyString);

            wxVersionRange v;
            if (port->HasProp(wxT("versions")))
                v.Set(verstr);
            else
                v = defrange;

            if (!v.IsOk())
            {
                wxLogWarning(wxT("Found a <wxport> tag with an invalid version string: %s"),
                           verstr.c_str());
                // continue nonetheless (remember we want to be able to load invalid WXP too!)
            }

            if (portname == wxT("any"))
            {
                // allow the use of "any" as name
                for (size_t i=0; i<wxPORT_MAX; i++)
                {
                    wxPortId id = (wxPortId)(1 << i);

                    if (ver) ver->Add(v);
                    if (ports) ports->Add(id);
                }
            }
            else
            {
                wxPortId id(wxPlatformInfo::GetPortId(portname));
                if (id != wxPORT_UNKNOWN)
                {
                    if (ver) ver->Add(v);
                    if (ports) ports->Add(id);
                }
            }
        }
        else
            HandleUnknownTag((wxXmlNode*)port);

        port = port->GetNext();
    }

    return true;
}

bool wxPackageXMLDescriptor::LoadDependencies(const wxXmlNode *n, wxPackageInfo &p)
{
    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("supported-ports"))
        {
            wxArrayInt ports;
            wxVersionRangeArray vers;

            wxString versions = child->GetPropVal(wxT("versions"), wxEmptyString);
            if (LoadWxportTags(child->GetChildren(), versions, &ports, &vers))
                p.SetSupportedPorts(ports, vers);

            m_pSupportedPorts = child;
        }
        else if (child->GetName() == wxT("tested-ports"))
        {
            wxArrayInt ports;
            wxVersionRangeArray vers;

            wxString versions = child->GetPropVal(wxT("versions"), wxEmptyString);
            if (LoadWxportTags(child->GetChildren(), versions, &ports, &vers))
                p.SetTestedPorts(ports, vers);

            m_pTestedPorts = child;
        }
        else if (child->GetName() == wxT("dependency"))
        {
            wxString deptypename = child->GetPropVal(wxT("type"), wxT("required"));
            wxPackageDependencyType deptype = wxString2PackageDependencyType(deptypename);

            wxXmlNode *dep = child->GetChildren();
            while (dep)
            {
                if (dep->GetName() == wxT("id") &&
                    dep->HasProp(wxT("name")))
                {
                    wxVersionRange ver(dep->GetPropVal(wxT("versions"), wxEmptyString));
                    wxPackageDependency pd(dep->GetPropVal(wxT("name"), wxEmptyString),
                                           ver, GetHrefProp(dep), deptype);

                    if (!pd.IsOk())
                    {                        
                        wxLogWarning(wxT("Found invalid dependency named '%s'"),
                                     pd.GetName().c_str());
                        // continue nonetheless (remember we want to be able to load invalid WXP too!)
                    }

                    p.AddDependency(pd);
                }
                else
                    HandleUnknownTag(dep);

                dep = dep->GetNext();
            }

            if (m_pDependencies[deptype] != NULL)
            {
                // the dependencies of these type are divided in two blocks...
                // while we can load them without problems we cannot save them
                // keeping the two blocks separed... so we remove this tag from the
                // tree keeping only the first parsed <dependency>
                ((wxXmlNode*)n)->RemoveChild(child);
            }
            else
                m_pDependencies[deptype] = child;
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    return true;
}

bool wxPackageXMLDescriptor::LoadBuildSystem(const wxXmlNode *n, wxPackageInfo &p)
{
    p.SetBuildSystemTypeStr(n->GetPropVal(wxT("type"), wxT("bakefile")));
    p.SetBuildSysHref(GetLocalRefProp(n));

    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("formats"))
        {
            wxString str = GetStrippedNodeContent(child);
            p.SetSupportedFormats(wxString2PackageCompilerFormatList(str));

            m_pFormats = child;
        }
        else if (child->GetName() == wxT("options"))
        {
            wxArrayString presets =
				wxString2Array(child->GetPropVal(wxT("presets"), wxEmptyString));

            // add preset options, if specified
            for (size_t i=0; i<presets.GetCount(); i++)
                if (wxPresetXMLDescriptor::s_hashmapOptions.count(presets[i]) > 0)
                    p.AddBuildSysOptions(wxPresetXMLDescriptor::s_hashmapOptions[presets[i]]);
                else
                    wxLogWarning(wxT("Ignoring unknown option preset '%s'"), 
                                 presets[i].c_str());

            // add any other option...
            wxPackageCommandOptionArray arr(LoadOptions(child));
            if (!arr.IsOk())
            {
                wxLogWarning(wxT("Found an invalid set of %d options"), arr.GetCount());
                // continue nonetheless (remember we want to be able to load invalid WXP too!)
            }

            p.AddBuildSysOptions(arr);
            m_pOptions = child;
        }
        else if (child->GetName() == wxT("stages"))
        {
            if (!LoadStages(child, p))
                return false;

            m_pStages = child;
        }
        else
            HandleUnknownTag(child);

        // proceed
        child = child->GetNext();
    }

    if (p.GetSupportedBuildSysFormats() == 0)
        p.SetSupportedFormats(wxGetFullPackageCompilerFormatList());

    return true;
}

bool wxPackageXMLDescriptor::LoadStages(const wxXmlNode *n, wxPackageInfo &p)
{
    wxXmlNode *child = n->GetChildren();
    while (child)
    {
        if (child->GetName() == wxT("cmd"))
        {
            wxPackageCommand command(LoadCommand(child));

            // add a non-conditional command to all stages
            if (!command.IsOk())
            {
                wxLogWarning(wxT("Found invalid <cmd> '%s'"), 
                             command.GetCommand().c_str());
                // continue nonetheless (remember we want to be able to load invalid WXP too!)
            }

            p.AddBuildSysCommand(wxGetFullPackageBuildSystemStageList(), command);
        }
        else if (child->GetName() == wxT("if"))
        {
            wxPackageCondition cnd(LoadCondition(child));

            // add conditional commands
            wxXmlNode *cmd = child->GetChildren();
            while (cmd)
            {
                if (cmd->GetName() == wxT("cmd"))
                {
                    wxPackageCommand command(LoadCommand(cmd));
                    command.SetCondition(cnd);

                    if (!command.IsOk())
                    {                        
                        wxLogWarning(wxT("Found invalid <cmd> '%s'"), 
                                     command.GetCommand().c_str());
                        // continue nonetheless (remember we want to be able to load invalid WXP too!)
                    }

                    p.AddBuildSysCommand(cnd.GetStages(), command);
                }
                else
                    HandleUnknownTag(cmd);

                cmd = cmd->GetNext();
            }
        }
        else
        {
            bool valid = false;

            // is this the name of a build system stage ?
            wxPackageBuildSystemStage stage =
                wxString2PackageBuildSystemStage(child->GetName());
            if (stage != wxPBSS_INVALID)
            {
                if (child->HasProp(wxT("targets")))
                {
                    // this is the list of targets supported for this stage
                    p.GetCmdForStage(stage).
                        SetTargets(child->GetPropVal(wxT("targets"), wxEmptyString));

                    valid = true;
                }
                else
                {
                    wxLogWarning(wxT("Ignoring tag '%s' which has no 'targets' attribute!"),
                               child->GetName().c_str());
                }
            }

            // is this the name of a command preset ?
            wxPackageCommandSetHashMap::iterator it;
            for( it = wxPresetXMLDescriptor::s_hashmapCommands.begin();
                it != wxPresetXMLDescriptor::s_hashmapCommands.end(); ++it )
            {
                if (it->first == child->GetName())
                {
                    if (it->second.GetBuildSystemType()!=p.GetBuildSystemType())
                    {
                        wxLogWarning(wxT("The command preset '%s' is not valid for '%s' build system!"),
                                     child->GetName().c_str(), p.GetBuildSystemTypeStr().c_str());
                        break;
                    }

                    // get the variable substitutions for this command preset
                    wxXmlProperty *prop = child->GetProperties();
                    wxArrayString names, values;
                    while (prop)
                    {
                        names.Add(prop->GetName());
                        values.Add(prop->GetValue());
                        prop = prop->GetNext();
                    }

                    // did the user specified a value for each variable supported
                    // by this command preset ?
                    valid = true;
                    for (size_t i=0; i<it->second.GetVars().GetCount(); i++)
                    {
                        wxString varname = it->second.GetVars().Item(i);
                        if (names.Index(varname) == wxNOT_FOUND)
                        {
                            valid = false;
                            wxLogWarning(wxT("The command preset '%s' needs a value for its '%s' variable"),
                                         child->GetName().c_str(), varname.c_str());
                            break;
                        }
                    }

                    if (!valid)
                        break;

                    // set the commandset vars as keywords to be substituted in
                    // *all* the commands which belong to the commandset
                    wxPackageCommandSet copy(it->second);
                    copy.AddKeywords(names, values);

                    // finally, add the commands to the package commands
                    p.AddBuildSysCommands(copy);
                }
            }

            if (!valid)
                HandleUnknownTag(child);
        }

        child = child->GetNext();
    }

    // add the default 'all' target if no other targets are available
    // FIXME: this is a sort of auto-repair which should be removed
    for (size_t i=0; i < wxPBST_MAX; i++)
    {
        wxPackageCommandArray &arr = p.GetCmdForStage(wxIdx2PackageBuildSystemStage(i));
        if (arr.GetTargets().GetCount() == 0)
            arr.SetTargets(wxT("all"));
    }

    return true;
}

