/////////////////////////////////////////////////////////////////////////////
// Name:        mainfrm.cpp
// Purpose:     wxWidgets Packager main app class
// Author:      Francesco Montorsi
// Modified by:
// Created:     2006-06-08
// RCS-ID:      $Id: mainfrm.cpp,v 1.19 2007/02/01 22:58:12 frm Exp $
// Copyright:   (c) Francesco Montorsi
// Licence:     wxWidgets licence
/////////////////////////////////////////////////////////////////////////////


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

#ifdef __BORLANDC__
#pragma hdrstop
#endif

#include <wx/notebook.h>
#include <wx/menu.h>
#include <wx/sizer.h>
#include <wx/msgdlg.h>
#include <wx/splitter.h>
#include <wx/filedlg.h>
#include <wx/intl.h>
#include <wx/html/helpctrl.h>
#include <wx/stdpaths.h>
#include <wx/aboutdlg.h>
#include <wx/busyinfo.h>
#include <wx/artprov.h>

#include "symbols.h"
#include "mainfrm.h"
#include "app.h"

#include "guipkg/proppanel.h"
#include "guipkg/newwizard.h"
#include "guipkg/sourceeditctrl.h"
#include "guipkg/uploaddlg.h"


// ----------------------------------------------------------------------------
// XPM resources
// ----------------------------------------------------------------------------

#include "bitmaps/wxp16x16.xpm"
#include "bitmaps/wxp32x32.xpm"


// ----------------------------------------------------------------------------
// constants
// ----------------------------------------------------------------------------

enum wxPackagerFrameID
{
    ID_PFRAME_CHECK,
    ID_PFRAME_REPACKAGE,
    ID_PFRAME_UPLOAD
};

#define FRAME_TITLE     wxT("wxWidgets Packager")



// ----------------------------------------------------------------------------
// wxPackagerFrame
// ----------------------------------------------------------------------------

IMPLEMENT_CLASS(wxPackagerFrame, wxFrame)
BEGIN_EVENT_TABLE(wxPackagerFrame, wxFrame)
    EVT_CLOSE(wxPackagerFrame::OnCloseWindow)
    EVT_UPDATE_UI(wxID_ANY, wxPackagerFrame::OnUpdateUI)
    EVT_PROPPANEL_USER_CHANGE(wxID_ANY, wxPackagerFrame::OnPropPanelUserChange)

    // file menu
    EVT_MENU(wxID_NEW, wxPackagerFrame::OnNewPackage)
    EVT_MENU(wxID_OPEN, wxPackagerFrame::OnOpenPackage)
    EVT_MENU_RANGE(wxID_FILE1, wxID_FILE9, wxPackagerFrame::OnOpenMRUPackage)
    EVT_MENU(wxID_SAVE, wxPackagerFrame::OnSavePackage)
    EVT_MENU(wxID_SAVEAS, wxPackagerFrame::OnSaveAsPackage)
    EVT_MENU(wxID_CLOSE, wxPackagerFrame::OnClosePackage)
    EVT_MENU(wxID_EXIT, wxPackagerFrame::OnExit)

    // package menu
    EVT_MENU(ID_PFRAME_CHECK, wxPackagerFrame::OnCheck)
    EVT_MENU(ID_PFRAME_REPACKAGE, wxPackagerFrame::OnRepackage)
    EVT_MENU(ID_PFRAME_UPLOAD, wxPackagerFrame::OnUpload)

    // help menu
    EVT_MENU(wxID_ABOUT, wxPackagerFrame::OnAbout)
    EVT_MENU(wxID_HELP, wxPackagerFrame::OnHelp)

END_EVENT_TABLE()


