/////////////////////////////////////////////////////////////////////////////
// Name:        downloadprogresspanel.cpp
// Purpose:     wxDownloadProgressPanel
// Author:      Francesco Montorsi
// Modified by:
// Created:     15/07/2006 16:25:14
// RCS-ID:      $Id: downloadprogresspanel.cpp,v 1.8 2007/01/31 14:39:31 frm Exp $
// Copyright:   (c) 2006 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 "guipm/downloadprogresspanel.h"
#include "wx/oscopectrl.h"
#include "wx/stdpaths.h"


// ----------------------------------------------------------------------------
// wxDownloadProgressPanel
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS( wxDownloadProgressPanel, wxProgressPanel )
BEGIN_EVENT_TABLE( wxDownloadProgressPanel, wxProgressPanel )

    EVT_TIMER(ID_DOWNLOADPROGRESS_TIMER, wxDownloadProgressPanel::OnTimer)

    // download thread events
    EVT_DOWNLOAD_UPDATE(ID_DOWNLOADPROGRESS_THREAD, 
                        wxDownloadProgressPanel::OnDownloadProgress)
    EVT_DOWNLOAD_COMPLETE(ID_DOWNLOADPROGRESS_THREAD, 
                          wxDownloadProgressPanel::OnDownloadComplete)
    EVT_DOWNLOAD_ABORTED(ID_DOWNLOADPROGRESS_THREAD, 
                         wxDownloadProgressPanel::OnDownloadAbort)
    EVT_DOWNLOAD_USER_ABORTED(ID_DOWNLOADPROGRESS_THREAD, 
                              wxDownloadProgressPanel::OnDownloadUserAbort)
END_EVENT_TABLE()


wxDownloadProgressPanel::wxDownloadProgressPanel( )
{
}

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

bool wxDownloadProgressPanel::Create( wxWindow* parent, wxWindowID id, const wxPoint& pos, const wxSize& size, long style )
{
    m_bThreadExists = false;

////@begin wxDownloadProgressPanel member initialisation
    m_pRemainingTime = NULL;
    m_pElapsedTime = NULL;
    m_pSize = NULL;
    m_pSpeed = NULL;
    m_pDestination = NULL;
    m_pAnimation = NULL;
    m_pOScope = NULL;
    m_pGauge = NULL;
////@end wxDownloadProgressPanel member initialisation

////@begin wxDownloadProgressPanel creation
    wxProgressPanel::Create( parent, id, pos, size, style );

    CreateControls();
    if (GetSizer())
    {
        GetSizer()->SetSizeHints(this);
    }
////@end wxDownloadProgressPanel creation
    return true;
}

