/////////////////////////////////////////////////////////////////////////////
// Name:        src/common/anidecod.cpp
// Purpose:     wxANIDecoder, ANI reader for wxImage and wxAnimation
// Author:      Francesco Montorsi
// Copyright:   (c) Francesco Montorsi
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

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


#if wxUSE_STREAMS && wxUSE_ICO_CUR

#include "wx/anidecod.h"

#ifndef WX_PRECOMP
    #include "wx/palette.h"
#endif

#include <stdlib.h>
#include <string.h>

// static
wxCURHandler wxANIDecoder::sm_handler;

//---------------------------------------------------------------------------
// wxANIFrameInfo
//---------------------------------------------------------------------------

class wxANIFrameInfo
{
public:
    wxANIFrameInfo(unsigned int delay = 0, int idx = -1)
        { m_delay=delay; m_imageIndex=idx; }

    unsigned int m_delay;
    int m_imageIndex;
};

//---------------------------------------------------------------------------
// wxANIDecoder
//---------------------------------------------------------------------------

wxANIDecoder::wxANIDecoder()
{
}

wxANIDecoder::~wxANIDecoder()
{
}

bool wxANIDecoder::ConvertToImage(unsigned int frame, wxImage *image) const
{
    unsigned int idx = m_info[frame].m_imageIndex;
    *image = m_images[idx];       // copy
    return image->IsOk();
}


//---------------------------------------------------------------------------
// Data accessors
//---------------------------------------------------------------------------

wxSize wxANIDecoder::GetFrameSize(unsigned int WXUNUSED(frame)) const
{
    // all frames are of the same size...
    return m_szAnimation;
}

wxPoint wxANIDecoder::GetFramePosition(unsigned int WXUNUSED(frame)) const
{
    // all frames are of the same size...
    return wxPoint(0,0);
}

wxAnimationDisposal wxANIDecoder::GetDisposalMethod(unsigned int WXUNUSED(frame)) const
{
    // this disposal is implicit for all frames inside an ANI file
    return wxANIM_TOBACKGROUND;
}

long wxANIDecoder::GetDelay(unsigned int frame) const
{
    return m_info[frame].m_delay;
}

wxColour wxANIDecoder::GetTransparentColour(unsigned int frame) const
{
    unsigned int idx = m_info[frame].m_imageIndex;

    if (!m_images[idx].HasMask())
        return wxNullColour;

    return wxColour(m_images[idx].GetMaskRed(),
                    m_images[idx].GetMaskGreen(),
                    m_images[idx].GetMaskBlue());
}


//---------------------------------------------------------------------------
// ANI reading and decoding
//---------------------------------------------------------------------------

bool wxANIDecoder::DoCanRead(wxInputStream& stream) const
{
    wxInt32 FCC1, FCC2;
    wxUint32 datalen;

    wxInt32 riff32;
    memcpy( &riff32, "RIFF", 4 );
    wxInt32 list32;
    memcpy( &list32, "LIST", 4 );
    wxInt32 ico32;
    memcpy( &ico32, "icon", 4 );
    wxInt32 anih32;
    memcpy( &anih32, "anih", 4 );

    if ( stream.IsSeekable() && stream.SeekI(0) == wxInvalidOffset )
    {
        return false;
    }

    if ( !stream.Read(&FCC1, 4) )
        return false;

    if ( FCC1 != riff32 )
        return false;

    // we have a riff file:
    while ( stream.IsOk() )
    {
        if ( FCC1 == anih32 )
            return true;        // found the ANIH chunk - this should be an ANI file

        // we always have a data size:
        stream.Read(&datalen, 4);
        datalen = wxINT32_SWAP_ON_BE(datalen) ;

        // data should be padded to make even number of bytes
        if (datalen % 2 == 1) datalen ++ ;

        // now either data or a FCC:
        if ( (FCC1 == riff32) || (FCC1 == list32) )
        {
            stream.Read(&FCC2, 4);
        }
        else
        {
            if ( stream.SeekI(stream.TellI() + datalen) == wxInvalidOffset )
                return false;
        }

        // try to read next data chunk:
        if ( !stream.Read(&FCC1, 4) )
        {
            // reading failed -- either EOF or IO error, bail out anyhow
            return false;
        }
    }

    return false;
}

// the "anih" RIFF chunk
struct wxANIHeader
{
    wxInt32 cbSizeOf;     // Num bytes in AniHeader (36 bytes)
    wxInt32 cFrames;      // Number of unique Icons in this cursor
    wxInt32 cSteps;       // Number of Blits before the animation cycles
    wxInt32 cx;           // width of the frames
    wxInt32 cy;           // height of the frames
    wxInt32 cBitCount;    // bit depth
    wxInt32 cPlanes;      // 1
    wxInt32 JifRate;      // Default Jiffies (1/60th of a second) if rate chunk not present.
    wxInt32 flags;        // Animation Flag (see AF_ constants)

    // ANI files are always little endian so we need to swap bytes on big
    // endian architectures
#ifdef WORDS_BIGENDIAN
    void AdjustEndianness()
    {
        // this works because all our fields are wxInt32 and they must be
        // packed without holes between them (if they're not, they wouldn't map
        // to the file header!)
        wxInt32 * const start = (wxInt32 *)this;
        wxInt32 * const end = start + sizeof(wxANIHeader)/sizeof(wxInt32);
        for ( wxInt32 *p = start; p != end; p++ )
        {
            *p = wxINT32_SWAP_ALWAYS(*p);
        }
    }
#else
    void AdjustEndianness() { }
#endif
};

