///////////////////////////////////////////////////////////////////////////////
// Name:        animateg.cpp
// Purpose:     wxAnimation and wxAnimationCtrl
// Author:      Julian Smart and Guillermo Rodriguez Garcia
// Modified by: Francesco Montorsi
// Created:     13/8/99
// RCS-ID:      $Id$
// Copyright:   (c) Julian Smart and Guillermo Rodriguez Garcia
// Licence:     wxWindows licence
///////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"

#ifdef __BORLANDC__
  #pragma hdrstop
#endif  //__BORLANDC__

#if wxUSE_ANIMATIONCTRL

#include "wx/animate.h"

#ifndef WX_PRECOMP
    #include "wx/log.h"
    #include "wx/image.h"
    #include "wx/dcmemory.h"
    #include "wx/dcclient.h"
    #include "wx/module.h"
#endif

#include "wx/wfstream.h"
#include "wx/gifdecod.h"
#include "wx/anidecod.h"

#include "wx/listimpl.cpp"
WX_DEFINE_LIST(wxAnimationDecoderList)

wxAnimationDecoderList wxAnimation::sm_handlers;


// ----------------------------------------------------------------------------
// wxAnimation
// ----------------------------------------------------------------------------

IMPLEMENT_DYNAMIC_CLASS(wxAnimation, wxAnimationBase)
#define M_ANIMDATA      static_cast<wxAnimationDecoder*>(m_refData)

wxSize wxAnimation::GetSize() const
{
    wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );

    return M_ANIMDATA->GetAnimationSize();
}

unsigned int wxAnimation::GetFrameCount() const
{
    wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );

    return M_ANIMDATA->GetFrameCount();
}

wxImage wxAnimation::GetFrame(unsigned int i) const
{
    wxCHECK_MSG( IsOk(), wxNullImage, wxT("invalid animation") );

    wxImage ret;
    if (!M_ANIMDATA->ConvertToImage(i, &ret))
        return wxNullImage;
    return ret;
}

int wxAnimation::GetDelay(unsigned int i) const
{
    wxCHECK_MSG( IsOk(), 0, wxT("invalid animation") );

    return M_ANIMDATA->GetDelay(i);
}

wxPoint wxAnimation::GetFramePosition(unsigned int frame) const
{
    wxCHECK_MSG( IsOk(), wxDefaultPosition, wxT("invalid animation") );

    return M_ANIMDATA->GetFramePosition(frame);
}

wxSize wxAnimation::GetFrameSize(unsigned int frame) const
{
    wxCHECK_MSG( IsOk(), wxDefaultSize, wxT("invalid animation") );

    return M_ANIMDATA->GetFrameSize(frame);
}

wxAnimationDisposal wxAnimation::GetDisposalMethod(unsigned int frame) const
{
    wxCHECK_MSG( IsOk(), wxANIM_UNSPECIFIED, wxT("invalid animation") );

    return M_ANIMDATA->GetDisposalMethod(frame);
}

wxColour wxAnimation::GetTransparentColour(unsigned int frame) const
{
    wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );

    return M_ANIMDATA->GetTransparentColour(frame);
}

wxColour wxAnimation::GetBackgroundColour() const
{
    wxCHECK_MSG( IsOk(), wxNullColour, wxT("invalid animation") );

    return M_ANIMDATA->GetBackgroundColour();
}

bool wxAnimation::LoadFile(const wxString& filename, wxAnimationType type)
{
    wxFileInputStream stream(filename);
    if ( !stream.IsOk() )
        return false;

    return Load(stream, type);
}

bool wxAnimation::Load(wxInputStream &stream, wxAnimationType type)
{
    UnRef();

    const wxAnimationDecoder *handler;
    if ( type == wxANIMATION_TYPE_ANY )
    {
        for ( wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
              node; node = node->GetNext() )
        {
            handler=(const wxAnimationDecoder*)node->GetData();

            if ( handler->CanRead(stream) )
            {
                // do a copy of the handler from the static list which we will own
                // as our reference data
                m_refData = handler->Clone();
                return M_ANIMDATA->Load(stream);
            }
        }

        wxLogWarning( _("No handler found for animation type.") );
        return false;
    }

    handler = FindHandler(type);

    if (handler == NULL)
    {
        wxLogWarning( _("No animation handler for type %ld defined."), type );

        return false;
    }


    // do a copy of the handler from the static list which we will own
    // as our reference data
    m_refData = handler->Clone();

    if (stream.IsSeekable() && !M_ANIMDATA->CanRead(stream))
    {
        wxLogError(_("Animation file is not of type %ld."), type);
        return false;
    }
    else
        return M_ANIMDATA->Load(stream);
}


