/////////////////////////////////////////////////////////////////////////////
// Name:        src/html/m_image.cpp
// Purpose:     wxHtml module for displaying images
// Author:      Vaclav Slavik
// Copyright:   (c) 1999 Vaclav Slavik, Joel Lucsy
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#include "wx/wxprec.h"

#ifdef __BORLANDC__
    #pragma hdrstop
#endif

#if wxUSE_HTML && wxUSE_STREAMS

#ifndef WX_PRECOMP
    #include "wx/dynarray.h"
    #include "wx/dc.h"
    #include "wx/scrolwin.h"
    #include "wx/timer.h"
    #include "wx/dcmemory.h"
    #include "wx/log.h"
    #include "wx/math.h"
    #include "wx/image.h"
    #include "wx/wxcrtvararg.h"
#endif

#include "wx/html/forcelnk.h"
#include "wx/html/m_templ.h"
#include "wx/html/htmlwin.h"

#include "wx/gifdecod.h"
#include "wx/artprov.h"

#include <float.h>

FORCE_LINK_ME(m_image)




WX_DECLARE_OBJARRAY(int, CoordArray);
#include "wx/arrimpl.cpp" // this is a magic incantation which must be done!
WX_DEFINE_OBJARRAY(CoordArray)


// ---------------------------------------------------------------------------
// wxHtmlImageMapAreaCell
//                  0-width, 0-height cell that represents single area in
//                  imagemap (it's GetLink is called from wxHtmlImageCell's)
// ---------------------------------------------------------------------------

class wxHtmlImageMapAreaCell : public wxHtmlCell
{
    public:
        enum celltype { CIRCLE, RECT, POLY };
    protected:
        CoordArray coords;
        celltype type;
        int radius;
    public:
        wxHtmlImageMapAreaCell( celltype t, wxString &coords, double pixel_scale = 1.0);
        virtual wxHtmlLinkInfo *GetLink( int x = 0, int y = 0 ) const;
        void Draw(wxDC& WXUNUSED(dc),
                  int WXUNUSED(x), int WXUNUSED(y),
                  int WXUNUSED(view_y1), int WXUNUSED(view_y2),
                  wxHtmlRenderingInfo& WXUNUSED(info)) {}


    wxDECLARE_NO_COPY_CLASS(wxHtmlImageMapAreaCell);
};





wxHtmlImageMapAreaCell::wxHtmlImageMapAreaCell( wxHtmlImageMapAreaCell::celltype t, wxString &incoords, double pixel_scale )
{
    int i;
    wxString x = incoords, y;

    type = t;
    while ((i = x.Find( ',' )) != wxNOT_FOUND)
    {
        coords.Add( (int)(pixel_scale * (double)wxAtoi( x.Left( i ).c_str())) );
        x = x.Mid( i + 1 );
    }
    coords.Add( (int)(pixel_scale * (double)wxAtoi( x.c_str())) );
}

