/////////////////////////////////////////////////////////////////////////////
// Name:        extcmdprogresspanel.cpp
// Purpose:     wxExtCmdProgressPanel
// Author:      Julian Smart, Francesco Montorsi
// Modified by:
// Created:     15/06/2006 21:21:31
// RCS-ID:      $Id: extcmdprogresspanel.cpp,v 1.9 2007/02/01 11:49:13 frm Exp $
// Copyright:   (c) 2004 Francesco Montorsi
// Licence:     wxWidgets license
/////////////////////////////////////////////////////////////////////////////


// 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

#include <wx/txtstrm.h>
#include <wx/notebook.h>
#include "guipm/extcmdprogresspanel.h"


// ----------------------------------------------------------------------------
// wxExtCmdProgressPanel
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS( wxExtCmdProgressPanel, wxPanel )
DEFINE_EVENT_TYPE(wxEVT_COMMAND_PROGRESS_FAILED)
DEFINE_EVENT_TYPE(wxEVT_COMMAND_PROGRESS_END)
BEGIN_EVENT_TABLE( wxExtCmdProgressPanel, wxPanel )
    EVT_IDLE( wxExtCmdProgressPanel::OnIdle )
    EVT_END_PROCESS( ID_EXTCMD_PROCESS, wxExtCmdProgressPanel::OnProcessTerminate )
    EVT_UPDATE_UI( wxID_ANY, wxExtCmdProgressPanel::OnUpdateUI )
END_EVENT_TABLE()


wxExtCmdProgressPanel::wxExtCmdProgressPanel( )
{
}

wxExtCmdProgressPanel::wxExtCmdProgressPanel( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString &name )
{
    Create(parent, id, pos, size, style, name);
}

bool wxExtCmdProgressPanel::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style, const wxString &name )
{
    m_currProcess = NULL;
    m_pCommandList = NULL;
    m_pOutput = NULL;
    m_pGauge = NULL;
    m_stage = wxPBSS_INVALID;
    m_pid = 0;

    wxPanel::Create( parent, id, pos, size, style, name );

    CreateControls();
    GetSizer()->Fit(this);
    GetSizer()->SetSizeHints(this);
    Centre();

    return true;
}