// ----------------------------------------------------------------------------
// animation decoders
// ----------------------------------------------------------------------------

void wxAnimation::AddHandler( wxAnimationDecoder *handler )
{
    // Check for an existing handler of the type being added.
    if (FindHandler( handler->GetType() ) == 0)
    {
        sm_handlers.Append( handler );
    }
    else
    {
        // This is not documented behaviour, merely the simplest 'fix'
        // for preventing duplicate additions.  If someone ever has
        // a good reason to add and remove duplicate handlers (and they
        // may) we should probably refcount the duplicates.

        wxLogDebug( wxT("Adding duplicate animation handler for '%d' type"),
                    handler->GetType() );
        delete handler;
    }
}

void wxAnimation::InsertHandler( wxAnimationDecoder *handler )
{
    // Check for an existing handler of the type being added.
    if (FindHandler( handler->GetType() ) == 0)
    {
        sm_handlers.Insert( handler );
    }
    else
    {
        // see AddHandler for additional comments.
        wxLogDebug( wxT("Inserting duplicate animation handler for '%d' type"),
                    handler->GetType() );
        delete handler;
    }
}

const wxAnimationDecoder *wxAnimation::FindHandler( wxAnimationType animType )
{
    wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
    while (node)
    {
        const wxAnimationDecoder *handler = (const wxAnimationDecoder *)node->GetData();
        if (handler->GetType() == animType) return handler;
        node = node->GetNext();
    }
    return 0;
}

void wxAnimation::InitStandardHandlers()
{
#if wxUSE_GIF
    AddHandler(new wxGIFDecoder);
#endif // wxUSE_GIF
#if wxUSE_ICO_CUR
    AddHandler(new wxANIDecoder);
#endif // wxUSE_ICO_CUR
}

void wxAnimation::CleanUpHandlers()
{
    wxAnimationDecoderList::compatibility_iterator node = sm_handlers.GetFirst();
    while (node)
    {
        wxAnimationDecoder *handler = (wxAnimationDecoder *)node->GetData();
        wxAnimationDecoderList::compatibility_iterator next = node->GetNext();
        delete handler;
        node = next;
    }

    sm_handlers.Clear();
}


// A module to allow wxAnimation initialization/cleanup
// without calling these functions from app.cpp or from
// the user's application.

class wxAnimationModule: public wxModule
{
DECLARE_DYNAMIC_CLASS(wxAnimationModule)
public:
    wxAnimationModule() {}
    bool OnInit() { wxAnimation::InitStandardHandlers(); return true; }
    void OnExit() { wxAnimation::CleanUpHandlers(); }
};

IMPLEMENT_DYNAMIC_CLASS(wxAnimationModule, wxModule)


// ----------------------------------------------------------------------------
// wxAnimationCtrl
// ----------------------------------------------------------------------------

IMPLEMENT_CLASS(wxAnimationCtrl, wxAnimationCtrlBase)
BEGIN_EVENT_TABLE(wxAnimationCtrl, wxAnimationCtrlBase)
    EVT_PAINT(wxAnimationCtrl::OnPaint)
    EVT_SIZE(wxAnimationCtrl::OnSize)
    EVT_TIMER(wxID_ANY, wxAnimationCtrl::OnTimer)
END_EVENT_TABLE()

void wxAnimationCtrl::Init()
{
    m_currentFrame = 0;
    m_looped = false;
    m_isPlaying = false;

    // use the window background colour by default to be consistent
    // with the GTK+ native version
    m_useWinBackgroundColour = true;
}

