/////////////////////////////////////////////////////////////////////////////
// Name:        wx/dfb/wrapdfb.h
// Purpose:     wx wrappers for DirectFB interfaces
// Author:      Vaclav Slavik
// Created:     2006-08-23
// Copyright:   (c) 2006 REA Elektronik GmbH
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////

#ifndef _WX_DFB_WRAPDFB_H_
#define _WX_DFB_WRAPDFB_H_

#include "wx/dfb/dfbptr.h"
#include "wx/gdicmn.h"
#include "wx/vidmode.h"

// "register" is removed in C++17 but used inside these headers.
#define register

#include <directfb.h>
#include <directfb_version.h>

#undef register

// DFB < 1.0 didn't have u8 type, only __u8
#if DIRECTFB_MAJOR_VERSION == 0
typedef __u8 u8;
#endif


wxDFB_DECLARE_INTERFACE(IDirectFB);
wxDFB_DECLARE_INTERFACE(IDirectFBDisplayLayer);
wxDFB_DECLARE_INTERFACE(IDirectFBFont);
wxDFB_DECLARE_INTERFACE(IDirectFBWindow);
wxDFB_DECLARE_INTERFACE(IDirectFBSurface);
wxDFB_DECLARE_INTERFACE(IDirectFBPalette);
wxDFB_DECLARE_INTERFACE(IDirectFBEventBuffer);


/**
    Checks the @a code of a DirectFB call and returns true if it was
    successful and false if it failed, logging the errors as appropriate
    (asserts for programming errors, wxLogError for runtime failures).
 */
bool wxDfbCheckReturn(DFBResult code);

//-----------------------------------------------------------------------------
// wxDfbEvent
//-----------------------------------------------------------------------------

/**
    The struct defined by this macro is a thin wrapper around DFB*Event type.
    It is needed because DFB*Event are typedefs and so we can't forward declare
    them, but we need to pass them to methods declared in public headers where
    <directfb.h> cannot be included. So this struct just holds the event value,
    it's sole purpose is that it can be forward declared.
 */
#define WXDFB_DEFINE_EVENT_WRAPPER(T)                                       \
    struct wx##T                                                            \
    {                                                                       \
        wx##T() = default;                                                  \
        wx##T(const T& event) : m_event(event) {}                           \
                                                                            \
        operator T&() { return m_event; }                                   \
        operator const T&() const { return m_event; }                       \
        T* operator&() { return &m_event; }                                 \
                                                                            \
        DFBEventClass GetClass() const { return m_event.clazz; }            \
                                                                            \
    private:                                                                \
        T m_event;                                                          \
    };

WXDFB_DEFINE_EVENT_WRAPPER(DFBEvent)
WXDFB_DEFINE_EVENT_WRAPPER(DFBWindowEvent)


//-----------------------------------------------------------------------------
// wxDfbWrapper<T>
//-----------------------------------------------------------------------------

/// Base class for wxDfbWrapper<T>
class wxDfbWrapperBase
{
public:
    /// Increases reference count of the object
    void AddRef()
    {
        m_refCnt++;
    }

    /// Decreases reference count and if it reaches zero, deletes the object
    void Release()
    {
        if ( --m_refCnt == 0 )
            delete this;
    }

    /// Returns result code of the last call
    DFBResult GetLastResult() const { return m_lastResult; }

protected:
    wxDfbWrapperBase() : m_refCnt(1), m_lastResult(DFB_OK) {}

    /// Dtor may only be called from Release()
    virtual ~wxDfbWrapperBase() = default;

    /**
        Checks the @a result of a DirectFB call and returns true if it was
        successful and false if it failed. Also stores result of the call
        so that it can be obtained by calling GetLastResult().
     */
    bool Check(DFBResult result)
    {
        m_lastResult = result;
        return wxDfbCheckReturn(result);
    }

protected:
    /// Reference count
    unsigned m_refCnt;

    /// Result of the last DirectFB call
    DFBResult m_lastResult;
};

/**
    This template is base class for friendly C++ wrapper around DirectFB
    interface T.

    The wrapper provides same API as DirectFB, with a few exceptions:
     - methods return true/false instead of error code
     - methods that return or create another interface return pointer to the
       interface (or nullptr on failure) instead of storing it in the last
       argument
     - interface arguments use wxFooPtr type instead of raw DirectFB pointer
     - methods taking flags use int type instead of an enum when the flags
       can be or-combination of enum elements (this is workaround for
       C++-unfriendly DirectFB API)
 */