void wxDownloadProgressPanel::CreateControls()
{
	// the label and the URL will be set later
	m_pSource = new wxHyperlinkCtrl(this, ID_DOWNLOADPROGRESS_SOURCE,
		                            wxT("Not Available"), wxEmptyString,
                                    wxDefaultPosition, wxDefaultSize,
                                    wxNO_BORDER|wxHL_CONTEXTMENU|wxHL_ALIGN_LEFT);

    wxString imgpath = wxStandardPaths::Get().GetDataDir() + 
                       wxFileName::GetPathSeparator() + 
                       wxT("images") + wxFileName::GetPathSeparator();
    m_animation.LoadFile(imgpath + wxT("internetfiletransfer.gif"));


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

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

    wxBoxSizer* itemBoxSizer3 = new wxBoxSizer(wxHORIZONTAL);
    itemBoxSizer2->Add(itemBoxSizer3, 0, wxGROW, 5);

    wxFlexGridSizer* itemFlexGridSizer4 = new wxFlexGridSizer(2, 2, 0, 15);
    itemFlexGridSizer4->AddGrowableCol(1);
    itemBoxSizer3->Add(itemFlexGridSizer4, 1, wxALIGN_CENTER_VERTICAL|wxALL, 5);

    wxStaticText* itemStaticText5 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Time left:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemFlexGridSizer4->Add(itemStaticText5, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    m_pRemainingTime = new wxStaticText( itemProgressPanel1, ID_DOWNLOADPROGRESS_TIMELEFT, _("Static text"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
    itemFlexGridSizer4->Add(m_pRemainingTime, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    wxStaticText* itemStaticText7 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Elapsed time:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemFlexGridSizer4->Add(itemStaticText7, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    m_pElapsedTime = new wxStaticText( itemProgressPanel1, ID_DOWNLOADPROGRESS_ELAPSEDTIME, _("Static text"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
    itemFlexGridSizer4->Add(m_pElapsedTime, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    wxStaticText* itemStaticText9 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Size:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemFlexGridSizer4->Add(itemStaticText9, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    m_pSize = new wxStaticText( itemProgressPanel1, ID_DOWNLOADPROGRESS_SIZE, _("Static text"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
    itemFlexGridSizer4->Add(m_pSize, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    wxStaticText* itemStaticText11 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Speed:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemFlexGridSizer4->Add(itemStaticText11, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    m_pSpeed = new wxStaticText( itemProgressPanel1, ID_DOWNLOADPROGRESS_SPEED, _("Static text"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
    itemFlexGridSizer4->Add(m_pSpeed, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    wxStaticText* itemStaticText13 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Downloading:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemFlexGridSizer4->Add(itemStaticText13, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    wxWindow* itemWindow14 = (wxWindow*) FindWindow(ID_DOWNLOADPROGRESS_SOURCE);
    wxASSERT( itemWindow14 != NULL );
    itemFlexGridSizer4->Add(itemWindow14, 0, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP, 5);

    wxStaticText* itemStaticText15 = new wxStaticText( itemProgressPanel1, wxID_STATIC, _("Destination:"), wxDefaultPosition, wxDefaultSize, 0 );
    itemFlexGridSizer4->Add(itemStaticText15, 0, wxALIGN_LEFT|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    m_pDestination = new wxStaticText( itemProgressPanel1, ID_DOWNLOADPROGRESS_DESTINATION, _("Static text"), wxDefaultPosition, wxDefaultSize, wxST_NO_AUTORESIZE );
    itemFlexGridSizer4->Add(m_pDestination, 1, wxGROW|wxALIGN_CENTER_VERTICAL|wxLEFT|wxRIGHT|wxTOP|wxADJUST_MINSIZE, 5);

    m_pAnimation = new wxAnimationCtrl( itemProgressPanel1, ID_DOWNLOADPROGRESS_ANIMATION, m_animation );
    itemBoxSizer3->Add(m_pAnimation, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

    itemBoxSizer3->Add(5, 5, 0, wxALIGN_CENTER_VERTICAL|wxALL, 5);

    itemBoxSizer2->Add(50, 5, 0, wxALIGN_CENTER_HORIZONTAL|wxLEFT|wxRIGHT|wxBOTTOM, 5);

    m_pOScope = new wxOScopeCtrl( itemProgressPanel1, ID_DOWNLOADPROGRESS_SPEEDLOG, wxDefaultPosition, wxDefaultSize, 0 );
    itemBoxSizer2->Add(m_pOScope, 1, wxGROW|wxALL, 5);

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

    itemBoxSizer2->Add(50, 1, 0, wxALIGN_CENTER_HORIZONTAL|wxLEFT|wxRIGHT|wxBOTTOM, 5);

////@end wxDownloadProgressPanel content construction

#define NUM_MINUTES     5

    // setup the oscope control to show the speed of the downloads
    // in the last NUM_MINUTES minutes
    m_pOScope->SetFixedXRange(1000 * 60 * NUM_MINUTES);
    m_pOScope->SetXUnits(wxString::Format(wxT("Last %d minutes"), NUM_MINUTES));

    wxOScopeData dt(ID_DOWNLOADPROGRESS_TIMER_INTERVAL, 
                    0.0, 100.0, *wxGREEN);
    m_pOScope->AddData(dt);

    // NB: later in OnTimer() we tell the oscope control to update the
    //     y range keeping a little negative offset from the lowest
    //     sampled data point available, which in our case is always zero
    //     (we don't sample any negative value!); we do that to avoid that
    //     for some rounding errors the zero-speed line is clipped off the
    //     oscope control - but in any case we still want to have the ymin
    //     labeled as "0" so we explicitely set the ymin label here
    m_pOScope->SetYUnits(wxT("Kb/s"), wxT("0"));

    // run the timer even when not downloading...
    m_timer.SetOwner(this, ID_DOWNLOADPROGRESS_TIMER);
    m_timer.Start(ID_DOWNLOADPROGRESS_TIMER_INTERVAL);

    ClearControls();
}

bool wxDownloadProgressPanel::ShowToolTips()
{
    return true;
}

void wxDownloadProgressPanel::Start()
{
    wxFileOutputStream *out = new wxFileOutputStream(m_pkg.GetDownloadPath());
    if (!out->IsOk())
    {
        wxLogError(wxT("Problems creating the output file %s!"),
                   m_pkg.GetDownloadPath().c_str());
        return;
    }

    // reset the gauge GUI
    m_pGauge->SetValue(0);
    m_pGauge->SetRange(100);
    m_flag = wxDTF_CONTINUE;

    // by now, set it to false
    m_bSuccess = false;

    // just before launching the new thread, set up some things;
    // note that these must be done _before_ launching the thread
    // since it may happen that just after thread creation the thread
    // ends and the wxEVT_DOWNLOAD_COMPLETE is received before e.g.
    // the animation has started to play!
    m_bThreadExists = true;
    m_pAnimation->Play();

    wxString url = m_pkg.GetDownloadLink();
    wxASSERT_MSG(!url.IsEmpty(), 
                 wxT("packages without valid download links shouldn't have been queued!"));

    // launch the new download thread
    new wxDownloadThread(this, ID_DOWNLOADPROGRESS_THREAD, &m_flag, url, out);
}

void wxDownloadProgressPanel::Stop()
{
    // stop the thread but don't delete it
    m_flag = wxDTF_ABORT;

    // wait until the thread does not receive this pause command
    while (m_bThreadExists)
    {
        // process events
        wxYield();
    }

    // ClearControls() will be automatically called by wxPackageTaskQueuePanel
}

bool wxDownloadProgressPanel::IsProcessing() const
{
    return m_bThreadExists;
}

void wxDownloadProgressPanel::SetPackage(const wxPackage &pkg)
{
    wxProgressPanel::SetPackage(pkg);

    // update source label
    m_pSource->SetLabel(pkg.GetDownloadLink());
    m_pSource->SetURL(pkg.GetDownloadLink());
    m_pSource->Refresh();

    m_pDestination->SetLabel(pkg.GetDownloadPath());
}

wxString wxDownloadProgressPanel::GetLogData() const
{
    return wxString::Format(
        wxT("Download of %s (size of the compressed package) %s"),
        wxFileName::GetHumanReadableSize(
                (const wxULongLong &)m_pkg.GetCompressedSize()).c_str(),
        m_bSuccess ? wxT("was successful.") : wxT("failed!"));
}

wxString wxDownloadProgressPanel::GetLogLabel() const
{
    return wxString::Format(wxT("Download log for %s:"),
                            m_pkg.GetName().c_str());
}

void wxDownloadProgressPanel::ClearControls()
{
    m_pRemainingTime->SetLabel(wxT("Not available"));
    m_pElapsedTime->SetLabel(wxT("Not available"));
    m_pSpeed->SetLabel(wxT("Not available"));
    m_pSize->SetLabel(wxT("Not available"));

    m_pSource->SetURL(wxT("Not Available"));
    m_pSource->SetLabel(wxT("Not Available"));
    m_pSource->Refresh();

    m_pDestination->SetLabel(wxT("Not Available"));

    m_pRemainingTime->GetContainingSizer()->Layout();
}

void wxDownloadProgressPanel::DoEnd(bool success)
{
    // for some reason the download has ended...

    m_bSuccess = success;
    m_bThreadExists = false;

    // reset the GUI
    m_pGauge->SetValue(0);
    m_pAnimation->Stop();
    m_nLastSpeed = 0;

    // remove the partially downloaded file, if the download failed
    // (else it will force wxPM to show an annoying message 'cannot load package X'
    //  the next time the wxPM is started).
    wxString path = m_pkg.GetDownloadPath();
    if (success == false && wxFileExists(path))
    {
        bool removeSuccess;

        {
            wxLogNull trash;
            removeSuccess = wxRemoveFile(m_pkg.GetDownloadPath());
        }

        if (!removeSuccess)
            wxLogError(wxT("Couldn't remove the '%s' incomplete download. Please do remove it manually."), path.c_str());
    }

    // download ended...
    wxCommandEvent ev(success ? wxEVT_COMMAND_PROGRESS_END : wxEVT_COMMAND_PROGRESS_FAILED,
                      GetId());
    GetEventHandler()->AddPendingEvent(ev);
}


// ----------------------------------------------------------------------------
// wxDownloadProgressPanel - event handlers
// ----------------------------------------------------------------------------

void wxDownloadProgressPanel::OnDownloadProgress(wxDownloadEvent &ev)
{
    static wxDateTime lastLabelUpdate = wxDateTime::Now();

    // avoid flickering: update no more often than 200 ms
    if ((wxDateTime::Now() - lastLabelUpdate).GetMilliseconds() < 200)
        return;

    double fraction = ev.GetDownloadProgress();
    if (fraction != 0)
    {
        m_pGauge->SetValue((int)(fraction * 100));
    }
    else
    {
        // we don't know how much we progressed
        m_pGauge->Pulse();
    }

    if (m_pElapsedTime)
        m_pElapsedTime->SetLabel(ev.GetElapsedTime().Format());
    if (m_pRemainingTime)
        m_pRemainingTime->SetLabel(ev.GetEstimatedRemainingTime().Format());

    // update size & speed
    wxString currsize = wxFileName::GetHumanReadableSize(ev.GetCurrentSize()),
             totalsize = wxFileName::GetHumanReadableSize(ev.GetTotalSize());
    m_pSize->SetLabel(currsize + wxT(" / ") + totalsize);
    m_pSpeed->SetLabel(ev.GetHumanReadableDownloadSpeed());

    // add a new sample for the download speed
    m_nLastSpeed = ev.GetDownloadSpeed();

    lastLabelUpdate = wxDateTime::Now();
}

void wxDownloadProgressPanel::OnDownloadComplete(wxDownloadEvent &event)
{
    m_pkg.SetCompressedSize(event.GetTotalSize());
    DoEnd(true);
}

void wxDownloadProgressPanel::OnDownloadAbort(wxDownloadEvent &WXUNUSED(event))
{
    DoEnd(false);
}

void wxDownloadProgressPanel::OnDownloadUserAbort(wxDownloadEvent &WXUNUSED(event))
{
    DoEnd(false);
}

void wxDownloadProgressPanel::OnTimer(wxTimerEvent& WXUNUSED(evt))
{
    // update the oscope ctrl with the speed for the last event
    // NOTE: we need to convert from bytes/second to kilobyte/second
    m_pOScope->AddSampleDataAndUpdateRange(0, (float)(m_nLastSpeed.ToDouble() / 1000.),
                                              (float)-0.5, +10.0);
}