bool wxAnimationCtrl::Create(wxWindow *parent, wxWindowID id,
            const wxAnimation& animation, const wxPoint& pos,
            const wxSize& size, long style, const wxString& name)
{
    m_timer.SetOwner(this);

    if (!base_type::Create(parent, id, pos, size, style, wxDefaultValidator, name))
        return false;

    // by default we get the same background colour of our parent
    SetBackgroundColour(parent->GetBackgroundColour());

    SetAnimation(animation);

    return true;
}

wxAnimationCtrl::~wxAnimationCtrl()
{
    Stop();
}

bool wxAnimationCtrl::LoadFile(const wxString& filename, wxAnimationType type)
{
    wxFileInputStream fis(filename);
    if (!fis.Ok())
        return false;
    return Load(fis, type);
}

bool wxAnimationCtrl::Load(wxInputStream& stream, wxAnimationType type)
{
    wxAnimation anim;
    if ( !anim.Load(stream, type) || !anim.IsOk() )
        return false;

    SetAnimation(anim);
    return true;
}

wxSize wxAnimationCtrl::DoGetBestSize() const
{
    if (m_animation.IsOk() && !this->HasFlag(wxAC_NO_AUTORESIZE))
        return m_animation.GetSize();

    return wxSize(100, 100);
}

void wxAnimationCtrl::SetAnimation(const wxAnimation& animation)
{
    if (IsPlaying())
        Stop();

    // set new animation even if it's wxNullAnimation
    m_animation = animation;
    if (!m_animation.IsOk())
    {
        DisplayStaticImage();
        return;
    }

    if (m_animation.GetBackgroundColour() == wxNullColour)
        SetUseWindowBackgroundColour();
    if (!this->HasFlag(wxAC_NO_AUTORESIZE))
        FitToAnimation();

    DisplayStaticImage();
}

void wxAnimationCtrl::SetInactiveBitmap(const wxBitmap &bmp)
{
    // if the bitmap has an associated mask, we need to set our background to
    // the colour of our parent otherwise when calling DrawCurrentFrame()
    // (which uses the bitmap's mask), our background colour would be used for
    // transparent areas - and that's not what we want (at least for
    // consistency with the GTK version)
    if ( bmp.GetMask() != NULL && GetParent() != NULL )
        SetBackgroundColour(GetParent()->GetBackgroundColour());

    wxAnimationCtrlBase::SetInactiveBitmap(bmp);
}

void wxAnimationCtrl::FitToAnimation()
{
    SetSize(m_animation.GetSize());
}

bool wxAnimationCtrl::SetBackgroundColour(const wxColour& colour)
{
    if ( !wxWindow::SetBackgroundColour(colour) )
        return false;

    // if not playing, then this change must be seen immediately (unless
    // there's an inactive bitmap set which has higher priority than bg colour)
    if ( !IsPlaying() )
        DisplayStaticImage();

    return true;
}


// ----------------------------------------------------------------------------
// wxAnimationCtrl - stop/play methods
// ----------------------------------------------------------------------------

void wxAnimationCtrl::Stop()
{
    m_timer.Stop();
    m_isPlaying = false;

    // reset frame counter
    m_currentFrame = 0;

    DisplayStaticImage();
}

bool wxAnimationCtrl::Play(bool looped)
{
    if (!m_animation.IsOk())
        return false;

    m_looped = looped;
    m_currentFrame = 0;

    if (!RebuildBackingStoreUpToFrame(0))
        return false;

    m_isPlaying = true;

    // do a ClearBackground() to avoid that e.g. the custom static bitmap which
    // was eventually shown previously remains partially drawn
    ClearBackground();

    // DrawCurrentFrame() will use our updated backing store
    wxClientDC clientDC(this);
    DrawCurrentFrame(clientDC);

    // start the timer
    int delay = m_animation.GetDelay(0);
    if (delay == 0)
        delay = 1;      // 0 is invalid timeout for wxTimer.
    m_timer.Start(delay, true);

    return true;
}



// ----------------------------------------------------------------------------
// wxAnimationCtrl - rendering methods
// ----------------------------------------------------------------------------