bool wxPackagerFrame::Create()
{
    // create the frame directly with loaded size & position
    wxPoint pos = wxDefaultPosition;
    wxSize sz = wxDefaultSize;
    wxConfigBase::Get()->Read(_T("Windows/Packager/MainWindowX"), &pos.x, 50);
    wxConfigBase::Get()->Read(_T("Windows/Packager/MainWindowY"), &pos.y, 50);
    wxConfigBase::Get()->Read(_T("Windows/Packager/MainWindowWidth"), &sz.x, 600);
    wxConfigBase::Get()->Read(_T("Windows/Packager/MainWindowHeight"), &sz.y, 600);

    if (!wxFrame::Create(NULL, wxID_ANY, FRAME_TITLE, pos, sz))
    {
        wxLogError(wxT("Could not create the wxPackager!"));
        return false;
    }

    // set icons
    wxIconBundle iconBundle;
    iconBundle.AddIcon(wxIcon(wxp16x16_xpm));
    iconBundle.AddIcon(wxIcon(wxp32x32_xpm));
    SetIcons(iconBundle);

    // init help system
    m_bestHelp.Initialize(wxStandardPaths::Get().GetDataDir() +
                          wxFileName::GetPathSeparator() + wxT("docs") +
                          wxFileName::GetPathSeparator() + wxT("user") + 
                          wxFileName::GetPathSeparator() + wxT("ch03.html"));

    // create menu bar
    wxMenuBar* menuBar = new wxMenuBar;

    // File menu
    wxMenu* menuFile = new wxMenu;
    menuFile->Append(wxID_NEW);
    menuFile->Append(wxID_OPEN, wxT("Open..."));
    menuFile->Append(wxID_SAVE);
    menuFile->Append(wxID_SAVEAS);
    menuFile->Append(wxID_CLOSE);
    menuFile->AppendSeparator();
    menuFile->Append(wxID_EXIT);

    // a separator is automatically added !
    m_mru.UseMenu(menuFile);

    // load most recently used (MRU) filelist
    wxConfigBase::Get()->SetPath(wxT("/LastFiles"));
    m_mru.Load(*wxConfigBase::Get());

#if 0
    // Edit menu
    wxMenu *menuEdit = new wxMenu;
    menuEdit->Append(wxID_UNDO,   _("&Undo\tCtrl+Z"));
    menuEdit->Append(wxID_REDO,   _("&Redo\tCtrl+Shift+Z"));
    menuEdit->AppendSeparator();
    menuEdit->Append(wxID_CUT,    _("Cu&t\tCtrl+X"));
    menuEdit->Append(wxID_COPY,   _("&Copy\tCtrl+C"));
    menuEdit->Append(wxID_PASTE,  _("&Paste\tCtrl+V"));
    menuEdit->Append(wxID_CLEAR,  _("&Delete\tDel"));
    menuEdit->AppendSeparator();
    menuEdit->Append(wxID_FIND,             _("&Find\tCtrl+F"));
    menuEdit->Append(ID_SEC_FINDNEXT,    _("Find &next\tF3"));
    menuEdit->Append(ID_SEC_REPLACE,     _("&Replace\tCtrl+H"));
    menuEdit->Append(ID_SEC_REPLACENEXT, _("Replace &again\tShift+F4"));
    menuEdit->AppendSeparator();
    menuEdit->Append(ID_SEC_INDENTINC,   _("&Indent\tTab"));
    menuEdit->Append(ID_SEC_INDENTRED,   _("Uni&ndent\tShift+Tab"));
    menuEdit->AppendSeparator();
    menuEdit->Append(wxID_SELECTALL,   _("&Select all\tCtrl+A"));

    // View menu
    wxMenu *menuView = new wxMenu;
    menuView->AppendCheckItem(ID_SEC_WRAPMODEON, _("&Wrap mode\tCtrl+U"));
    menuView->AppendSeparator();
    menuView->AppendCheckItem(ID_SEC_DISPLAYEOL,  _("Show line &endings"));
    menuView->AppendCheckItem(ID_SEC_INDENTGUIDE, _("Show &indent guides"));
    menuView->AppendCheckItem(ID_SEC_LINENUMBER,  _("Show line &numbers"));
    menuView->AppendCheckItem(ID_SEC_LONGLINEON,  _("Show &long line marker"));
    menuView->AppendCheckItem(ID_SEC_WHITESPACE,  _("Show white&space"));
#endif

    // Package menu
    wxMenu *packageMenu = new wxMenu;

    {
        wxMenuItem *theItem = new wxMenuItem(packageMenu, ID_PFRAME_CHECK, _T("Check...\tCtrl-C"),
                                            _T("Check if the current package descriptor is valid."));

#ifdef __WXGTK20__
        theItem->SetBitmap(wxArtProvider::GetBitmap(wxT("gtk-info")));
#endif
        packageMenu->Append(theItem);
    }

    {
        wxMenuItem *theItem = new wxMenuItem(packageMenu, ID_PFRAME_REPACKAGE, _T("Pack...\tCtrl-P"),
                                            _T("Use the info in the 'Packaging' tab to create the compressed package."));
#ifdef __WXGTK20__
        theItem->SetBitmap(wxArtProvider::GetBitmap(wxT("gtk-execute")));
#endif
        packageMenu->Append(theItem);
    }

    {
        wxMenuItem *theItem = new wxMenuItem(packageMenu, ID_PFRAME_UPLOAD, _T("Upload...\tCtrl-U"),
                                            _T("Uploads the compressed package to an FTP server."));
#ifdef __WXGTK20__
        theItem->SetBitmap(wxArtProvider::GetBitmap(wxT("gtk-network")));
#endif
        packageMenu->Append(theItem);
    }

    // Help menu
    wxMenu *helpMenu = new wxMenu;
    helpMenu->Append(wxID_HELP, _T("Packager Guide"));
    helpMenu->Append(wxID_ABOUT, _T("&About"));

    // setup menubar
    menuBar->Append(menuFile, wxT("&File"));
#if 0
    menuBar->Append(menuEdit, wxT("&Edit"));
    menuBar->Append(menuView, wxT("&View"));
#endif
    menuBar->Append(packageMenu, wxT("&Package"));
    menuBar->Append(helpMenu, wxT("&Help"));
    SetMenuBar(menuBar);

    // setup statusbar
    CreateStatusBar(1);
    SetStatusText(wxT("Welcome to the wxWidgets packager!"));

    // setup main child
    m_prop = NULL;      // the line below will generate calls to OnPropPanelUserChange
                        // even handler which must be discarded (and thus we need a 
                        // valid value for m_prop)!
    m_prop = new wxPackagePropertiesPanel(this, -1);

    // set the min size of this frame
    // NB: the +10 is just for max security; the +50 is because there's
    //     the menubar+statusbar to take in count
    // NB2: there's probably a better way to solve this !
    wxSize childsz(m_prop->GetBestSize());
    SetMinSize(wxSize(childsz.GetWidth()+10,
                      childsz.GetHeight()+50));

    m_current = wxT("new");
    UpdateFrameTitle();

    return true;
}