template<typename T>
class wxDfbWrapper : public wxDfbWrapperBase
{
public:
    /// "Raw" DirectFB interface type
    typedef T DirectFBIface;

    /// Returns raw DirectFB pointer
    T *GetRaw() const { return m_ptr; }

protected:
    /// To be called from ctor. Takes ownership of raw object.
    void Init(T *ptr) { m_ptr = ptr; }

    /// Dtor may only be used from Release
    ~wxDfbWrapper()
    {
        if ( m_ptr )
            m_ptr->Release(m_ptr);
    }

protected:
    // pointer to DirectFB object
    T *m_ptr;
};


//-----------------------------------------------------------------------------
// wxIDirectFBFont
//-----------------------------------------------------------------------------

struct wxIDirectFBFont : public wxDfbWrapper<IDirectFBFont>
{
    wxIDirectFBFont(IDirectFBFont *s) { Init(s); }

    bool GetStringWidth(const char *text, int bytes, int *w)
        { return Check(m_ptr->GetStringWidth(m_ptr, text, bytes, w)); }

    bool GetStringExtents(const char *text, int bytes,
                          DFBRectangle *logicalRect, DFBRectangle *inkRect)
    {
        return Check(m_ptr->GetStringExtents(m_ptr, text, bytes,
                                               logicalRect, inkRect));
    }

    bool GetHeight(int *h)
        { return Check(m_ptr->GetHeight(m_ptr, h)); }

    bool GetDescender(int *descender)
        { return Check(m_ptr->GetDescender(m_ptr, descender)); }
};


//-----------------------------------------------------------------------------
// wxIDirectFBPalette
//-----------------------------------------------------------------------------

struct wxIDirectFBPalette : public wxDfbWrapper<IDirectFBPalette>
{
    wxIDirectFBPalette(IDirectFBPalette *s) { Init(s); }
};


//-----------------------------------------------------------------------------
// wxIDirectFBSurface
//-----------------------------------------------------------------------------

struct wxIDirectFBSurface : public wxDfbWrapper<IDirectFBSurface>
{
    wxIDirectFBSurface(IDirectFBSurface *s) { Init(s); }

    bool GetSize(int *w, int *h)
        { return Check(m_ptr->GetSize(m_ptr, w, h)); }

    bool GetCapabilities(DFBSurfaceCapabilities *caps)
        { return Check(m_ptr->GetCapabilities(m_ptr, caps)); }

    bool GetPixelFormat(DFBSurfacePixelFormat *caps)
        { return Check(m_ptr->GetPixelFormat(m_ptr, caps)); }

    // convenience version of GetPixelFormat, returns DSPF_UNKNOWN if fails
    DFBSurfacePixelFormat GetPixelFormat();

    bool SetClip(const DFBRegion *clip)
        { return Check(m_ptr->SetClip(m_ptr, clip)); }

    bool SetColor(u8 r, u8 g, u8 b, u8 a)
        { return Check(m_ptr->SetColor(m_ptr, r, g, b, a)); }

    bool Clear(u8 r, u8 g, u8 b, u8 a)
        { return Check(m_ptr->Clear(m_ptr, r, g, b, a)); }

    bool DrawLine(int x1, int y1, int x2, int y2)
        { return Check(m_ptr->DrawLine(m_ptr, x1, y1, x2, y2)); }

    bool DrawRectangle(int x, int y, int w, int h)
        { return Check(m_ptr->DrawRectangle(m_ptr, x, y, w, h)); }

    bool FillRectangle(int x, int y, int w, int h)
        { return Check(m_ptr->FillRectangle(m_ptr, x, y, w, h)); }

    bool SetFont(const wxIDirectFBFontPtr& font)
        { return Check(m_ptr->SetFont(m_ptr, font->GetRaw())); }

    bool DrawString(const char *text, int bytes, int x, int y, int flags)
    {
        return Check(m_ptr->DrawString(m_ptr, text, bytes, x, y,
                                         (DFBSurfaceTextFlags)flags));
    }

    /**
        Updates the front buffer from the back buffer. If @a region is not
        nullptr, only given rectangle is updated.
     */
    bool FlipToFront(const DFBRegion *region = nullptr);

    wxIDirectFBSurfacePtr GetSubSurface(const DFBRectangle *rect)
    {
        IDirectFBSurface *s;
        if ( Check(m_ptr->GetSubSurface(m_ptr, rect, &s)) )
            return new wxIDirectFBSurface(s);
        else
            return nullptr;
    }