bool wxAnimationCtrl::RebuildBackingStoreUpToFrame(unsigned int frame)
{
    // if we've not created the backing store yet or it's too
    // small, then recreate it
    wxSize sz = m_animation.GetSize(),
           winsz = GetClientSize();
    int w = wxMin(sz.GetWidth(), winsz.GetWidth());
    int h = wxMin(sz.GetHeight(), winsz.GetHeight());

    if ( !m_backingStore.IsOk() ||
            m_backingStore.GetWidth() < w || m_backingStore.GetHeight() < h )
    {
        if (!m_backingStore.Create(w, h))
            return false;
    }

    wxMemoryDC dc;
    dc.SelectObject(m_backingStore);

    // Draw the background
    DisposeToBackground(dc);

    // Draw all intermediate frames that haven't been removed from the animation
    for (unsigned int i = 0; i < frame; i++)
    {
        if (m_animation.GetDisposalMethod(i) == wxANIM_DONOTREMOVE ||
            m_animation.GetDisposalMethod(i) == wxANIM_UNSPECIFIED)
        {
            DrawFrame(dc, i);
        }
        else if (m_animation.GetDisposalMethod(i) == wxANIM_TOBACKGROUND)
            DisposeToBackground(dc, m_animation.GetFramePosition(i),
                                    m_animation.GetFrameSize(i));
    }

    // finally draw this frame
    DrawFrame(dc, frame);
    dc.SelectObject(wxNullBitmap);

    return true;
}

void wxAnimationCtrl::IncrementalUpdateBackingStore()
{
    wxMemoryDC dc;
    dc.SelectObject(m_backingStore);

    // OPTIMIZATION:
    // since wxAnimationCtrl can only play animations forward, without skipping
    // frames, we can be sure that m_backingStore contains the m_currentFrame-1
    // frame and thus we just need to dispose the m_currentFrame-1 frame and
    // render the m_currentFrame-th one.

    if (m_currentFrame == 0)
    {
        // before drawing the first frame always dispose to bg colour
        DisposeToBackground(dc);
    }
    else
    {
        switch (m_animation.GetDisposalMethod(m_currentFrame-1))
        {
        case wxANIM_TOBACKGROUND:
            DisposeToBackground(dc, m_animation.GetFramePosition(m_currentFrame-1),
                                    m_animation.GetFrameSize(m_currentFrame-1));
            break;

        case wxANIM_TOPREVIOUS:
            // this disposal should never be used too often.
            // E.g. GIF specification explicitely say to keep the usage of this
            //      disposal limited to the minimum.
            // In fact it may require a lot of time to restore
            if (m_currentFrame == 1)
            {
                // if 0-th frame disposal is to restore to previous frame,
                // the best we can do is to restore to background
                DisposeToBackground(dc);
            }
            else
                if (!RebuildBackingStoreUpToFrame(m_currentFrame-2))
                    Stop();
            break;

        case wxANIM_DONOTREMOVE:
        case wxANIM_UNSPECIFIED:
            break;
        }
    }

    // now just draw the current frame on the top of the backing store
    DrawFrame(dc, m_currentFrame);
    dc.SelectObject(wxNullBitmap);
}

void wxAnimationCtrl::DisplayStaticImage()
{
    wxASSERT(!IsPlaying());

    // m_bmpStaticReal will be updated only if necessary...
    UpdateStaticImage();

    if (m_bmpStaticReal.IsOk())
    {
        // copy the inactive bitmap in the backing store
        // eventually using the mask if the static bitmap has one
        if ( m_bmpStaticReal.GetMask() )
        {
            wxMemoryDC temp;
            temp.SelectObject(m_backingStore);
            DisposeToBackground(temp);
            temp.DrawBitmap(m_bmpStaticReal, 0, 0, true /* use mask */);
        }
        else
            m_backingStore = m_bmpStaticReal;
    }
    else
    {
        // put in the backing store the first frame of the animation
        if (!m_animation.IsOk() ||
            !RebuildBackingStoreUpToFrame(0))
        {
            m_animation = wxNullAnimation;
            DisposeToBackground();
        }
    }

    Refresh();
}

