/////////////////////////////////////////////////////////////////////////////
// Name:        app.cpp
// Purpose:     wxWidgets command-line Package Manager and Packager
// Author:      Francesco Montorsi
// Modified by:
// Created:     2006-11-08
// RCS-ID:      $Id: app.cpp,v 1.20 2007/02/01 11:55:36 frm Exp $
// Copyright:   (c) Francesco Montorsi
// Licence:     wxWidgets license
/////////////////////////////////////////////////////////////////////////////


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

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#include <wx/cmdline.h>
#include <wx/config.h>
#include <wx/stdpaths.h>
#include <wx/sstream.h>
#include <wx/zstream.h>

#include "wxp/packagewxp.h"
#include "wx/md5.h"

// wxP own headers
#include "symbols.h"
#include "app.h"
#include "utils.h"

#if wxUSE_GUI
    #error This application must be compiled as console application!
#endif



// ----------------------------------------------------------------------------
// Constants
// ----------------------------------------------------------------------------

// when changing this also update the wxPackageConsoleApp::Usage function
// which cannot use these directly for a spacing issue
wxString wxWorkModeName[wxWM_MAX] =
{
    _T("showinfo"),
    _T("download"),
    _T("decompress"),
    _T("build"),
    _T("install"),
    _T("uninstall"),
    _T("list"),
    _T("list-repo"),
    _T("add-repo"),
    _T("update"),

    _T("make-template"),
    _T("pack"),
    _T("upload"),
    _T("check"),

    _T("get-info"),
    _T("create-repo"),
    _T("output-stat"),
    _T("output-html"),
    _T("list-view-formats")
};

wxIMPLEMENT_STRING2ENUM(WorkMode, wxWM_MAX)

#define wxWXP_HELP_SWITCH              wxT("h")
#define wxWXP_VERSION_SWITCH           wxT("v")
#define wxWXP_PREFIX_OPTION            wxT("p")

static const wxCmdLineEntryDesc g_cmdLineDesc[] =
{
    { wxCMD_LINE_SWITCH, wxWXP_HELP_SWITCH, wxT("help"),
      wxT("displays help on the command line parameters") },
    { wxCMD_LINE_SWITCH, wxWXP_VERSION_SWITCH, wxT("version"), wxT("prints version") },

    { wxCMD_LINE_OPTION, wxWXP_PREFIX_OPTION, wxT("prefix"), wxT("set program prefix") },

    // the mode parameter must be made optional otherwise when specifying the
    // help or version switches, you'll get a
    //    The required parameter 'mode' was not specified.
    // error message
    { wxCMD_LINE_PARAM, wxT(""), wxT(""), wxT("mode"),
      wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL},

    { wxCMD_LINE_PARAM, wxT(""), wxT(""), wxT("package name(s) / file(s)"),
      wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_MULTIPLE|wxCMD_LINE_PARAM_OPTIONAL },

    { wxCMD_LINE_NONE }
};



// ----------------------------------------------------------------------------
// wxPackageConsoleApp
// ----------------------------------------------------------------------------

IMPLEMENT_APP(wxPackageConsoleApp)

wxPackageConsoleApp::wxPackageConsoleApp()
{
    // this is not our real name; see cmnsymbols.h for more info
    SetAppName(APPNAME);
}

wxPackageConsoleApp::~wxPackageConsoleApp()
{
}

void wxPackageConsoleApp::Usage()
{
    m_cmdParser.Usage();

    // give additional info about the "mode" parameter
    wxString pm_modes =
        wxT("* showinfo package-name\n")
        wxT("* download package-name\n")
        wxT("* decompress package-name path\n")
        wxT("* build package-name compiler [option1=value] [option2=value] ...\n")
        wxT("* install package-name compiler [option1=value] [option2=value] ...\n")
        wxT("* uninstall package-name compiler [option1=value] [option2=value] ...\n")
        wxT("* list [local|remote]\n")
        wxT("* list-repo [local|remote]\n")
        wxT("* add-repo local|remote repository-folder|repository-URL\n")
        wxT("* update\n");

    wxString pkg_modes =
        wxT("* make-template\n")
        wxT("* pack WXP-file\n")
        wxT("* upload WXP-file\n")
        wxT("* check WXP-file\n");

    wxString other_modes =
        wxT("* get-info keyword WXZ-file\n")
        wxT("* create-repo WXL-output-file WXZ-file1 [WXZ-file2] ...\n")
        wxT("* output-stat WXZ-file1 [WXZ-file2] ...\n")
        wxT("* output-html view-format-name summary|details WXZ-file\n")
        wxT("* list-view-formats\n");

    // add indentations
    pm_modes = wxT("\n") + pm_modes;
    pkg_modes = wxT("\n") + pkg_modes;
    other_modes = wxT("\n") + other_modes;
    pm_modes.Replace(wxT("\n"), wxT("\n   "));
    pkg_modes.Replace(wxT("\n"), wxT("\n   "));
    other_modes.Replace(wxT("\n"), wxT("\n   "));

    wxPrintf(wxT("\n where [mode] is one of:\n\n")
             wxT("  => Package management work modes:") +
             pm_modes +
             wxT("\n") +
             wxT("  => Packager work modes:") +
             pkg_modes +
             wxT("\n") +
             wxT("  => Repository management work modes:") +
             other_modes +
             wxT("\n") +
             wxT("Note that '[WXZ file]' can actually be any compressed wxWidgets package\n")
             wxT("(not only those ending with .wxz).\n")
             wxT("For more info or to report bugs, please visit http://wxwpm.sourceforge.net\n"));
}

