/////////////////////////////////////////////////////////////////////////////
// Name:        src/qt/region.cpp
// Author:      Peter Most, Javier Torres
// Copyright:   (c) Peter Most, Javier Torres
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

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

#include "wx/region.h"
#include "wx/bitmap.h"
#include "wx/scopedarray.h"
#include "wx/qt/private/converter.h"
#include "wx/qt/private/utils.h"

#include <QtGui/QRegion>
#include <QtGui/QBitmap>
#include <QtGui/QPainter>

class wxRegionRefData: public wxGDIRefData
{
public:
    wxRegionRefData()
    {
    }

    wxRegionRefData( QRect r ) : m_qtRegion( r )
    {
    }

    wxRegionRefData( QBitmap b ) : m_qtRegion ( b )
    {
    }

    wxRegionRefData( QPolygon p, Qt::FillRule fr ) : m_qtRegion( p, fr )
    {
    }

    wxRegionRefData( const wxRegionRefData& data )
        : wxGDIRefData()
        , m_qtRegion(data.m_qtRegion)
    {
    }

    bool operator == (const wxRegionRefData& data) const
    {
        return m_qtRegion == data.m_qtRegion;
    }

    QRegion m_qtRegion;
};

#define M_REGIONDATA ((wxRegionRefData *)m_refData)->m_qtRegion

wxIMPLEMENT_DYNAMIC_CLASS(wxRegion,wxGDIObject);

wxRegion::wxRegion()
{
    m_refData = NULL;
}

wxRegion::wxRegion(wxCoord x, wxCoord y, wxCoord w, wxCoord h)
{
    m_refData = new wxRegionRefData( QRect( x, y, w, h ) );
}

wxRegion::wxRegion(const wxPoint& topLeft, const wxPoint& bottomRight)
{
    m_refData = new wxRegionRefData( QRect( wxQtConvertPoint( topLeft ),
                                           wxQtConvertPoint( bottomRight ) ) );
}

wxRegion::wxRegion(const wxRect& rect)
{
    m_refData = new wxRegionRefData( wxQtConvertRect( rect ) );
}

wxRegion::wxRegion(size_t n, const wxPoint *points, wxPolygonFillMode fillStyle)
{
    QVector< QPoint > qtPoints;
    for ( uint i = 0; i < n; i++)
    {
        qtPoints << wxQtConvertPoint( points[i] );
    }
    QPolygon p( qtPoints );

    Qt::FillRule fillingRule = fillStyle == wxODDEVEN_RULE ? Qt::OddEvenFill : Qt::WindingFill;
    m_refData = new wxRegionRefData( p, fillingRule );
}

wxRegion::wxRegion(const wxBitmap& bmp)
{
    if ( bmp.GetMask() != NULL )
        m_refData = new wxRegionRefData( *bmp.GetMask()->GetHandle() );
    else
        m_refData = new wxRegionRefData( QRect( 0, 0, bmp.GetWidth(), bmp.GetHeight() ) );
}

wxRegion::wxRegion(const wxBitmap& bmp, const wxColour& transp, int tolerance)
{
    if ( !bmp.GetHandle() ) {
        m_refData = new wxRegionRefData();
        return;
    }

    if ( tolerance == 0 ) {
        m_refData = new wxRegionRefData(bmp.GetHandle()->createMaskFromColor(transp.GetQColor()));
        return;
    }

    wxScopedArray<unsigned char> raw(bmp.GetWidth()*bmp.GetHeight());
    memset(raw.get(), 0, bmp.GetWidth()*bmp.GetHeight());

    QImage img(bmp.GetHandle()->toImage());
    const int r = transp.Red(), g = transp.Green(), b = transp.Blue();
    for ( int y = 0; y < img.height(); y++ )
    {
        for ( int x = 0; x < img.width(); x++ )
        {
            const QColor c = img.pixel(x, y);
            if ( abs(c.red()   - r ) > tolerance ||
               abs(c.green() - g) > tolerance ||
               abs(c.blue()  - b) > tolerance) {
                    const int ind = y*img.width()+x;
                    raw[ind>>3] |= 1<<(ind&7);
            }
        }
    }

    m_refData = new wxRegionRefData(QBitmap::fromData(bmp.GetHandle()->size(), raw.get()));
}