void wxPackagerFrame::Open(const wxString &file)
{
    // NB: we need to preserve all whitespaces of the loaded document in order
    //     to be able to recreate it perfectly when saving it
    if (!m_currentXml.Load(file))
    {
        wxLogError(wxT("Could not load the '%s' file!"), file.c_str());
        return;
    }

    // set the package info into the properties panel
    wxPackageInfo pkg = m_currentXml.GetPackageInfo();
    m_prop->SetPackageInfo(m_currentXml, file);

    // remember this file
    m_mru.AddFileToHistory(file);
    m_current = file;

    if (!pkg.IsOk())
    {
        wxLogWarning(wxT("The package '%s' contains some errors!\nYou can use the 'Check' command from the 'Package' menu to get a list of those errors/warnings (some may have been fixed automatically)."),
                     file.c_str());

        // when opening an invalid WXP, during the load process
        // some errors may have been (partially) fixed automatically
        // (e.g. if category is "misc" instead of "miscellaneous" the
        //  XML loader will set the category to "invalid")... so to be
        // sure, mark the data as changed.
        m_prop->SetModified(true);
        // UpdateFrameTitle() is called below

        // proceed
    }

    // update the frame title
    UpdateFrameTitle();
}

void wxPackagerFrame::UpdateFrameTitle()
{
    wxString file = wxFileName(m_current).GetFullName();

    if (m_prop->IsModified())
        file += wxT(" *");

    SetTitle(FRAME_TITLE + 
             wxString::Format(wxT(" - [%s]"), file.c_str()));
}