wxArrayString wxPackageConsoleApp::GetValidParamFiles(int start, int end)
{
    if (end == -1)
        end = m_cmdParser.GetParamCount();

    wxArrayString files;
    for (int i=start; i < end; i++)
    {
        wxFileName f(m_cmdParser.GetParam(i));
        if (!f.FileExists())
        {
            wxLogError(wxT("The '%s' file does not exist!"), f.GetFullPath().c_str());
            continue;       // skip this
        }

        files.Add(f.GetFullPath());
    }

    return files;
}

bool wxPackageConsoleApp::OnInit()
{
    wxLog::SetTimestamp(NULL);

    // VERY IMPORTANT: do not change our working directory otherwise
    //                 we won't be able to load packages with relative paths
    //                 given to arguments of this program
    // wxSetWorkingDirectory(wxStandardPaths::Get().GetDataDir());

    // Parse command line
    wxString cmdFilename;
    m_cmdParser.SetDesc(g_cmdLineDesc);
    m_cmdParser.SetCmdLine(argc, argv);
    m_cmdParser.SetLogo(
        wxT("wxWidgets Package Manager and Packager all-in-one\n")
        COPYRIGHT);

    // don't give usage on errors as the complete usage description
    // must be given only below when wxWXP_HELP_SWITCH is given:
    if (m_cmdParser.Parse(false) != 0)
    {
        wxPrintf(wxT("\n"));
        Usage();
        return false;
    }

    if (m_cmdParser.Found(wxWXP_HELP_SWITCH))
    {
        Usage();
        return false;
    }

    if (m_cmdParser.Found(wxWXP_VERSION_SWITCH))
    {
        wxString msg;
        wxString date(wxString::FromAscii(__DATE__));
        msg.Printf(wxT("Version %.1f, %s"),
                   VERSION_NUMBER, date.c_str());
        wxLogMessage(msg);
        return false;
    }

    if (m_cmdParser.GetParamCount() == 0)
    {
        wxLogError(wxT("The work mode parameter is missing!"));
        return false;
    }

    wxString str;
#ifdef __WXGTK__
    if (m_cmdParser.Found(wxWXP_PREFIX_OPTION, &str))
    {
        // do not autodetect the prefix
        ((wxStandardPaths&)wxStandardPaths::Get()).SetInstallPrefix(str);
    }
#endif

    // NB: all the remaining parse work to do is done by OnRun()

    return TRUE;
}

int wxPackageConsoleApp::OnRun()
{
    wxString basepath = wxStandardPaths::Get().GetDataDir() +
                        wxFileName::GetPathSeparator();

    // load presets
    // NB: presets are fundamental even for apparently unrelated operations
    //     (e.g. creation of a WXL file) in order to correctly load the WXP files
    wxString xmlpath = basepath + wxT("xml") + wxFileName::GetPathSeparator();
    if (!wxPresetXMLDescriptor::Load(xmlpath + DEFAULT_PRESETS))
    {
        wxLogError(_("Could not load the presets file '%s'; it's not a valid preset file."),
                   (basepath + DEFAULT_PRESETS).c_str());

        // do not proceed as without presets the wxPM is quite useless
        wxLogError(_("Cannot proceed without presets. Aborting."));
        return -1;
    }

    // load template package
    wxFileInputStream fis(xmlpath + DEFAULT_TEMPLATE);
    if (!fis.IsOk())
    {
        wxLogWarning(_("Could not load the template file '%s'."),
                     (xmlpath + DEFAULT_TEMPLATE).c_str());

        // proceed
    }
    else
    {
        wxStringOutputStream output(&m_strTemplate);
        if (fis.Read(output).GetLastError() == wxSTREAM_READ_ERROR)
            wxLogWarning(_("Could not read the template file '%s'."),
                         (xmlpath + DEFAULT_TEMPLATE).c_str());
    }

    // load view formats
    wxString viewfmtpath = basepath + wxT("viewformats");
    if (!wxPackageInfo::LoadStandardOutputFormats(viewfmtpath))
    {
        wxLogError(_("Could not load the view format modes from directory '%s'."),
                   viewfmtpath.c_str());

        // do not proceed as without view formats the wxPM is quite useless
        wxLogError(_("Cannot proceed without view formats. Aborting."));
        return -1;
    }

    // load config stuff
    SetGlobalConfig();
    if (!wxPackage::LoadGlobals(wxConfigBase::Get(), wxEmptyString))
    {
        // wxLogError(_("No local/remote repository paths defined."));
        // return false;

        // proceed as the user may add paths with add-repo command
    }

    // we'll need sockets, too
    wxSocketBase::Initialize();

    // we are going to need stdout for various tasks
    m_stdout.Attach(wxFile::fd_stdout);

    // detect work mode
    wxString modestr = m_cmdParser.GetParam(0);
    wxWorkMode mode = wxString2WorkMode(modestr);
    if (mode == wxWM_INVALID)
    {
        wxLogError(wxT("Uknown work mode '%s'.\n"), modestr.c_str());
        Usage();
        return -1;
    }

    // create repo cache path
    // NB: this is a folder where files are going to be written and not only read
    //     thus we use the user data directory to be sure to have enough permissions
    //     for writing under e.g. Unix
    m_strRepoCachePath = wxStandardPaths::Get().GetUserDataDir();

    // be sure it ends with a path separator or wxFileName won't interpret it as a directory
    if (m_strRepoCachePath.Last() != wxFileName::GetPathSeparator())
        m_strRepoCachePath += wxFileName::GetPathSeparator();

    if (!wxDirExists(m_strRepoCachePath) &&
        !wxFileName(m_strRepoCachePath).Mkdir(0777, wxPATH_MKDIR_FULL))
    {
        wxLogError(wxT("Cannot initialize the remote repository cache at '%s'"),
                   m_strRepoCachePath.c_str());
        return -1;
    }

    // convert from bool to std Unix return codes
    return RunCommand(mode) ? 0 /* success */ : -1 /* failure */;
}