bool wxRegion::IsEmpty() const
{
    if ( IsNull() )
        return true;

    wxCHECK_MSG(IsOk(), true, "Invalid region" );

    return M_REGIONDATA.isEmpty();
}

void wxRegion::Clear()
{
    if ( IsNull() )
        return;

    wxCHECK_RET(IsOk(), "Invalid region" );

    AllocExclusive();
    M_REGIONDATA = QRegion();
}

void wxRegion::QtSetRegion(QRegion region)
{
    AllocExclusive();
    M_REGIONDATA = region;
}

wxGDIRefData *wxRegion::CreateGDIRefData() const
{
    return new wxRegionRefData;
}

wxGDIRefData *wxRegion::CloneGDIRefData(const wxGDIRefData *data) const
{
    return new wxRegionRefData(*(wxRegionRefData *)data);
}

bool wxRegion::DoIsEqual(const wxRegion& region) const
{
    wxCHECK_MSG( IsOk(), false, "Invalid region" );
    wxCHECK_MSG( region.IsOk(), false, "Invalid parameter region" );

    return M_REGIONDATA == region.GetHandle();
}

bool wxRegion::DoGetBox(wxCoord& x, wxCoord& y, wxCoord& w, wxCoord& h) const
{
    if ( m_refData == NULL )
    {
        x =
        y =
        w =
        h = 0;
        return false;
    }

    wxCHECK_MSG( IsOk(), false, "Invalid region" );

    const QRect bounding = M_REGIONDATA.boundingRect();
    x = bounding.x();
    y = bounding.y();
    w = bounding.width();
    h = bounding.height();

    return true;
}

wxRegionContain wxRegion::DoContainsPoint(wxCoord x, wxCoord y) const
{
    wxCHECK_MSG( IsOk(), wxOutRegion, "Invalid region" );

    return M_REGIONDATA.contains( QPoint( x, y ) ) ? wxInRegion : wxOutRegion;
}

wxRegionContain wxRegion::DoContainsRect(const wxRect& rect) const
{
    wxCHECK_MSG( IsOk(), wxOutRegion, "Invalid region" );

    return M_REGIONDATA.contains( wxQtConvertRect( rect ) ) ? wxInRegion : wxOutRegion;
}

bool wxRegion::DoOffset(wxCoord x, wxCoord y)
{
    wxCHECK_MSG( IsOk(), false, "Invalid region" );

    M_REGIONDATA.translate( x, y );
    return true;
}

// combine another region with this one
bool wxRegion::DoCombine(const wxRegion& region, wxRegionOp op)
{
    // we can't use the API functions if we don't have a valid region
    if ( !m_refData )
    {
        // combining with an empty/invalid region works differently depending
        // on the operation
        switch ( op )
        {
            case wxRGN_COPY:
            case wxRGN_OR:
            case wxRGN_XOR:
                *this = region;
                break;

            default:
                wxFAIL_MSG(wxT("unknown region operation"));
                wxFALLTHROUGH;

            case wxRGN_AND:
            case wxRGN_DIFF:
                // leave empty/invalid
                return false;
        }
    }
    else // we have a valid region
    {
        AllocExclusive();

        switch ( op )
        {
        case wxRGN_AND:
            M_REGIONDATA = M_REGIONDATA.intersected(region.GetHandle());
            break;

        case wxRGN_OR:
            M_REGIONDATA = M_REGIONDATA.united(region.GetHandle());
            break;

        case wxRGN_XOR:
            M_REGIONDATA = M_REGIONDATA.xored(region.GetHandle());
            break;

        case wxRGN_DIFF:
            M_REGIONDATA = M_REGIONDATA.subtracted(region.GetHandle());
            break;

        default:
            wxFAIL_MSG(wxT("unknown region operation"));
            wxFALLTHROUGH;

        case wxRGN_COPY:
            M_REGIONDATA = QRegion(region.GetHandle());
            break;
        }
    }
    return true;
}

bool wxRegion::DoUnionWithRegion(const wxRegion& region)
{
    return DoCombine(region, wxRGN_OR);
}

bool wxRegion::DoIntersect(const wxRegion& region)
{
    return DoCombine(region, wxRGN_AND);
}

bool wxRegion::DoSubtract(const wxRegion& region)
{
    return DoCombine(region, wxRGN_DIFF);
}