bool wxPackagerFrame::CheckForErrors(bool showProceedQuestion)
{
    // use updated data !
    m_prop->TransferDataFromWindow();

    // NB: wxPropPanel::IsOk() will use wxLogWarning/Error if necessary so that
    //     we don't need to show an error message here
    wxLogBuffer *newl = new wxLogBuffer();
    wxLog *oldl = wxLog::SetActiveTarget(newl);
    
    bool ret = m_prop->IsOk();
    wxString log = newl->GetBuffer();
    
    wxLog::SetActiveTarget(oldl);

    if (!ret)
    {
        // there is something missing / wrong - however we don't want to
        // block the user from e.g. saving this WXP, so we better ask if
        // we should proceed or not.
        wxString msg;
        msg.Printf(_("There are some missing / wrong settings in this WXP:\n\n%s"),
                   log.c_str());

        if (showProceedQuestion)
        {
            msg += wxT("\n\nProceed anyway?");
            ret = wxMessageBox(msg, wxT("Warning"), wxICON_WARNING|wxYES_NO, this) == wxYES;
        }
        else
        {
            // just show the message box without any question
            wxMessageBox(msg, wxT("Warning"), wxICON_WARNING|wxOK, this);
        }
    }

    // delete now unused log target
    delete newl;

    return ret;
}

void wxPackagerFrame::Save()
{
    // NOTE: CheckForErrors() must have been already called by the caller!

    m_currentXml.SetPackageInfo(m_prop->GetPackageInfo());

    // automatic indentation is not necessary as we preserved all whitespaces
    // when loading the document
    if (!m_currentXml.Save(m_current))
    {
        wxLogError(wxT("Could not save to the '%s' file!"), m_current.c_str());
        return;
    }

    // clear modified flag
    m_prop->SetModified(false);
    UpdateFrameTitle();
}

void wxPackagerFrame::AskBeforeClosing()
{
    // are you sure you want to close it?
    if (m_prop->IsModified())
    {
        int reply = wxMessageBox(
            wxString::Format(_("The '%s' package descriptor has been modified.\n\nDo you want to save it?"),
                             m_current.c_str()),
            wxT("Warning"), wxYES_NO|wxICON_QUESTION, this);

        if (reply == wxYES)
        {
            wxCommandEvent ev;
            OnSaveAsPackage(ev);

            // continue closing the current package...
        }
    }
}

void wxPackagerFrame::Close()
{
    AskBeforeClosing();

    m_prop->SetPackageInfo(wxEmptyPackage);
    m_current = wxT("new");

    UpdateFrameTitle();
}

wxFileName wxPackagerFrame::GetPackageFileName(const wxPackage &pkg) const
{
    wxString root(m_prop->GetProjectAbsoluteRoot());

    // create output file name
    wxFileName output(root, pkg.SubstituteInfo(pkg.GetOutputFileName()));
    if (output.GetDirCount() > 0)
    {
         // don't put it in the project root but one level up
        output.RemoveLastDir();
    }

    return output;
}



// ----------------------------------------------------------------------------
// wxPackagerFrame - event handlers
// ----------------------------------------------------------------------------

void wxPackagerFrame::OnPropPanelUserChange(wxCommandEvent& WXUNUSED(event))
{
    if (!m_prop)
        return;

    UpdateFrameTitle();
}