wxHtmlLinkInfo *wxHtmlImageMapAreaCell::GetLink( int x, int y ) const
{
    switch (type)
    {
        case RECT:
            if ( coords.GetCount() == 4 )
            {
                int l, t, r, b;

                l = coords[ 0 ];
                t = coords[ 1 ];
                r = coords[ 2 ];
                b = coords[ 3 ];
                if (x >= l && x <= r && y >= t && y <= b)
                {
                    return m_Link;
                }
            }
            break;
        case CIRCLE:
            if ( coords.GetCount() == 3 )
            {
                int l, t, r;
                double  d;

                l = coords[ 0 ];
                t = coords[ 1 ];
                r = coords[ 2 ];
                d = sqrt( (double) (((x - l) * (x - l)) + ((y - t) * (y - t))) );
                if (d < (double)r)
                {
                    return m_Link;
                }
            }
            break;
        case POLY:
             if (coords.GetCount() >= 6)
             {
                 int intersects = 0;
                 int wherex = x;
                 int wherey = y;
                 int totalv = coords.GetCount() / 2;
                 int totalc = totalv * 2;
                 int xval = coords[totalc - 2];
                 int yval = coords[totalc - 1];
                 int end = totalc;
                 int pointer = 1;

                 if ((yval >= wherey) != (coords[pointer] >= wherey))
                 {
                     if ((xval >= wherex) == (coords[0] >= wherex))
                     {
                         intersects += (xval >= wherex) ? 1 : 0;
                     }
                     else
                     {
                         intersects += ((xval - (yval - wherey) *
                                         (coords[0] - xval) /
                                         (coords[pointer] - yval)) >= wherex) ? 1 : 0;
                     }
                 }

                 while (pointer < end)
                 {
                     yval = coords[pointer];
                     pointer += 2;
                     if (yval >= wherey)
                     {
                         while ((pointer < end) && (coords[pointer] >= wherey))
                         {
                             pointer += 2;
                         }
                         if (pointer >= end)
                         {
                             break;
                         }
                         if ((coords[pointer - 3] >= wherex) ==
                                 (coords[pointer - 1] >= wherex)) {
                             intersects += (coords[pointer - 3] >= wherex) ? 1 : 0;
                         }
                         else
                         {
                             intersects +=
                                 ((coords[pointer - 3] - (coords[pointer - 2] - wherey) *
                                   (coords[pointer - 1] - coords[pointer - 3]) /
                                   (coords[pointer] - coords[pointer - 2])) >= wherex) ? 1 : 0;
                         }
                     }
                     else
                     {
                         while ((pointer < end) && (coords[pointer] < wherey))
                         {
                             pointer += 2;
                         }
                         if (pointer >= end)
                         {
                             break;
                         }
                         if ((coords[pointer - 3] >= wherex) ==
                                 (coords[pointer - 1] >= wherex))
                         {
                             intersects += (coords[pointer - 3] >= wherex) ? 1 : 0;
                         }
                         else
                         {
                             intersects +=
                                 ((coords[pointer - 3] - (coords[pointer - 2] - wherey) *
                                   (coords[pointer - 1] - coords[pointer - 3]) /
                                   (coords[pointer] - coords[pointer - 2])) >= wherex) ? 1 : 0;
                         }
                     }
                 }
                 if ((intersects & 1) != 0)
                 {
                     return m_Link;
                 }
            }
            break;
    }

    if (m_Next)
    {
        wxHtmlImageMapAreaCell  *a = (wxHtmlImageMapAreaCell*)m_Next;
        return a->GetLink( x, y );
    }
    return NULL;
}








//--------------------------------------------------------------------------------
// wxHtmlImageMapCell
//                  0-width, 0-height cell that represents map from imagemaps
//                  it is always placed before wxHtmlImageMapAreaCells
//                  It responds to Find(wxHTML_COND_ISIMAGEMAP)
//--------------------------------------------------------------------------------


class wxHtmlImageMapCell : public wxHtmlCell
{
    public:
        wxHtmlImageMapCell( wxString &name );
    protected:
        wxString m_Name;
    public:
        virtual wxHtmlLinkInfo *GetLink( int x = 0, int y = 0 ) const;
        virtual const wxHtmlCell *Find( int cond, const void *param ) const;
        void Draw(wxDC& WXUNUSED(dc),
                  int WXUNUSED(x), int WXUNUSED(y),
                  int WXUNUSED(view_y1), int WXUNUSED(view_y2),
                  wxHtmlRenderingInfo& WXUNUSED(info)) {}

    wxDECLARE_NO_COPY_CLASS(wxHtmlImageMapCell);
};


wxHtmlImageMapCell::wxHtmlImageMapCell( wxString &name )
{
    m_Name = name ;
}

wxHtmlLinkInfo *wxHtmlImageMapCell::GetLink( int x, int y ) const
{
    wxHtmlImageMapAreaCell  *a = (wxHtmlImageMapAreaCell*)m_Next;
    if (a)
        return a->GetLink( x, y );
    return wxHtmlCell::GetLink( x, y );
}

const wxHtmlCell *wxHtmlImageMapCell::Find( int cond, const void *param ) const
{
    if (cond == wxHTML_COND_ISIMAGEMAP)
    {
        if (m_Name == *((wxString*)(param)))
            return this;
    }
    return wxHtmlCell::Find(cond, param);
}