void wxAnimationCtrl::DrawFrame(wxDC &dc, unsigned int frame)
{
    // PERFORMANCE NOTE:
    // this draw stuff is not as fast as possible: the wxAnimationDecoder
    // needs first to convert from its internal format to wxImage RGB24;
    // the wxImage is then converted as a wxBitmap and finally blitted.
    // If wxAnimationDecoder had a function to convert directly from its
    // internal format to a port-specific wxBitmap, it would be somewhat faster.
    wxBitmap bmp(m_animation.GetFrame(frame));
    dc.DrawBitmap(bmp, m_animation.GetFramePosition(frame),
                  true /* use mask */);
}

void wxAnimationCtrl::DrawCurrentFrame(wxDC& dc)
{
    wxASSERT( m_backingStore.IsOk() );

    // m_backingStore always contains the current frame
    dc.DrawBitmap(m_backingStore, 0, 0, true /* use mask in case it's present */);
}

void wxAnimationCtrl::DisposeToBackground()
{
    // clear the backing store
    wxMemoryDC dc;
    dc.SelectObject(m_backingStore);
    if ( dc.IsOk() )
        DisposeToBackground(dc);
}

void wxAnimationCtrl::DisposeToBackground(wxDC& dc)
{
    wxColour col = IsUsingWindowBackgroundColour()
                    ? GetBackgroundColour()
                    : m_animation.GetBackgroundColour();

    wxBrush brush(col);
    dc.SetBackground(brush);
    dc.Clear();
}

void wxAnimationCtrl::DisposeToBackground(wxDC& dc, const wxPoint &pos, const wxSize &sz)
{
    wxColour col = IsUsingWindowBackgroundColour()
                    ? GetBackgroundColour()
                    : m_animation.GetBackgroundColour();
    wxBrush brush(col);
    dc.SetBrush(brush);         // SetBrush and not SetBackground !!
    dc.SetPen(*wxTRANSPARENT_PEN);
    dc.DrawRectangle(pos, sz);
}

// ----------------------------------------------------------------------------
// wxAnimationCtrl - event handlers
// ----------------------------------------------------------------------------

void wxAnimationCtrl::OnPaint(wxPaintEvent& WXUNUSED(event))
{
    // VERY IMPORTANT: the wxPaintDC *must* be created in any case
    wxPaintDC dc(this);

    if ( m_backingStore.IsOk() )
    {
        // NOTE: we draw the bitmap explicitely ignoring the mask (if any);
        //       i.e. we don't want to combine the backing store with the
        //       possibly wrong preexisting contents of the window!
        dc.DrawBitmap(m_backingStore, 0, 0, false /* no mask */);
    }
    else
    {
        // m_animation is not valid and thus we don't have a valid backing store...
        // clear then our area to the background colour
        DisposeToBackground(dc);
    }
}

void wxAnimationCtrl::OnTimer(wxTimerEvent &WXUNUSED(event))
{
    m_currentFrame++;
    if (m_currentFrame == m_animation.GetFrameCount())
    {
        // Should a non-looped animation display the last frame?
        if (!m_looped)
        {
            Stop();
            return;
        }
        else
            m_currentFrame = 0;     // let's restart
    }

    IncrementalUpdateBackingStore();

    wxClientDC dc(this);
    DrawCurrentFrame(dc);

#ifdef __WXMAC__
    // without this, the animation currently doesn't redraw under Mac
    Refresh();
#endif // __WXMAC__

    // Set the timer for the next frame
    int delay = m_animation.GetDelay(m_currentFrame);
    if (delay == 0)
        delay = 1;      // 0 is invalid timeout for wxTimer.
    m_timer.Start(delay, true);
}

void wxAnimationCtrl::OnSize(wxSizeEvent &WXUNUSED(event))
{
    // NB: resizing an animation control may take a lot of time
    //     for big animations as the backing store must be
    //     extended and rebuilt. Try to avoid it e.g. using
    //     a null proportion value for your wxAnimationCtrls
    //     when using them inside sizers.
    if (m_animation.IsOk())
    {
        // be careful to change the backing store *only* if we are
        // playing the animation as otherwise we may be displaying
        // the inactive bitmap and overwriting the backing store
        // with the last played frame is wrong in this case
        if (IsPlaying())
        {
            if (!RebuildBackingStoreUpToFrame(m_currentFrame))
                Stop();     // in case we are playing
        }
    }
}

#endif // wxUSE_ANIMATIONCTRL