void wxPackagerFrame::OnCloseWindow(wxCloseEvent& WXUNUSED(event))
{
    if (!IsIconized())
    {
        wxSize sz = GetSize();
        wxPoint pos = GetPosition();

        wxConfigBase::Get()->Write(_T("/Windows/Packager/MainWindowX"), pos.x);
        wxConfigBase::Get()->Write(_T("/Windows/Packager/MainWindowY"), pos.y);
        wxConfigBase::Get()->Write(_T("/Windows/Packager/MainWindowWidth"), sz.x);
        wxConfigBase::Get()->Write(_T("/Windows/Packager/MainWindowHeight"), sz.y);
    }

    wxConfigBase::Get()->SetPath(wxT("/LastFiles"));
    m_mru.Save(*wxConfigBase::Get());

    AskBeforeClosing();

    Destroy();
}

void wxPackagerFrame::OnUpdateUI(wxUpdateUIEvent& WXUNUSED(event))
{
    // if a file is loaded or the user modified the empty form, mark
    // the current data as "modified" (and thus as "closable")
    bool modified = m_prop->IsModified();

    GetMenuBar()->FindItem(wxID_SAVE)->Enable(modified);
    GetMenuBar()->FindItem(wxID_SAVEAS)->Enable(modified);
    GetMenuBar()->FindItem(wxID_CLOSE)->Enable(modified || m_current != wxT("new"));
}

void wxPackagerFrame::OnNewPackage(wxCommandEvent& WXUNUSED(event))
{
    wxExecute(wxT("packager"));

#if 0
    wxPackageNewWizard dlg(this);
    dlg.CentreOnParent();
    dlg.Run();
#endif
}

void wxPackagerFrame::OnOpenMRUPackage(wxCommandEvent& ev)
{
    Open(m_mru.GetHistoryFile(ev.GetId() - wxID_FILE1));
}

void wxPackagerFrame::OnOpenPackage(wxCommandEvent& WXUNUSED(event))
{
    wxFileDialog dlg(this, wxT("Choose the WXP to open"),
                    wxGetApp().GetDefaultPackageDir(), wxT(""),
                    wxT("wxWidgets package descriptors (*.wxp)|*.wxp"),
                    wxFD_OPEN|wxFD_FILE_MUST_EXIST);

    dlg.CentreOnParent();
    if (dlg.ShowModal() == wxID_OK)
        Open(dlg.GetPath());
}

void wxPackagerFrame::OnSavePackage(wxCommandEvent& ev)
{
    if (m_current.IsEmpty())
    {
        OnSaveAsPackage(ev);
        return;
    }

    if (!CheckForErrors())
        return;
    Save();
}

void wxPackagerFrame::OnSaveAsPackage(wxCommandEvent& WXUNUSED(ev))
{
    // NB: we want to show to the user errors eventually present
    //     *before* asking him the filename!
    if (!CheckForErrors())
        return;

    wxFileDialog dlg(this, wxT("Save as..."),
                     wxGetApp().GetDefaultPackageDir(), wxT(""),
                     wxT("wxWidgets package descriptors (*.wxp)|*.wxp"),
                     wxFD_SAVE);

    dlg.CentreOnParent();
    if (dlg.ShowModal() == wxID_OK)
    {
        m_current = dlg.GetPath();
        Save();
    }
}

void wxPackagerFrame::OnClosePackage(wxCommandEvent& WXUNUSED(ev))
{
    Close();
}

void wxPackagerFrame::OnAbout(wxCommandEvent& WXUNUSED(event))
{
    wxAboutDialogInfo info;
    info.SetName(_("wxWidgets Package Manager"));
    info.SetDescription(
        wxT("A tool for creating wxWidgets packages\n\n")
        wxT("Project kindly sponsored by Google and mentored by Julian Smart."));
    info.SetIcon(this->GetIcon());

    SetGlobalAboutInfo(info);
    wxAboutBox(info);
}

void wxPackagerFrame::OnExit(wxCommandEvent& WXUNUSED(event))
{
    AskBeforeClosing();

    // the following will call automatically OnCloseWindow
    wxFrame::Close(true);
}

void wxPackagerFrame::OnHelp(wxCommandEvent& WXUNUSED(event))
{
    m_bestHelp.DisplayContents();
}