//--------------------------------------------------------------------------------
// wxHtmlImageCell
//                  Image/bitmap
//--------------------------------------------------------------------------------

class wxHtmlImageCell : public wxHtmlCell
{
public:
    wxHtmlImageCell(wxHtmlWindowInterface *windowIface,
                    wxFSFile *input,
                    int w = wxDefaultCoord, bool wpercent = false,
                    int h = wxDefaultCoord, bool hpresent = false,
                    double scale = 1.0, int align = wxHTML_ALIGN_BOTTOM,
                    const wxString& mapname = wxEmptyString);
    virtual ~wxHtmlImageCell();
    void Draw(wxDC& dc, int x, int y, int view_y1, int view_y2,
              wxHtmlRenderingInfo& info);
    virtual wxHtmlLinkInfo *GetLink(int x = 0, int y = 0) const;

    void SetImage(const wxImage& img);

    // If "alt" text is set, it will be used when converting this cell to text.
    void SetAlt(const wxString& alt);
    virtual wxString ConvertToText(wxHtmlSelection *sel) const;

#if wxUSE_GIF && wxUSE_TIMER
    void AdvanceAnimation(wxTimer *timer);
    virtual void Layout(int w);
#endif

private:
    wxBitmap           *m_bitmap;
    int                 m_align;
    int                 m_bmpW, m_bmpH;
    bool                m_bmpWpercent:1;
    bool                m_bmpHpresent:1;
    bool                m_showFrame:1;
    wxHtmlWindowInterface *m_windowIface;
#if wxUSE_GIF && wxUSE_TIMER
    wxGIFDecoder       *m_gifDecoder;
    wxTimer            *m_gifTimer;
    int                 m_physX, m_physY;
    size_t              m_nCurrFrame;
#endif
    double              m_scale;
    wxHtmlImageMapCell *m_imageMap;
    wxString            m_mapName;
    wxString            m_alt;

    wxDECLARE_NO_COPY_CLASS(wxHtmlImageCell);
};

#if wxUSE_GIF && wxUSE_TIMER
class wxGIFTimer : public wxTimer
{
    public:
        wxGIFTimer(wxHtmlImageCell *cell) : m_cell(cell) {}
        virtual void Notify()
        {
            m_cell->AdvanceAnimation(this);
        }

    private:
        wxHtmlImageCell *m_cell;

    wxDECLARE_NO_COPY_CLASS(wxGIFTimer);
};
#endif


//----------------------------------------------------------------------------
// wxHtmlImageCell
//----------------------------------------------------------------------------


wxHtmlImageCell::wxHtmlImageCell(wxHtmlWindowInterface *windowIface,
                                 wxFSFile *input,
                                 int w, bool wpercent, int h, bool hpresent, double scale, int align,
                                 const wxString& mapname) : wxHtmlCell()
{
    m_windowIface = windowIface;
    m_scale = scale;
    m_showFrame = false;
    m_bitmap = NULL;
    m_bmpW   = w;
    m_bmpH   = h;
    m_align  = align;
    m_bmpWpercent = wpercent;
    m_bmpHpresent = hpresent;
    m_imageMap = NULL;
    m_mapName = mapname;
    SetCanLiveOnPagebreak(false);
#if wxUSE_GIF && wxUSE_TIMER
    m_gifDecoder = NULL;
    m_gifTimer = NULL;
    m_physX = m_physY = wxDefaultCoord;
    m_nCurrFrame = 0;
#endif

    if ( m_bmpW && m_bmpH )
    {
        if ( input )
        {
            wxInputStream *s = input->GetStream();

            if ( s )
            {
#if wxUSE_GIF && wxUSE_TIMER
                bool readImg = true;
                if ( m_windowIface &&
                     (input->GetLocation().Matches(wxT("*.gif")) ||
                      input->GetLocation().Matches(wxT("*.GIF"))) )
                {
                    m_gifDecoder = new wxGIFDecoder();
                    if ( m_gifDecoder->LoadGIF(*s) == wxGIF_OK )
                    {
                        wxImage img;
                        if ( m_gifDecoder->ConvertToImage(0, &img) )
                            SetImage(img);

                        readImg = false;

                        if ( m_gifDecoder->IsAnimation() )
                        {
                            m_gifTimer = new wxGIFTimer(this);
                            long delay = m_gifDecoder->GetDelay(0);
                            if ( delay == 0 )
                                delay = 1;
                            m_gifTimer->Start(delay, true);
                        }
                        else
                        {
                            wxDELETE(m_gifDecoder);
                        }
                    }
                    else
                    {
                        wxDELETE(m_gifDecoder);
                    }
                }

                if ( readImg )
#endif // wxUSE_GIF && wxUSE_TIMER
                {
                    wxImage image(*s, wxBITMAP_TYPE_ANY);
                    if ( image.IsOk() )
                        SetImage(image);
                }
            }
        }
        else // input==NULL, use "broken image" bitmap
        {
            if ( m_bmpW == wxDefaultCoord && m_bmpH == wxDefaultCoord )
            {
                m_bmpW = 29;
                m_bmpH = 31;
            }
            else
            {
                m_showFrame = true;
                if ( m_bmpW == wxDefaultCoord ) m_bmpW = 31;
                if ( m_bmpH == wxDefaultCoord ) m_bmpH = 33;
            }
            m_bitmap =
                new wxBitmap(wxArtProvider::GetBitmap(wxART_MISSING_IMAGE));
        }
    }
    //else: ignore the 0-sized images used sometimes on the Web pages

 }