bool wxRegion::DoXor(const wxRegion& region)
{
    return DoCombine(region, wxRGN_XOR);
}

bool wxRegion::DoUnionWithRect(const wxRect& rect)
{
    if ( m_refData == NULL )
    {
        m_refData = new wxRegionRefData(wxQtConvertRect(rect));
        return true;
    }

    wxCHECK_MSG( IsOk(), false, "Invalid region" );

    AllocExclusive();
    M_REGIONDATA = M_REGIONDATA.united( wxQtConvertRect( rect ) );
    return true;
}

const QRegion &wxRegion::GetHandle() const
{
    wxCHECK_MSG( IsOk(), GetHandle(), "Invalid region" );

    return M_REGIONDATA;
}

//##############################################################################

wxIMPLEMENT_DYNAMIC_CLASS(wxRegionIterator,wxObject);

wxRegionIterator::wxRegionIterator()
{
    m_qtRects = NULL;
    m_pos = 0;
}

wxRegionIterator::wxRegionIterator(const wxRegion& region)
{
    m_qtRects = new QVector< QRect >( region.GetHandle().rects() );
    m_pos = 0;
}

wxRegionIterator::wxRegionIterator(const wxRegionIterator& ri)
{
    m_qtRects = new QVector< QRect >( *ri.m_qtRects );
    m_pos = ri.m_pos;
}

wxRegionIterator::~wxRegionIterator()
{
    delete m_qtRects;
}

wxRegionIterator& wxRegionIterator::operator=(const wxRegionIterator& ri)
{
    if (this != &ri)
    {
        delete m_qtRects;
        m_qtRects = new QVector< QRect >( *ri.m_qtRects );
        m_pos = ri.m_pos;
    }
    return *this;
}

void wxRegionIterator::Reset()
{
    m_pos = 0;
}

void wxRegionIterator::Reset(const wxRegion& region)
{
    delete m_qtRects;

    m_qtRects = new QVector< QRect >( region.GetHandle().rects() );
    m_pos = 0;
}

bool wxRegionIterator::HaveRects() const
{
    wxCHECK_MSG( m_qtRects != NULL, false, "Invalid iterator" );

    return m_pos < m_qtRects->size();
}

wxRegionIterator::operator bool () const
{
    return HaveRects();
}

wxRegionIterator& wxRegionIterator::operator ++ ()
{
    m_pos++;
    return *this;
}

wxRegionIterator wxRegionIterator::operator ++ (int)
{
    wxRegionIterator copy(*this);
    ++*this;
    return copy;
}

wxCoord wxRegionIterator::GetX() const
{
    wxCHECK_MSG( m_qtRects != NULL, 0, "Invalid iterator" );
    wxCHECK_MSG( m_pos < m_qtRects->size(), 0, "Invalid position" );

    return m_qtRects->at( m_pos ).x();
}

wxCoord wxRegionIterator::GetY() const
{
    wxCHECK_MSG( m_qtRects != NULL, 0, "Invalid iterator" );
    wxCHECK_MSG( m_pos < m_qtRects->size(), 0, "Invalid position" );

    return m_qtRects->at( m_pos ).y();
}

wxCoord wxRegionIterator::GetW() const
{
    return GetWidth();
}

wxCoord wxRegionIterator::GetWidth() const
{
    wxCHECK_MSG( m_qtRects != NULL, 0, "Invalid iterator" );
    wxCHECK_MSG( m_pos < m_qtRects->size(), 0, "Invalid position" );

    return m_qtRects->at( m_pos ).width();
}

wxCoord wxRegionIterator::GetH() const
{
    return GetHeight();
}

wxCoord wxRegionIterator::GetHeight() const
{
    wxCHECK_MSG( m_qtRects != NULL, 0, "Invalid iterator" );
    wxCHECK_MSG( m_pos < m_qtRects->size(), 0, "Invalid position" );

    return m_qtRects->at( m_pos ).height();
}

wxRect wxRegionIterator::GetRect() const
{
    wxCHECK_MSG( m_qtRects != NULL, wxRect(), "Invalid iterator" );
    wxCHECK_MSG( m_pos < m_qtRects->size(), wxRect(), "Invalid position" );

    return wxQtConvertRect( m_qtRects->at( m_pos ) );
}