void wxPackagerFrame::OnCheck(wxCommandEvent& WXUNUSED(event))
{
    if (CheckForErrors(false /* no proceed question */))
    {
        wxMessageBox(wxT("The package is valid!"),
                     wxT("Check successful"),
                     wxICON_INFORMATION|wxOK,
                     this);
    }
}

void wxPackagerFrame::OnRepackage(wxCommandEvent& WXUNUSED(event))
{
    // create the wxPackage instance
    wxPackageInfo info(m_prop->GetPackageInfo());
    wxPackage pkg(info);

    // create output file name
    wxFileName output = GetPackageFileName(pkg);
    if (output.GetName().IsEmpty())
    {
        // show this critical error message _before_ showing the warnings
        // with CheckForErrors()

        wxMessageBox(wxT("The output file name has not been defined. Cannot proceed."), 
                     wxT("Error"), wxOK|wxICON_ERROR, this);
        return;
    }

    // now show the warnings, if any
    if (!CheckForErrors())
        return;

    // mark the package as "decompressed"
    wxString root(m_prop->GetProjectAbsoluteRoot());
    pkg.SetDecompressedStatus(root);

    // does such file already exist?
    if (output.FileExists())
    {
        wxString msg;
        msg.Printf(wxT("The file '%s' already exists. Overwrite it?"),
            output.GetFullPath().c_str());

        int reply = 
            wxMessageBox(msg, wxT("Warning"), wxYES_NO|wxICON_WARNING, this);
        if (reply == wxNO)
            return;
    }

    // helper class
    class myProgressHandler : public wxProgressHandler
    {
    public:
        myProgressHandler(wxProgressDialog *p) { m_dialog=p; }

        bool OnUpdate(unsigned long progress, const wxString &text)
        {
            m_dialog->Update(progress, 
                             wxT("Compressing file:\n\n  ") + text + wxT("\n")); 
            wxYieldIfNeeded();
            return true; 
        }

        wxProgressDialog *m_dialog;
    };

    // create the progress dialog to show while compressing
    // NOTE: the spaces in the initial text are there to force the progress dialog
    //       to be big enough to contain the filenames of the compressed files
    wxProgressDialog dlg(wxT("Compressing"),
                         wxT("Creating the WXZ package... please wait.             \n\n\n"),
                         1000,
                         this);
    dlg.SetFocus();

    myProgressHandler handler(&dlg);
    if (pkg.Compress(output.GetFullPath(), &handler))
    {
        wxString str;

        str.Printf(wxT("The package has been successfully created as '%s'."),
                   output.GetFullPath().c_str());

        wxMessageBox(str);
    }
}

void wxPackagerFrame::OnUpload(wxCommandEvent& WXUNUSED(event))
{
    wxPackageInfo info(m_prop->GetPackageInfo());
    wxPackage pkg(info);

    // init file to upload
    wxFileName output = GetPackageFileName(pkg);
    if (output.GetName().IsEmpty())
    {
        wxMessageBox(wxT("The output file name has not been defined. Cannot proceed."), 
                     wxT("Error"), wxOK|wxICON_ERROR, this);
        return;
    }

    if (!output.FileExists())
    {
        wxString msg;
        msg.Printf(wxT("The file '%s' does not exist. Cannot upload it."),
                   output.GetFullPath().c_str());

        wxMessageBox(msg, wxT("Error"), wxOK|wxICON_ERROR, this);
        return;
    }

    pkg.SetDownloadedStatus(output.GetFullPath());

    // show the upload dialog
    wxPackageUploadDlg dlg(this);
    dlg.SetDefaults(pkg.GetUploadDestination());
    dlg.SetFileToUpload(output.GetFullPath());
    if (dlg.ShowModal() == wxID_OK)
    {
        // FIXME: need a better design here (with a secondary thread and progress dialog)

        wxWindowDisabler disableAll;
        wxBusyInfo wait(wxT("Uploading the file, please wait..."));

        pkg.UploadWithFTP(dlg.GetUser(), dlg.GetPassword(),
                          dlg.GetServer(), dlg.GetPath());
    }
}