bool wxANIDecoder::Load( wxInputStream& stream )
{
    wxInt32 FCC1, FCC2;
    wxUint32 datalen;
    unsigned int globaldelay=0;

    wxInt32 riff32;
    memcpy( &riff32, "RIFF", 4 );
    wxInt32 list32;
    memcpy( &list32, "LIST", 4 );
    wxInt32 ico32;
    memcpy( &ico32, "icon", 4 );
    wxInt32 anih32;
    memcpy( &anih32, "anih", 4 );
    wxInt32 rate32;
    memcpy( &rate32, "rate", 4 );
    wxInt32 seq32;
    memcpy( &seq32, "seq ", 4 );

    if ( stream.IsSeekable() && stream.SeekI(0) == wxInvalidOffset )
    {
        return false;
    }

    if ( !stream.Read(&FCC1, 4) )
        return false;
    if ( FCC1 != riff32 )
        return false;

    m_nFrames = 0;
    m_szAnimation = wxDefaultSize;

    m_images.clear();
    m_info.clear();

    // we have a riff file:
    while ( !stream.Eof() )
    {
        // we always have a data size:
        if (!stream.Read(&datalen, 4))
            return false;

        datalen = wxINT32_SWAP_ON_BE(datalen);

        //data should be padded to make even number of bytes
        if (datalen % 2 == 1) datalen++;

        // now either data or a FCC:
        if ( (FCC1 == riff32) || (FCC1 == list32) )
        {
            if (!stream.Read(&FCC2, 4))
                return false;
        }
        else if ( FCC1 == anih32 )
        {
            if ( datalen != sizeof(wxANIHeader) )
                return false;

            if (m_nFrames > 0)
                return false;       // already parsed an ani header?

            struct wxANIHeader header;
            if (!stream.Read(&header, sizeof(wxANIHeader)))
                return false;
            header.AdjustEndianness();

            // we should have a global frame size
            m_szAnimation = wxSize(header.cx, header.cy);

            // save interesting info from the header
            m_nFrames = header.cSteps;   // NB: not cFrames!!
            if ( m_nFrames == 0 )
                return false;

            globaldelay = header.JifRate * 1000 / 60;

            m_images.reserve(header.cFrames);
            m_info.resize(m_nFrames);
        }
        else if ( FCC1 == rate32 )
        {
            // did we already process the anih32 chunk?
            if (m_nFrames == 0)
                return false;       // rate chunks should always be placed after anih chunk

            wxASSERT(m_info.size() == m_nFrames);
            for (unsigned int i=0; i<m_nFrames; i++)
            {
                if (!stream.Read(&FCC2, 4))
                    return false;
                m_info[i].m_delay = wxINT32_SWAP_ON_BE(FCC2) * 1000 / 60;
            }
        }
        else if ( FCC1 == seq32 )
        {
            // did we already process the anih32 chunk?
            if (m_nFrames == 0)
                return false;       // seq chunks should always be placed after anih chunk

            wxASSERT(m_info.size() == m_nFrames);
            for (unsigned int i=0; i<m_nFrames; i++)
            {
                if (!stream.Read(&FCC2, 4))
                    return false;
                m_info[i].m_imageIndex = wxINT32_SWAP_ON_BE(FCC2);
            }
        }
        else if ( FCC1 == ico32 )
        {
            // use DoLoadFile() and not LoadFile()!
            wxImage image;
            if (!sm_handler.DoLoadFile(&image, stream, false /* verbose */, -1))
                return false;

            image.SetType(wxBITMAP_TYPE_ANI);
            m_images.push_back(image);
        }
        else
        {
            if ( stream.SeekI(stream.TellI() + datalen) == wxInvalidOffset )
                return false;
        }

        // try to read next data chunk:
        if ( !stream.Read(&FCC1, 4) && !stream.Eof())
        {
            // we didn't reach the EOF! An other kind of error has occurred...
            return false;
        }
        //else: proceed with the parsing of the next header block or
        //      exiting this loop (if stream.Eof() == true)
    }

    if (m_nFrames==0)
        return false;

    // Without any loaded icon, m_images[0] below and the public accessors
    // would index into an empty vector.
    if (m_images.empty())
        return false;

    if (m_nFrames==m_images.size())
    {
        // if no SEQ chunk is available, display the frames in the order
        // they were loaded
        for (unsigned int i=0; i<m_nFrames; i++)
            if (m_info[i].m_imageIndex == -1)
                m_info[i].m_imageIndex = i;
    }

    // SEQ chunk indices come straight from the input, so reject the file if
    // any of them would index m_images out of range.
    for (unsigned int i=0; i<m_nFrames; i++)
    {
        if (m_info[i].m_imageIndex < 0 ||
            static_cast<size_t>(m_info[i].m_imageIndex) >= m_images.size())
            return false;
    }

    // if some frame has an invalid delay, use the global delay given in the
    // ANI header
    for (unsigned int i=0; i<m_nFrames; i++)
        if (m_info[i].m_delay == 0)
            m_info[i].m_delay = globaldelay;

    // if the header did not contain a valid frame size, try to grab
    // it from the size of the first frame (all frames are of the same size)
    if (m_szAnimation.GetWidth() == 0 ||
        m_szAnimation.GetHeight() == 0)
        m_szAnimation = wxSize(m_images[0].GetWidth(), m_images[0].GetHeight());

    return m_szAnimation != wxDefaultSize;
}

#endif // wxUSE_STREAMS && wxUSE_ICO_CUR