    wxIDirectFBPalettePtr GetPalette()
    {
        IDirectFBPalette *s;
        if ( Check(m_ptr->GetPalette(m_ptr, &s)) )
            return new wxIDirectFBPalette(s);
        else
            return nullptr;
    }

    bool SetPalette(const wxIDirectFBPalettePtr& pal)
        { return Check(m_ptr->SetPalette(m_ptr, pal->GetRaw())); }

    bool SetBlittingFlags(int flags)
    {
        return Check(
            m_ptr->SetBlittingFlags(m_ptr, (DFBSurfaceBlittingFlags)flags));
    }

    bool Blit(const wxIDirectFBSurfacePtr& source,
              const DFBRectangle *source_rect,
              int x, int y)
        { return Blit(source->GetRaw(), source_rect, x, y); }

    bool Blit(IDirectFBSurface *source,
              const DFBRectangle *source_rect,
              int x, int y)
        { return Check(m_ptr->Blit(m_ptr, source, source_rect, x, y)); }

    bool StretchBlit(const wxIDirectFBSurfacePtr& source,
              const DFBRectangle *source_rect,
              const DFBRectangle *dest_rect)
    {
        return Check(m_ptr->StretchBlit(m_ptr, source->GetRaw(),
                                        source_rect, dest_rect));
    }

    /// Returns bit depth used by the surface or -1 on error
    int GetDepth();

    /**
        Creates a new surface by cloning this one. New surface will have same
        capabilities, pixel format and pixel data as the existing one.

        @see CreateCompatible
     */
    wxIDirectFBSurfacePtr Clone();

    /// Flags for CreateCompatible()
    enum CreateCompatibleFlags
    {
        /// Don't create double-buffered surface
        CreateCompatible_NoBackBuffer = 1
    };

    /**
        Creates a surface compatible with this one, i.e. surface with the same
        capabilities and pixel format, but with different and size.

        @param size  Size of the surface to create. If wxDefaultSize, use the
                     size of this surface.
        @param flags Or-combination of CreateCompatibleFlags values
     */
    wxIDirectFBSurfacePtr CreateCompatible(const wxSize& size = wxDefaultSize,
                                           int flags = 0);

    bool Lock(DFBSurfaceLockFlags flags, void **ret_ptr, int *ret_pitch)
        { return Check(m_ptr->Lock(m_ptr, flags, ret_ptr, ret_pitch)); }

    bool Unlock()
        { return Check(m_ptr->Unlock(m_ptr)); }

    /// Helper struct for safe locking & unlocking of surfaces
    struct Locked
    {
        Locked(const wxIDirectFBSurfacePtr& surface, DFBSurfaceLockFlags flags)
            : m_surface(surface)
        {
            if ( !surface->Lock(flags, &ptr, &pitch) )
                ptr = nullptr;
        }

        ~Locked()
        {
            if ( ptr )
                m_surface->Unlock();
        }

        void *ptr;
        int pitch;

    private:
        wxIDirectFBSurfacePtr m_surface;
    };


private:
    // this is private because we want user code to use FlipToFront()
    bool Flip(const DFBRegion *region, int flags);
};


//-----------------------------------------------------------------------------
// wxIDirectFBEventBuffer
//-----------------------------------------------------------------------------

struct wxIDirectFBEventBuffer : public wxDfbWrapper<IDirectFBEventBuffer>
{
    wxIDirectFBEventBuffer(IDirectFBEventBuffer *s) { Init(s); }

    bool CreateFileDescriptor(int *ret_fd)
    {
        return Check(m_ptr->CreateFileDescriptor(m_ptr, ret_fd));
    }
};


//-----------------------------------------------------------------------------
// wxIDirectFBWindow
//-----------------------------------------------------------------------------

struct wxIDirectFBWindow : public wxDfbWrapper<IDirectFBWindow>
{
    wxIDirectFBWindow(IDirectFBWindow *s) { Init(s); }

    bool GetID(DFBWindowID *id)
        { return Check(m_ptr->GetID(m_ptr, id)); }

    bool GetPosition(int *x, int *y)
        { return Check(m_ptr->GetPosition(m_ptr, x, y)); }

    bool GetSize(int *w, int *h)
        { return Check(m_ptr->GetSize(m_ptr, w, h)); }

    bool MoveTo(int x, int y)
        { return Check(m_ptr->MoveTo(m_ptr, x, y)); }

    bool Resize(int w, int h)
        { return Check(m_ptr->Resize(m_ptr, w, h)); }

    bool SetOpacity(u8 opacity)
        { return Check(m_ptr->SetOpacity(m_ptr, opacity)); }