bool wxPackageConsoleApp::RunCommand(wxWorkMode mode)
{
    // check argument count
    switch (mode)
    {
    case wxWM_SHOWINFO_PACKAGE:
        if (m_cmdParser.GetParamCount() != 2)
        {
            wxLogError(wxT("The %s work mode requires the name of one package."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;
        
    case wxWM_DOWNLOAD_PACKAGE:
        if (m_cmdParser.GetParamCount() != 2)
        {
            wxLogError(wxT("The %s work mode requires the name of one package."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_DECOMPRESS_PACKAGE:
        if (m_cmdParser.GetParamCount() != 3)
        {
            wxLogError(wxT("The %s work mode requires the name of a package and the decompression path."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_BUILD_PACKAGE:
    case wxWM_INSTALL_PACKAGE:
    case wxWM_UNINSTALL_PACKAGE:
        if (m_cmdParser.GetParamCount() < 3)
        {
            wxLogError(wxT("The %s work mode requires at least the name of one package and the compiler to use."),
                       wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_CREATE_WXL:
        if (m_cmdParser.GetParamCount() < 3)
        {
            wxLogError(wxT("The %s work mode requires at least one output filename and a WXZ package filename."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_OUTPUT_STAT:
        if (m_cmdParser.GetParamCount() < 2)
        {
            wxLogError(wxT("The %s work mode requires at least one WXZ package filename."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_OUTPUT_HTML:
        if (m_cmdParser.GetParamCount() < 4)
        {
            wxLogError(wxT("The %s work mode requires a view mode, summary|details keyword and a WXZ package filename."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_ADD_REPOSITORY:
        if (m_cmdParser.GetParamCount() != 3)
        {
            wxLogError(wxT("The %s work mode requires a local|remote keyword and a valid directory|URL."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_CHECK:
    case wxWM_PACK:
    case wxWM_UPLOAD:
        if (m_cmdParser.GetParamCount() != 2)
        {
            wxLogError(wxT("The %s work mode requires a WXP filename."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    case wxWM_GET_INFO:
        if (m_cmdParser.GetParamCount() != 3)
        {
            wxLogError(wxT("The %s work mode requires a keyword and a filename."),
                        wxWorkMode2String(mode).c_str());
            return false;
        }
        break;

    default:
        break;
    }

    // call mode-specific function
    switch (mode)
    {
    case wxWM_SHOWINFO_PACKAGE:
        return ShowInfo(m_cmdParser.GetParam(1));

    case wxWM_DOWNLOAD_PACKAGE:
        return Download(m_cmdParser.GetParam(1));

    case wxWM_DECOMPRESS_PACKAGE:
        return Decompress(m_cmdParser.GetParam(1), m_cmdParser.GetParam(2));

    case wxWM_BUILD_PACKAGE:
    case wxWM_INSTALL_PACKAGE:
    case wxWM_UNINSTALL_PACKAGE:
        {
            // parse compiler
            wxString compilername = m_cmdParser.GetParam(2);
            wxPackageCompilerFormat compiler = wxString2PackageCompilerFormat(compilername);
            if (compiler == wxPCF_INVALID)
            {
                wxLogError(wxT("Unknown '%s' compiler format."), compilername.c_str());
                return false;
            }

            // parse options
            wxStringHashMap options;
            for (size_t i=3; i<m_cmdParser.GetParamCount(); i++)
            {
                wxString str = m_cmdParser.GetParam(i);
                if (!str.Contains(wxT("=")))
                {
                    wxLogWarning(wxT("Invalid option '%s'; use optionname=optionvalue format."),
                                 str.c_str());
                }
                else
                {
                    // add this option
                    options[str.BeforeFirst(wxT('='))] = str.AfterFirst(wxT('='));
                }
            }

            wxString pkgname(m_cmdParser.GetParam(1));
            wxPackageBuildSystemStage stage;
            if (mode == wxWM_BUILD_PACKAGE)
                stage = wxPBSS_BUILD;
            else if (mode == wxWM_INSTALL_PACKAGE)
                stage = wxPBSS_INSTALL;
            else
                stage = wxPBSS_UNINSTALL;

            return RunStage(pkgname, compiler, options, stage);
        }

    case wxWM_LIST_PACKAGES:
    case wxWM_LIST_REPOSITORIES:
    case wxWM_ADD_REPOSITORY:
        {
            bool remote = m_cmdParser.GetParamCount() == 2 &&
                          m_cmdParser.GetParam(1) == wxT("remote");

            if (mode == wxWM_LIST_PACKAGES)
                return ListPackages(remote);
            else if (mode == wxWM_LIST_REPOSITORIES)
                return ListRepositories(remote);
            else 
                return AddRepository(remote, m_cmdParser.GetParam(2));
        }

    case wxWM_MAKE_TEMPLATE:
        return OutputTemplate();

    case wxWM_PACK:
        return Pack(m_cmdParser.GetParam(1));

    case wxWM_UPLOAD:
        return Upload(m_cmdParser.GetParam(1));

    case wxWM_CHECK:
        return Check(m_cmdParser.GetParam(1));

    case wxWM_UPDATE:
        return Update();

    case wxWM_GET_INFO:
        return GetInfo(m_cmdParser.GetParam(1), m_cmdParser.GetParam(2));

    case wxWM_CREATE_WXL:
        return CreateWXL(m_cmdParser.GetParam(1), GetValidParamFiles(2));

    case wxWM_OUTPUT_HTML:
        {
            wxString viewformat = m_cmdParser.GetParam(1);
            if (wxPackageInfo::s_hashmapHTMLFormats.count(viewformat) == 0)
            {
                wxLogError(wxT("Unknown '%s' view format."),
                           viewformat.c_str());
                return false;
            }

            bool summary = true;
            wxString mode = m_cmdParser.GetParam(2);
            if (mode == wxT("details"))
                summary = false;

            return OutputHTML(viewformat, summary, GetValidParamFiles(3));
        }
        break;

    case wxWM_OUTPUT_STAT:
        return OutputStat(GetValidParamFiles(1));

    case wxWM_LIST_VIEW_FORMATS:
        return ListViewFormats();

    default:
        wxPrintf(wxT("Sorry, command not implemented yet.\n"));
        break;
    }

    return true;
}

int wxPackageConsoleApp::OnExit(void)
{
    m_stdout.Detach();

    // we may have updated some package status
    m_arrLocalPackage.SavePackagesStatusInfo(wxConfigBase::Get(), wxT("/Packages"));

    // we may have updated the paths of local/remote repos
    wxPackage::SaveGlobals(wxConfigBase::Get(), wxEmptyString);

    if (wxConfigBase::Get())
        delete wxConfigBase::Set(NULL);
    return 0;
}

bool wxPackageConsoleApp::InitLocalPackages()
{
    static bool alreadyInit = false;

    // VERY IMPORTANT: only init if not already init; this because functions like
    //                 RunStage() could recursively call this function
    //                 (see the RunStage -> UpgradePackageTo -> RunStage call path)
    if (alreadyInit)
        return true;

    if (!m_arrLocalPackage.LoadCompressedPackagesFromLocalRepo())
    {
        wxLogError(_("Cannot load all the packages in the local repository paths."));
        return false;
    }

    if (!m_arrLocalPackage.LoadPackagesStatusInfo(wxConfigBase::Get(), wxT("/Packages")))
    {
        wxLogError(_("Cannot load the status of all local packages."));
        return false;
    }

    size_t n = m_arrLocalPackage.RemoveDuplicates();
    if (n > 0)
        wxLogWarning(_("Found %d duplicate packages; duplicates removed."), n);

    alreadyInit = true;
    return true;
}

bool wxPackageConsoleApp::InitLocalPackagesAndSearch(const wxString &pkgname)
{
    if (!InitLocalPackages())
        return false;

    if (!m_arrLocalPackage.ContainsItemNamed(pkgname))
    {
        wxLogError(wxT("Cannot find the '%s' package in local repositories."),
                   pkgname.c_str());
        return false;
    }

    return true;
}

bool wxPackageConsoleApp::InitRemotePackages()
{
    if (!wxFileExists(m_strRepoCachePath + wxT("repo0.wxl")))
    {
        wxPrintf(wxT("No cache of remote repositories found. Creating it now.\n"));

        // we have no cache of remote repo; create it
        if (!Update())
            return false;   // Update() already warned the user
    }

    for (size_t i=0; i<wxPackage::s_arrRemoteRepo.GetCount(); i++)
    {
        wxPackageRepositoryXMLDescriptor repo;

        wxString filename = GetRepoCacheFile(i);
        if (!repo.Load(filename))
        {
            wxLogWarning(wxT("The '%s' file is an invalid repository"),
                         filename.c_str());
            continue;
        }

        wxPackageArray temp = repo.GetPackageArray(wxPackage::s_arrRemoteRepo[i]);
        WX_APPEND_ARRAY(m_arrRemotePackage, temp);
    }

    return true;
}

bool wxPackageConsoleApp::UpgradePackageTo(wxPackage &pkg, wxPackageStatus status)
{
    // NOTE: we won't log any error for a negative reply from the user.
    //       we will just exit with 'false' return code.

    while (pkg.GetStatus() < status)
    {
        wxPackageCompilerFormat fmt = pkg.GetCompilerSettings().GetSelFormat();

        bool reply;
        wxString arg;
        switch (pkg.GetStatus())
        {
        case wxPS_REMOTE:
            reply = wxAsk(wxT("The package '%s' needs to be downloaded first; proceed?"),
                        pkg.GetName().c_str());
            if (!reply)
                return false;
            
            if (!Download(pkg.GetName()))
            {
                wxLogError(wxT("Download of '%s' failed!"), pkg.GetName().c_str());
                return false;
            }
            break;

        case wxPS_DOWNLOADED:
            reply = wxAsk(wxT("The package '%s' needs to be decompressed first; proceed?"),
                          pkg.GetName().c_str());
            if (!reply)
                return false;

            arg = wxAskString(wxT("In which folder would you like to decompress it?"));
            if (arg.IsEmpty())
            {
                wxLogError(wxT("Invalid empty folder."));
                return false;
            }

            if (!Decompress(pkg.GetName(), arg))
            {
                wxLogError(wxT("Cannot decompress '%s' into '%s'..."), 
                           pkg.GetName().c_str(), arg.c_str());
                return false;
            }
            break;

        case wxPS_DECOMPRESSED:
        case wxPS_BUILT:
            {
                wxPackageBuildSystemStage stage =
                    pkg.GetStatus() == wxPS_DECOMPRESSED ? wxPBSS_BUILD : wxPBSS_INSTALL;

                reply = wxAsk(wxT("The packages '%s' needs to be %s first; proceed?"),
                            pkg.GetName().c_str(),
                            pkg.GetStatus() == wxPS_DECOMPRESSED ? wxT("built") : wxT("installed"));
                if (!reply)
                    return false;

                while (fmt == wxPCF_INVALID)
                {
                    wxString compiler = wxAskString(wxT("Which compiler should be used?"));
                    fmt = wxString2PackageCompilerFormat(compiler);
                }

                wxString opts = wxAskString(wxT("Enter the options to use for the %s stage, as a space-separed\n")
                                            wxT("list of optionname=optionvalue string:"),
                                            wxPackageBuildSystemStage2String(stage).c_str());
                wxArrayString optsarr = wxStringTokenize(opts, wxT(" "));
                wxStringHashMap options;
                for (size_t i=0; i<optsarr.GetCount(); i++)
                {
                    if (!optsarr[i].Contains(wxT("=")))
                    {
                        wxLogWarning(wxT("Invalid option '%s'; use optionname=optionvalue format."),
                                    optsarr[i].c_str());
                    }
                    else
                    {
                        // add this option
                        options[optsarr[i].BeforeFirst(wxT('='))] = optsarr[i].AfterFirst(wxT('='));
                    }
                }

                // RunStage() will already log an error message if something goes wrong:
                if (!RunStage(pkg.GetName(), fmt, options, stage))
                    return false;
            }
            break;

        case wxPS_INSTALLED:
        default:
            wxASSERT(0);
        }

        // upgrade was successful; reload our package (and its new state)
        pkg = m_arrLocalPackage.ItemByName(pkg.GetName());

        // ...but keep the compiler selected
        pkg.GetCompilerSettings().SetSelFormat(fmt);
    }

    return true;
}



// ----------------------------------------------------------------------------
// wxPackageConsoleApp core functions
// ----------------------------------------------------------------------------

bool wxPackageConsoleApp::CreateWXL(const wxString &outfile, const wxArrayString &arr)
{
    if (arr.GetCount() == 0)
        return false;

    wxPackageRepositoryXMLDescriptor wxl;
    for (size_t i=0; i<arr.GetCount(); i++)
        wxl.AddPackage(arr[i]);

    wxFileOutputStream outstream(outfile);
    if (!outstream.IsOk())
    {
        wxLogError(wxT("Cannot open the output file '%s'."), outfile.c_str());
        return false;
    }

    // save it compressed in the output file
    wxZlibOutputStream gzipstream(outstream, wxZ_BEST_COMPRESSION, wxZLIB_GZIP);
    if (!wxl.Save(gzipstream))
    {
        wxLogError(wxT("Cannot output the WXL file to stdout."));
        return false;
    }

    return true;
}

bool wxPackageConsoleApp::OutputHTML(const wxString &viewmode, bool showSummary, const wxArrayString &files)
{
    for (size_t i=0; i<files.GetCount(); i++)
    {
        wxPackage pkg;
        if (!pkg.LoadCompressedPackage(files[i]))
        {
            wxLogError(wxT("Cannot load the '%s' package."), files[i].c_str());
            return false;
        }

        // get summary/details
        wxString str;
        if (showSummary)
            str = wxPackageInfo::s_hashmapHTMLFormats[viewmode].GetSummaryFor(pkg);
        else
            str = wxPackageInfo::s_hashmapHTMLFormats[viewmode].GetDetailsFor(pkg);

        // we always want to output the summary of the package to STDOUT:
        // the user will just need to use the > redirection operator to put
        // the description in a file, if he wants.
        // Also note that ShowInfo() is based on writing info on stdout!
        if (!m_stdout.Write(str))
        {
            wxLogError(wxT("Cannot output the HTML description to stdout for '%s'."),
                       pkg.GetName().c_str());
            return false;
        }
    }

    return true;
}

bool wxPackageConsoleApp::OutputStat(const wxArrayString &arr)
{
    wxPackageArray pkgarr;
    for (size_t i=0; i<arr.GetCount(); i++)
    {
        wxPackage pkg;
        if (!pkg.LoadCompressedPackage(arr[i]))
        {
            wxLogError(wxT("Cannot load the '%s' package."), arr[i].c_str());
            return false;
        }

        pkgarr.Add(pkg);
    }

    for (size_t i=0; i<wxPC_MAX; i++)
    {
        wxPackageCategory curr = (wxPackageCategory)i;
        wxPackageArray temp = pkgarr.GetPackagesWhichBelongTo(curr);
        if (temp.GetCount() == 0)
            continue;       // skip this

        wxPrintf(wxT("Category [%s] has %d package(s):\n"),
                 wxPackageCategory2String(curr).c_str(),
                 temp.GetCount());

        for (size_t j=0; j<temp.GetCount(); j++)
            wxPrintf(wxT("   ") + temp[j].GetName() + wxT("\n"));
    }

    return true;
}

bool wxPackageConsoleApp::ListPackages(bool remote)
{
    wxPackageArray *arr = NULL;
    if (remote)
    {
        if (!InitRemotePackages())
            return false;
        arr = &m_arrRemotePackage;

        if (arr->GetCount() > 0)
            wxPrintf(wxT("List of the remote packages cached:\n"));
        else
            wxPrintf(wxT("No remote packages\n"));
    }
    else
    {
        if (!InitLocalPackages())
            return false;
        arr = &m_arrLocalPackage;

        if (arr->GetCount() > 0)
            wxPrintf(wxT("List of the local packages detected:\n"));
        else
            wxPrintf(wxT("No local packages\n"));
    }

    for (size_t i=0; i<arr->GetCount(); i++)
    {
        wxString name = arr->Item(i).GetName();
        wxString status = arr->Item(i).GetStatusStr();

        switch (arr->Item(i).GetStatus())
        {
        case wxPS_DOWNLOADED:
            status += wxT(" in ") + arr->Item(i).GetDownloadPath();
            break;

        case wxPS_DECOMPRESSED:
            status += wxT(" in ") + arr->Item(i).GetDecompressionPath();
            break;
        }

        wxPrintf(wxT(" %d) %s%*c[%s]\n"), i+1,
                 name.c_str(),
                 25 - name.Len(),
                 wxT(' '),
                 status.c_str());
    }

    return true;
}

bool wxPackageConsoleApp::ListRepositories(bool remote)
{
    wxArrayString *arr = NULL;
    if (remote)
    {
        arr = &wxPackage::s_arrRemoteRepo;

        if (arr->GetCount() > 0)
            wxPrintf(wxT("Remote repositories:\n"));
        else
            wxPrintf(wxT("No remote repositories registered.\n"));
    }
    else
    {
        arr = &wxPackage::s_arrLocalRepo;

        if (arr->GetCount() > 0)
            wxPrintf(wxT("Local repositories:\n"));
        else
            wxPrintf(wxT("No local repositories registered.\n"));
    }

    for (size_t i=0; i<arr->GetCount(); i++)
        wxPrintf(wxT("   ") + arr->Item(i) + wxT("\n"));

    return true;
}

bool wxPackageConsoleApp::AddRepository(bool remote, const wxString &repo)
{
    if (remote)
    {
        if (!repo.StartsWith(wxT("http://")))
        {
            wxPrintf(wxT("Invalid remote repository '%s'; the remote repositiories should start with 'http://'.\n"), repo.c_str());
            return false;
        }

        // we won't test for existence of this repo...
        wxPackage::s_arrRemoteRepo.Add(repo);
    }
    else
    {
        wxFileName repofn = wxFileName::DirName(repo);
        repofn.MakeAbsolute();

        if (!repofn.DirExists())
        {
            wxPrintf(wxT("The directory '%s' does not exist. Cannot add it as local repo.\n"),
                     repofn.GetPath().c_str());
            return false;
        }
        else
        {
            wxPackage::s_arrLocalRepo.Add(repofn.GetPath());
            wxPrintf(wxT("The directory '%s' has been added to the local repositories.\n"),
                     repofn.GetPath().c_str());
        }
    }

    return true;
}

bool wxPackageConsoleApp::Update()
{
    bool success = true;

    // download all remote repo WXL
    for (size_t i=0; i<wxPackage::s_arrRemoteRepo.GetCount(); i++)
    {
        wxString filename = GetRepoCacheFile(i), 
                 repo = wxPackage::s_arrRemoteRepo[i];

        if (repo.EndsWith(wxT("/")))
            repo += DEFAULT_WXL_FILENAME;

        if (!wxDownload(repo, filename))
        {
            wxLogWarning(wxT("Cannot download repository '%s'"),
                         wxPackage::s_arrRemoteRepo[i].c_str());
            success = false;
            continue;
        }
    }

    if (success)
        wxPrintf(wxT("\nRepository cache updated.\n"));
    else
        wxPrintf(wxT("\nNot all repository caches were successfully updated.\n"));

    return true;
}

bool wxPackageConsoleApp::ShowInfo(const wxString &pkgname)
{
    wxArrayString wxz;

    if (!InitLocalPackagesAndSearch(pkgname))
        return false;

    // show the summary with the "plain" viewformat:
    wxz.Add(m_arrLocalPackage.ItemByName(pkgname).GetDownloadPath());

    wxPrintf(wxT("\n\n"));
    bool success = OutputHTML(wxT("plain"), true /* summary */, wxz);
    wxPrintf(wxT("\n\n"));

    return success;
}

bool wxPackageConsoleApp::Download(const wxString &pkgname)
{
    if (!InitRemotePackages())
        return false;

    if (!m_arrRemotePackage.ContainsItemNamed(pkgname))
    {
        wxLogError(wxT("Cannot find the '%s' package in remote repositories."),
                   pkgname.c_str());
        return false;
    }

    // download the package
    wxString url = m_arrRemotePackage.ItemByName(pkgname).GetDownloadLink();
    wxString filename = wxString::Format(wxT("%s%c%s"),
                                         wxPackage::s_arrLocalRepo[0].c_str(),
                                         wxFileName::GetPathSeparator(),
                                         url.AfterLast(wxT('/')).c_str());
    if (!wxDownload(url, filename))
    {
        wxLogError(wxT("Cannot download the '%s' file."), url.c_str());
        return false;
    }

    wxPrintf(wxT("\nDownloaded '%s' as '%s'.\n"), pkgname.c_str(), filename.c_str());
    return true;
}

bool wxPackageConsoleApp::Decompress(const wxString &pkgname, const wxString &dir)
{
    if (!InitLocalPackagesAndSearch(pkgname))
        return false;

    wxFileName dirname = wxFileName::DirName(dir);
    dirname.MakeAbsolute();
    if (!m_arrLocalPackage.ItemByName(pkgname).Decompress(dirname.GetPath()))
    {
        wxLogError(wxT("Cannot decompress the '%s' package."), pkgname.c_str());
        return false;
    }

    wxPrintf(wxT("Package successfully decompressed in '%s'.\n"), dirname.GetPath().c_str());
    return true;
}

bool wxPackageConsoleApp::RunStage(const wxString &pkgname, wxPackageCompilerFormat compiler,
                                   const wxStringHashMap &options, wxPackageBuildSystemStage stage)
{
    if (!InitLocalPackagesAndSearch(pkgname))
        return false;

    //wxPrintf(wxT("\n\nthe compiler is %d\n\n"), compiler);

    // select user chosen compiler
    // NB: this must be done before calling UpgradePackageTo() so that
    //     it knows which compiler should be used for upgrading the package!
    wxPackage pkg = m_arrLocalPackage.ItemByName(pkgname);
    pkg.GetCompilerSettings().SetSelFormat(compiler);
    pkg.GetCompilerSettings().UpdateEnvVars();

    // check status of the package
    wxPackageStatus required = wxPS_INVALID;
    switch (stage)
    {
    case wxPBSS_BUILD: required = wxPS_DECOMPRESSED; break;
    case wxPBSS_INSTALL: required = wxPS_BUILT; break;
    case wxPBSS_UNINSTALL: required = wxPS_INSTALLED; break;

    default:
        wxASSERT(0);
    }

    if (!UpgradePackageTo(pkg, required))
    {
        wxLogError(wxT("Cannot proceed without upgrading the package to the '%s' state first. Exiting"),
                   wxPackageStatus2String(required).c_str());
        return false;
    }

    // set all options with given values
    for( wxStringHashMap::const_iterator it = options.begin(); it != options.end(); ++it )
    {
        int idx = pkg.GetBuildSysOptions().IndexByName(it->first);
        if (idx == wxNOT_FOUND)
        {
            wxLogError(wxT("No option named '%s' is supported by '%s'."),
                       it->first.c_str(), pkgname.c_str());
            continue;
        }

        wxPackageCommandOption &opt = pkg.GetBuildSysOptions().ItemByName(it->first);
        opt.SetValue(it->second);
        if (!opt.IsOk())
        {
            wxLogError(wxT("The option '%s' does not allow the '%s' value."),
                       it->first.c_str(), it->second.c_str());
            opt.ResetToDefault();
            continue;
        }
    }

    // select which commands to run:
    // NOTE: we could just use the wxPKGCMD_ONLY_CHANGED flag but since we are
    //       a console application (i.e. for advanced use) we want to show
    //       (and explicitely pass to the MAKE process) _all_ command options
    //       (even if their selected value == their default value):
    wxPackageCondition cond(wxPackageCondition::GetCurrentPlatform(),
                            compiler,
                            pkg.GetBuildSystemType(),
                            stage);
    wxPackageCommandArray arr = 
        pkg.GetSubstCmdForStage(stage, wxPKGCMD_ONLY_MATCHING_CONDITION, cond);

    // execute all commands
    for (size_t i=0; i<arr.GetCount(); i++)
    {
        const wxPackageCommand &cmd = arr[i];
        if (!cmd.GetCondition().IsValidForCompiler(compiler))
            continue;

        // change our working directory as required
        cmd.UpdateWorkingDirectory(pkg.GetDecompressionPath());

        // and run this command
        wxPrintf(wxT("\n => Running '%s' from '%s'\n"), 
                 cmd.GetCommand().c_str(), wxGetCwd().c_str());
        wxPrintf(wxT(" ") + wxString(75, wxT('-')) + wxT("\n\n"));
        if (!wxShell(cmd.GetCommand()))
            return false;
    }

    switch (stage)
    {
    case wxPBSS_BUILD:
        wxPrintf(wxT("Package successfully built.\n"));
        m_arrLocalPackage.ItemByName(pkgname).SetBuiltStatus();
        break;
    case wxPBSS_INSTALL:
        wxPrintf(wxT("Package successfully installed.\n"));
        m_arrLocalPackage.ItemByName(pkgname).SetInstalledStatus(wxEmptyString);
        break;
    case wxPBSS_UNINSTALL:
        wxPrintf(wxT("Package successfully uninstalled.\n"));
        m_arrLocalPackage.ItemByName(pkgname).SetBuiltStatus();
        break;

    default:
        wxASSERT(0);
    }

    return true;
}

bool wxPackageConsoleApp::OutputTemplate()
{
    // don't do 
    //      wxPrintf(m_strTemplate); 
    // as the '%' contained in the string would be interpreted in a
    // special way otherwise...
    wxPrintf(wxT("%s"), m_strTemplate.c_str());

    return true;
}

bool wxPackageConsoleApp::ListViewFormats()
{
    wxPrintf(wxT("List of output view formats:\n"));

    // iterate over all the elements in the class
    wxPackageHTMLOutputFormatHashMap::iterator it;
    int i = 0;
    for (it = wxPackageInfo::s_hashmapHTMLFormats.begin();
         it != wxPackageInfo::s_hashmapHTMLFormats.end();
         ++it)
    {
        const wxPackageHTMLOutputFormat &fmt = it->second;
        wxPrintf(wxT(" %d) %s [%s]\n"), ++i,
                 fmt.GetName().c_str(), fmt.GetContextStr().c_str());
    }

    return true;
}

bool wxPackageConsoleApp::GetPackage(wxPackageInfo &pkg, const wxString &file)
{
    wxPackageXMLDescriptor xml;
    if (!xml.Load(file))
    {
        wxPrintf(wxT("Cannot load the package '%s'.\n"), file.c_str());
        return false;
    }

    pkg = xml.GetPackageInfo();
    if (!pkg.IsOk())
    {
        wxPrintf(wxT("The package descriptor '%s' is not valid.\n"), file.c_str());
        return false;
    }

    return true;
}

bool wxPackageConsoleApp::Check(const wxString &file)
{
    wxPackageInfo info;
    if (!GetPackage(info, file))
        return false;

    wxPrintf(wxT("The package descriptor '%s' is valid.\n"), file.c_str());
    return true;
}

bool wxPackageConsoleApp::Upload(const wxString &file)
{
    wxPackageInfo info;
    if (!GetPackage(info, file))
        return false;

    wxPackage pkg(info);
    if (!pkg.UploadWithFTP())
    {
        wxPrintf(wxT("Couldn't upload the package '%s' to '%s'.\n"), 
                 file.c_str(), pkg.GetUploadDestination().c_str());
        return true;
    }

    wxPrintf(wxT("The package has been upload to '%s'.\n"), 
             pkg.GetUploadDestination().c_str());

    return true;
}

bool wxPackageConsoleApp::Pack(const wxString &file)
{
    wxPackageInfo info;
    if (!GetPackage(info, file))
        return false;

    // "file" must be trasformed into an absolute path
    wxFileName fn(file);
    fn.MakeAbsolute();
    wxString ff = fn.GetFullPath();

    wxPackage pkg(info);
    pkg.SetDecompressedStatus(pkg.GetProjectFolder(ff));
    if (!pkg.Compress())
    {
        wxPrintf(wxT("Couldn't compress the package '%s'.\n"), file.c_str());
        return true;
    }

    wxPrintf(wxT("The package has been compressed as '%s'.\n"),
             pkg.SubstituteInfo(pkg.GetOutputFileName()).c_str());
    return true;
}

bool wxPackageConsoleApp::GetInfo(const wxString &keyword, const wxString &file)
{
    wxPackage pkg;
    if (!pkg.LoadCompressedPackage(file))
    {
        wxLogError(wxT("Cannot load the '%s' package."), file.c_str());
        return false;
    }

    wxString key = wxWPM_SUBSTITUTION_START_MARKER + keyword + wxWPM_SUBSTITUTION_END_MARKER;
    wxString out = pkg.SubstituteInfo(key);
    if (out != key)
    {
        wxPrintf(wxT("%s"), out.c_str());
        return true;
    }

    // the given keyword could not be substituted!
    return false;
}