void wxHtmlImageCell::SetImage(const wxImage& img)
{
#if !defined(__WXMSW__) || wxUSE_WXDIB
    if ( img.IsOk() )
    {
        delete m_bitmap;

        int ww, hh;
        ww = img.GetWidth();
        hh = img.GetHeight();

        if ( m_bmpW == wxDefaultCoord)
            m_bmpW = ww;
        if ( m_bmpH == wxDefaultCoord)
            m_bmpH = hh;

        // Only scale the bitmap at the rendering stage,
        // so we don't lose quality twice
/*
        if ((m_bmpW != ww) || (m_bmpH != hh))
        {
            wxImage img2 = img.Scale(m_bmpW, m_bmpH);
            m_bitmap = new wxBitmap(img2);
        }
        else
*/
            m_bitmap = new wxBitmap(img);
    }
#endif
}

void wxHtmlImageCell::SetAlt(const wxString& alt)
{
    m_alt = alt;
}

wxString wxHtmlImageCell::ConvertToText(wxHtmlSelection* WXUNUSED(sel)) const
{
    return m_alt;
}

#if wxUSE_GIF && wxUSE_TIMER
void wxHtmlImageCell::AdvanceAnimation(wxTimer *timer)
{
    wxImage img;

    // advance current frame
    m_nCurrFrame++;
    if (m_nCurrFrame == m_gifDecoder->GetFrameCount())
        m_nCurrFrame = 0;

    if ( m_physX == wxDefaultCoord )
    {
        m_physX = m_physY = 0;
        for (wxHtmlCell *cell = this; cell; cell = cell->GetParent())
        {
            m_physX += cell->GetPosX();
            m_physY += cell->GetPosY();
        }
    }

    wxWindow *win = m_windowIface->GetHTMLWindow();
    wxPoint pos =
        m_windowIface->HTMLCoordsToWindow(this, wxPoint(m_physX, m_physY));
    wxRect rect(pos, wxSize(m_Width, m_Height));

    if ( win->GetClientRect().Intersects(rect) &&
         m_gifDecoder->ConvertToImage(m_nCurrFrame, &img) )
    {
#if !defined(__WXMSW__) || wxUSE_WXDIB
        if ( m_gifDecoder->GetFrameSize(m_nCurrFrame) != wxSize(m_Width, m_Height) ||
             m_gifDecoder->GetFramePosition(m_nCurrFrame) != wxPoint(0, 0) )
        {
            wxBitmap bmp(img);
            wxMemoryDC dc;
            dc.SelectObject(*m_bitmap);
            dc.DrawBitmap(bmp, m_gifDecoder->GetFramePosition(m_nCurrFrame),
                          true /* use mask */);
        }
        else
#endif
            SetImage(img);
        win->Refresh(img.HasMask(), &rect);
    }

    long delay = m_gifDecoder->GetDelay(m_nCurrFrame);
    if ( delay == 0 )
        delay = 1;
    timer->Start(delay, true);
}