void wxExtCmdProgressPanel::CreateControls()
{

////@begin wxExtCmdProgressPanel content construction
    wxExtCmdProgressPanel* itemProgressPanel1 = this;

    wxBoxSizer* itemBoxSizer2 = new wxBoxSizer(wxVERTICAL);
    itemProgressPanel1->SetSizer(itemBoxSizer2);

    wxStaticText* itemStaticText3 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Commands:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer2->Add(itemStaticText3, 0, wxALIGN_LEFT|wxALL|wxADJUST_MINSIZE, 5);

    wxString* m_pCommandListStrings = NULL;
    m_pCommandList = new wxListBox( itemProgressPanel1, ID_EXTCMD_COMMANDS, wxDefaultPosition, wxDefaultSize, 0, m_pCommandListStrings, wxLB_SINGLE );
    itemBoxSizer2->Add(m_pCommandList, 2, wxGROW|wxALL, 5);

    wxStaticText* itemStaticText5 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Output:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer2->Add(itemStaticText5, 0, wxALIGN_LEFT|wxALL|wxADJUST_MINSIZE, 5);

    m_pGauge = new wxGauge( itemProgressPanel1, ID_EXTCMD_GAUGE, 100, wxDefaultPosition, wxDefaultSize, wxGA_HORIZONTAL );
    m_pGauge->SetValue(0);
    itemBoxSizer2->Add(m_pGauge, 0, wxGROW|wxALL, 5);

    wxNotebook* itemNotebook7 = new wxNotebook( itemProgressPanel1, ID_EXTCMD_NOTEBOOK, wxDefaultPosition, wxDefaultSize, wxNB_TOP );

    wxPanel* itemPanel8 = new wxPanel( itemNotebook7, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
    wxBoxSizer* itemBoxSizer9 = new wxBoxSizer(wxVERTICAL);
    itemPanel8->SetSizer(itemBoxSizer9);

    m_pOutput = new wxTextCtrl( itemPanel8, ID_EXTCMD_OUTPUT, _T(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE );
    itemBoxSizer9->Add(m_pOutput, 1, wxGROW|wxALL, 5);

    itemNotebook7->AddPage(itemPanel8, _("Output"));

    wxPanel* itemPanel11 = new wxPanel( itemNotebook7, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL );
    wxBoxSizer* itemBoxSizer12 = new wxBoxSizer(wxVERTICAL);
    itemPanel11->SetSizer(itemBoxSizer12);

    m_pOutputErr = new wxTextCtrl( itemPanel11, ID_EXTCMD_OUTPUTERR, _T(""), wxDefaultPosition, wxDefaultSize, wxTE_MULTILINE );
    itemBoxSizer12->Add(m_pOutputErr, 1, wxGROW|wxALL, 5);

    itemNotebook7->AddPage(itemPanel11, _("Errors/Warnings"));

    itemBoxSizer2->Add(itemNotebook7, 5, wxGROW|wxALL, 5);

////@end wxExtCmdProgressPanel content construction
}

void wxExtCmdProgressPanel::SelectCurrProcess()
{
    wxASSERT(m_nCurrProcess >= 0);

    // prefix the current process string for a nice effect
    m_pCommandList->SetString(m_nCurrProcess,
        ID_EXTCMD_CURRPROCESS_PREFIX +
        m_pCommandList->GetString(m_nCurrProcess));

    m_pCommandList->SetSelection(m_nCurrProcess);
}

void wxExtCmdProgressPanel::DeselectCurrProcess()
{
    wxASSERT(m_nCurrProcess >= 0);

    // de-'select' the previous command
    wxString str = m_pCommandList->GetString(m_nCurrProcess);
    wxASSERT(str.StartsWith(ID_EXTCMD_CURRPROCESS_PREFIX));
    m_pCommandList->SetString(m_nCurrProcess,
        str.Right(str.Len() - wxString(ID_EXTCMD_CURRPROCESS_PREFIX).Len()));
}

void wxExtCmdProgressPanel::OnProcessStart()
{
    SelectCurrProcess();

    m_pGauge->SetValue((m_nCurrProcess+1) * 100 / m_cmd.GetCount());
}

wxString wxExtCmdProgressPanel::GetLogData() const
{
    wxString ret;

    if (m_stage == wxPBSS_INVALID)
        return wxEmptyString;

    ret = wxString::Format(
        wxT(" ===== %s LOG FOR %s - %s ===== \n\n"),
        wxPackageBuildSystemStage2String(m_stage).MakeUpper().c_str(), 
        m_pkg.GetName().c_str(),
        m_start.Format().c_str());

    for (size_t i=0; i< m_cmd.GetCount(); i++)
        ret += GetCmdString(i) + wxString(wxT(' '), 15) + 
               wxT(" [in path: ") + GetCmdPath(i) + wxT("]\n");

    ret += wxT("\n\n ===> Output:\n");
    ret += m_pOutput->GetValue();
    ret += wxT("\n\n ===> Errors:\n");
    ret += m_pOutputErr->GetValue();

    return ret;
}
wxString wxExtCmdProgressPanel::GetLogLabel() const
{
    return wxString::Format(wxT("Log for %s of %s %s"),
                            wxPackageBuildSystemStage2String(m_stage).MakeUpper().c_str(),
                            m_pkg.GetName().c_str(),
                            m_pkg.GetVersion().GetAsString().c_str());
}

void wxExtCmdProgressPanel::Start()
{
    wxASSERT(!m_timerIdleWakeUp.IsRunning());

    m_pOutput->Clear();
    m_pOutputErr->Clear();
    m_nCurrProcess = -1;
    m_timerIdleWakeUp.Start(100);
    m_start = wxDateTime::Now();
}

void wxExtCmdProgressPanel::Stop()
{
    //  wxASSERT(m_timerIdleWakeUp.IsRunning());    it could have been already stopped
    m_timerIdleWakeUp.Stop();

    // ask the current process to terminate
    if (m_pid != 0)
        m_currProcess->Kill(m_pid, wxSIGTERM);
}

void wxExtCmdProgressPanel::ClearControls()
{
    // reset the controls (gauge is reset in OnUpdateUI)
    m_pCommandList->Clear();
    m_pOutput->Clear();
    m_pOutputErr->Clear();
}

void wxExtCmdProgressPanel::SetPackage(const wxPackage &pkg)
{
    m_pkg=pkg;

    // cache the substituted array of commands for this package:
    UpdateCachedCommands();

    // add the commands to our listbox:
    m_pCommandList->Clear();
    for (size_t i=0; i<m_cmd.GetCount(); i++)
        m_pCommandList->Append(m_cmd[i]);
}

void wxExtCmdProgressPanel::UpdateCachedCommands()
{
    m_cmd = m_pkg.GetSubstCmdForStage(m_stage, wxPKGCMD_ONLY_CHANGED);

    // now filter out the invalid commands for current package:
    m_cmd = m_cmd.GetValidCommandsFor(m_pkg);
}

wxString wxExtCmdProgressPanel::GetCmdString(size_t n) const
{ 
    const wxPackageCommand &cmd = m_cmd[n]; 
    return cmd.GetProgram() + wxT(" ") + cmd.GetProgramArguments();
}

wxString wxExtCmdProgressPanel::GetCmdPath(size_t n) const
{
    // build the path where to exec the command
    wxFileName path = wxFileName::DirName(m_cmd[n].GetWorkingPath(), wxPATH_UNIX);

    wxString workDir(m_pkg.GetDecompressionPath());
    if (!path.MakeAbsolute(workDir) || !path.DirExists())
        path = wxFileName::DirName(workDir);

    return path.GetPath();
}



// ----------------------------------------------------------------------------
// wxExtCmdProgressPanel - event handlers
// ----------------------------------------------------------------------------

void wxExtCmdProgressPanel::OnTimer(wxTimerEvent& WXUNUSED(event))
{
    // force the system to send idle events
    wxWakeUpIdle();
}

void wxExtCmdProgressPanel::OnIdle(wxIdleEvent &ev)
{
    if (!m_timerIdleWakeUp.IsRunning())
        return;

    if (m_currProcess)
    {
        wxInputStream *stream = NULL;
        wxTextCtrl *output = NULL;

        // there is a process already running...
        if (m_currProcess->IsInputAvailable())
        {
            stream = m_currProcess->GetInputStream();
            output = m_pOutput;
        }
        else if (m_currProcess->IsErrorAvailable())
        {
            stream = m_currProcess->GetErrorStream();
            output = m_pOutputErr;
        }

        if (stream)
        {
            ev.RequestMore();
            wxTextInputStream tis(*stream);

#if 0
            // this assumes that the output is always line buffered
            wxString msg = tis.ReadLine() + wxT("\n");
#else
            static char buffer[4096];
            buffer[stream->Read(buffer, WXSIZEOF(buffer) - 1).LastRead()] = _T('\0');
            wxString msg(buffer, wxConvUTF8);
            msg = msg.BeforeLast(wxT('\n'));
#endif

            output->AppendText(msg);
        }
    }
    else if (++m_nCurrProcess >= (int)m_cmd.GetCount())
    {
        // no more commands to run !
        wxCommandEvent ev(wxEVT_COMMAND_PROGRESS_END, GetId());
        ev.SetInt(m_stage);
        ev.SetExtraLong(IsSuccessful());
        ev.SetString(m_pkg.GetName());

        GetEventHandler()->AddPendingEvent(ev);
    }
    else
    {
        // there's a command to run!

        // get next command to execute
        const wxPackageCommand &cmd = m_cmd[m_nCurrProcess];

        // update 'selected' command in the list box
        OnProcessStart();       // this must be called *before* wxExecute call

        // update this process' env var so that the
        // child process we're going to launch will inherit their values
        m_pkg.GetCompilerSettings().UpdateEnvVars();

        // set our current directory (previous commands could have changed it)
        wxString path = GetCmdPath(m_nCurrProcess);
        if (!wxSetWorkingDirectory(path))
        {
            wxLogError(_T("Cannot set the current working directory to '%s'."),
                       path.c_str());
            return;
        }

        m_currProcess = new wxProcess(this, ID_EXTCMD_PROCESS);
        m_currProcess->Redirect();

        wxString tolaunch = cmd.GetProgram();
        if (!tolaunch.IsEmpty())
        {
            wxString final = GetCmdString(m_nCurrProcess);
            wxLogDebug(wxT("Launching command '%s' in dir '%s'"), 
                       final.c_str(), wxGetCwd().c_str());

            // launch it !
            m_pid = wxExecute(final, wxEXEC_ASYNC|wxEXEC_MAKE_GROUP_LEADER, m_currProcess);
            if ( m_pid == 0 )
            {
                wxLogError(_T("Execution of '%s' failed."), cmd.GetCommand().c_str());
                wxDELETE(m_currProcess);
                DeselectCurrProcess();
            }
        }
        else     /* FIXME */
        {
            wxArrayString arr(m_pkg.GetCompilerSettings().GetCompilerPaths(wxPCPT_BIN));
            if (arr.GetCount() > 0)
            {
                wxString err;
                err.Printf(_("Cannot find the '%s' program in the compiler paths currently in use:\n\n"),
                           cmd.GetProgram().c_str());
                for (size_t i=0; i < arr.GetCount(); i++)
                    err += wxT(" => ") + arr[i] + wxT("\n");
                err += _("\n\nPlease configure appropriately the compiler paths in the 'Settings' dialog.");

                wxLogError(err);
            }
            else
            {
                wxLogError(_("The compiler paths for the '%s' format are empty! Cannot find the '%s' program.\nPlease configure appropriately the compiler paths in the 'Settings' dialog."),
                           wxPackageCompilerFormat2String(m_pkg.GetCompilerSettings().GetSelFormat()).c_str(),
                           cmd.GetProgram().c_str());
            }

            wxDELETE(m_currProcess);
            DeselectCurrProcess();
        }
    }
}

void wxExtCmdProgressPanel::OnProcessTerminate(wxProcessEvent &ev)
{
    wxIdleEvent fake;
    while (m_currProcess->IsInputAvailable() ||
           m_currProcess->IsErrorAvailable())
        OnIdle(fake);   // show the rest of the output

    // save exit code
    m_nExitCode = ev.GetExitCode();
    wxDELETE(m_currProcess);
    DeselectCurrProcess();

    // if this is the last process, reset the gauge
    if (m_nCurrProcess == (int)m_cmd.GetCount() - 1)
        m_pGauge->SetValue(0);
}

void wxExtCmdProgressPanel::OnUpdateUI(wxUpdateUIEvent &WXUNUSED(ev))
{
    if (!IsProcessing())
        m_pGauge->SetValue(0);      // reset the gauge
}