    bool SetStackingClass(DFBWindowStackingClass klass)
        { return Check(m_ptr->SetStackingClass(m_ptr, klass)); }

    bool RaiseToTop()
        { return Check(m_ptr->RaiseToTop(m_ptr)); }

    bool LowerToBottom()
        { return Check(m_ptr->LowerToBottom(m_ptr)); }

    wxIDirectFBSurfacePtr GetSurface()
    {
        IDirectFBSurface *s;
        if ( Check(m_ptr->GetSurface(m_ptr, &s)) )
            return new wxIDirectFBSurface(s);
        else
            return nullptr;
    }

    bool AttachEventBuffer(const wxIDirectFBEventBufferPtr& buffer)
        { return Check(m_ptr->AttachEventBuffer(m_ptr, buffer->GetRaw())); }

    bool RequestFocus()
        { return Check(m_ptr->RequestFocus(m_ptr)); }

    bool Destroy()
        { return Check(m_ptr->Destroy(m_ptr)); }
};


//-----------------------------------------------------------------------------
// wxIDirectFBDisplayLayer
//-----------------------------------------------------------------------------

struct wxIDirectFBDisplayLayer : public wxDfbWrapper<IDirectFBDisplayLayer>
{
    wxIDirectFBDisplayLayer(IDirectFBDisplayLayer *s) { Init(s); }

    wxIDirectFBWindowPtr CreateWindow(const DFBWindowDescription *desc)
    {
        IDirectFBWindow *w;
        if ( Check(m_ptr->CreateWindow(m_ptr, desc, &w)) )
            return new wxIDirectFBWindow(w);
        else
            return nullptr;
    }

    bool GetConfiguration(DFBDisplayLayerConfig *config)
        { return Check(m_ptr->GetConfiguration(m_ptr, config)); }

    wxVideoMode GetVideoMode();

    bool GetCursorPosition(int *x, int *y)
        { return Check(m_ptr->GetCursorPosition(m_ptr, x, y)); }

    bool WarpCursor(int x, int y)
        { return Check(m_ptr->WarpCursor(m_ptr, x, y)); }
};


//-----------------------------------------------------------------------------
// wxIDirectFB
//-----------------------------------------------------------------------------

struct wxIDirectFB : public wxDfbWrapper<IDirectFB>
{
    /**
        Returns pointer to DirectFB singleton object, it never returns nullptr
        after wxApp was initialized. The object is cached, so calling this
        method is cheap.
     */
    static wxIDirectFBPtr Get()
    {
        if ( !ms_ptr ) CreateDirectFB();
        return ms_ptr;
    }

    bool SetVideoMode(int w, int h, int bpp)
        { return Check(m_ptr->SetVideoMode(m_ptr, w, h, bpp)); }

    wxIDirectFBSurfacePtr CreateSurface(const DFBSurfaceDescription *desc)
    {
        IDirectFBSurface *s;
        if ( Check(m_ptr->CreateSurface(m_ptr, desc, &s)) )
            return new wxIDirectFBSurface(s);
        else
            return nullptr;
    }

    wxIDirectFBEventBufferPtr CreateEventBuffer()
    {
        IDirectFBEventBuffer *b;
        if ( Check(m_ptr->CreateEventBuffer(m_ptr, &b)) )
            return new wxIDirectFBEventBuffer(b);
        else
            return nullptr;
    }

    wxIDirectFBFontPtr CreateFont(const char *filename,
                                  const DFBFontDescription *desc)
    {
        IDirectFBFont *f;
        if ( Check(m_ptr->CreateFont(m_ptr, filename, desc, &f)) )
            return new wxIDirectFBFont(f);
        else
            return nullptr;
    }

    wxIDirectFBDisplayLayerPtr
    GetDisplayLayer(DFBDisplayLayerID id = DLID_PRIMARY)
    {
        IDirectFBDisplayLayer *l;
        if ( Check(m_ptr->GetDisplayLayer(m_ptr, id, &l)) )
            return new wxIDirectFBDisplayLayer(l);
        else
            return nullptr;
    }

    /// Returns primary surface
    wxIDirectFBSurfacePtr GetPrimarySurface();

private:
    wxIDirectFB(IDirectFB *ptr) { Init(ptr); }

    // creates ms_ptr instance
    static void CreateDirectFB();

    static void CleanUp();
    friend class wxApp; // calls CleanUp

    // pointer to the singleton IDirectFB object
    static wxIDirectFBPtr ms_ptr;
};

#endif // _WX_DFB_WRAPDFB_H_