void wxHtmlImageCell::Layout(int w)
{
    if (m_bmpWpercent)
    {

        m_Width = w*m_bmpW/100;

        if (!m_bmpHpresent && m_bitmap != NULL)
            m_Height = m_bitmap->GetHeight()*m_Width/m_bitmap->GetWidth();
        else
            m_Height = static_cast<int>(m_scale*m_bmpH);
    } else
    {
        m_Width  = static_cast<int>(m_scale*m_bmpW);
        m_Height = static_cast<int>(m_scale*m_bmpH);
    }

    switch (m_align)
    {
        case wxHTML_ALIGN_TOP :
            m_Descent = m_Height;
            break;
        case wxHTML_ALIGN_CENTER :
            m_Descent = m_Height / 2;
            break;
        case wxHTML_ALIGN_BOTTOM :
        default :
            m_Descent = 0;
            break;
    }

    wxHtmlCell::Layout(w);
    m_physX = m_physY = wxDefaultCoord;
}

#endif

wxHtmlImageCell::~wxHtmlImageCell()
{
    delete m_bitmap;
#if wxUSE_GIF && wxUSE_TIMER
    delete m_gifTimer;
    delete m_gifDecoder;
#endif
}


void wxHtmlImageCell::Draw(wxDC& dc, int x, int y,
                           int WXUNUSED(view_y1), int WXUNUSED(view_y2),
                           wxHtmlRenderingInfo& WXUNUSED(info))
{
    if ( m_showFrame )
    {
        dc.SetBrush(*wxTRANSPARENT_BRUSH);
        dc.SetPen(*wxBLACK_PEN);
        dc.DrawRectangle(x + m_PosX, y + m_PosY, m_Width, m_Height);
        x++, y++;
    }
    if ( m_bitmap )
    {
        // We add in the scaling from the desired bitmap width
        // and height, so we only do the scaling once.
        double imageScaleX = 1.0;
        double imageScaleY = 1.0;
        if (m_Width != m_bitmap->GetWidth())
            imageScaleX = (double) m_Width / (double) m_bitmap->GetWidth();
        if (m_Height != m_bitmap->GetHeight())
            imageScaleY = (double) m_Height / (double) m_bitmap->GetHeight();

        double us_x, us_y;
        dc.GetUserScale(&us_x, &us_y);
        dc.SetUserScale(us_x * imageScaleX, us_y * imageScaleY);

        dc.DrawBitmap(*m_bitmap, (int) ((x + m_PosX) / (imageScaleX)),
                                 (int) ((y + m_PosY) / (imageScaleY)), true);
        dc.SetUserScale(us_x, us_y);
    }
}

wxHtmlLinkInfo *wxHtmlImageCell::GetLink( int x, int y ) const
{
    if (m_mapName.empty())
        return wxHtmlCell::GetLink( x, y );
    if (!m_imageMap)
    {
        wxHtmlContainerCell *p, *op;
        op = p = GetParent();
        while (p)
        {
            op = p;
            p = p->GetParent();
        }
        p = op;
        wxHtmlCell *cell = (wxHtmlCell*)p->Find(wxHTML_COND_ISIMAGEMAP,
                                                (const void*)(&m_mapName));
        if (!cell)
        {
            ((wxString&)m_mapName).Clear();
            return wxHtmlCell::GetLink( x, y );
        }
        {   // dirty hack, ask Joel why he fills m_ImageMap in this place
            // THE problem is that we're in const method and we can't modify m_ImageMap
            wxHtmlImageMapCell **cx = (wxHtmlImageMapCell**)(&m_imageMap);
            *cx = (wxHtmlImageMapCell*)cell;
        }
    }
    return m_imageMap->GetLink(x, y);
}



//--------------------------------------------------------------------------------
// tag handler
//--------------------------------------------------------------------------------

TAG_HANDLER_BEGIN(IMG, "IMG,MAP,AREA")
    TAG_HANDLER_CONSTR(IMG) { }

    TAG_HANDLER_PROC(tag)
    {
        if (tag.GetName() == wxT("IMG"))
        {
            wxString tmp;
            if (tag.GetParamAsString(wxT("SRC"), &tmp))
            {
                int w = wxDefaultCoord, h = wxDefaultCoord;
                bool wpercent = false;
                bool hpresent = false;
                int al;
                wxFSFile *str;
                wxString mn;

                str = m_WParser->OpenURL(wxHTML_URL_IMAGE, tmp);

                if (tag.GetParamAsIntOrPercent(wxT("WIDTH"), &w, wpercent))
                {
                    if (wpercent)
                    {
                        if (w < 0)
                            w = 0;
                        else if (w > 100)
                            w = 100;
                    }
                }

                if (tag.GetParamAsInt(wxT("HEIGHT"), &h))
                {
                    hpresent = true;
                }

                al = wxHTML_ALIGN_BOTTOM;
                wxString alstr;
                if (tag.GetParamAsString(wxT("ALIGN"), &alstr))
                {
                    alstr.MakeUpper();  // for the case alignment was in ".."
                    if (alstr == wxT("TEXTTOP"))
                        al = wxHTML_ALIGN_TOP;
                    else if ((alstr == wxT("CENTER")) || (alstr == wxT("ABSCENTER")))
                        al = wxHTML_ALIGN_CENTER;
                }
                if (tag.GetParamAsString(wxT("USEMAP"), &mn))
                {
                    if ( !mn.empty() && *mn.begin() == '#' )
                    {
                        mn = mn.Mid( 1 );
                    }
                }
                wxHtmlImageCell *cel = new wxHtmlImageCell(
                                          m_WParser->GetWindowInterface(),
                                          str, w, wpercent, h, hpresent,
                                          m_WParser->GetPixelScale(),
                                          al, mn);
                m_WParser->ApplyStateToCell(cel);
                m_WParser->StopCollapsingSpaces();
                cel->SetId(tag.GetParam(wxT("id"))); // may be empty
                cel->SetAlt(tag.GetParam(wxT("alt")));
                m_WParser->GetContainer()->InsertCell(cel);
                if (str)
                    delete str;
            }
        }
        if (tag.GetName() == wxT("MAP"))
        {
            m_WParser->CloseContainer();
            m_WParser->OpenContainer();
            wxString tmp;
            if (tag.GetParamAsString(wxT("NAME"), &tmp))
            {
                wxHtmlImageMapCell *cel = new wxHtmlImageMapCell( tmp );
                m_WParser->GetContainer()->InsertCell( cel );
            }
            ParseInner( tag );
            m_WParser->CloseContainer();
            m_WParser->OpenContainer();
        }
        if (tag.GetName() == wxT("AREA"))
        {
            wxString tmp;
            if (tag.GetParamAsString(wxT("SHAPE"), &tmp))
            {
                wxString coords = tag.GetParam(wxT("COORDS"));
                tmp.MakeUpper();
                wxHtmlImageMapAreaCell *cel = NULL;
                if (tmp == wxT("POLY"))
                {
                    cel = new wxHtmlImageMapAreaCell( wxHtmlImageMapAreaCell::POLY, coords, m_WParser->GetPixelScale() );
                }
                else if (tmp == wxT("CIRCLE"))
                {
                    cel = new wxHtmlImageMapAreaCell( wxHtmlImageMapAreaCell::CIRCLE, coords, m_WParser->GetPixelScale() );
                }
                else if (tmp == wxT("RECT"))
                {
                    cel = new wxHtmlImageMapAreaCell( wxHtmlImageMapAreaCell::RECT, coords, m_WParser->GetPixelScale() );
                }
                wxString href;
                if (cel != NULL && tag.GetParamAsString(wxT("HREF"), &href))
                    cel->SetLink(wxHtmlLinkInfo(href, tag.GetParam(wxT("TARGET"))));
                if (cel != NULL)
                    m_WParser->GetContainer()->InsertCell( cel );
            }
        }

        return false;
    }

TAG_HANDLER_END(IMG)



TAGS_MODULE_BEGIN(Image)

    TAGS_MODULE_ADD(IMG)

TAGS_MODULE_END(Image)


#endif
