/*
 * Copyright 2021 Nikolay Sivov for CodeWeavers
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 *
 */

#define COBJMACROS
#include "d3dx10.h"

#include "wine/debug.h"
#include "wine/heap.h"

WINE_DEFAULT_DEBUG_CHANNEL(d3dx);

#define D3DERR_INVALIDCALL 0x8876086c

struct d3dx10_sprite
{
    ID3DX10Sprite ID3DX10Sprite_iface;
    LONG refcount;

    D3DXMATRIX projection;
    ID3D10Device *device;
};

static inline struct d3dx10_sprite *impl_from_ID3DX10Sprite(ID3DX10Sprite *iface)
{
    return CONTAINING_RECORD(iface, struct d3dx10_sprite, ID3DX10Sprite_iface);
}

static HRESULT WINAPI d3dx10_sprite_QueryInterface(ID3DX10Sprite *iface, REFIID riid, void **out)
{
    TRACE("iface %p, riid %s, out %p.\n", iface, debugstr_guid(riid), out);

    if (IsEqualGUID(riid, &IID_ID3DX10Sprite)
            || IsEqualGUID(riid, &IID_IUnknown))
    {
        IUnknown_AddRef(iface);
        *out = iface;
        return S_OK;
    }

    WARN("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(riid));

    *out = NULL;
    return E_NOINTERFACE;
}

static ULONG WINAPI d3dx10_sprite_AddRef(ID3DX10Sprite *iface)
{
    struct d3dx10_sprite *sprite = impl_from_ID3DX10Sprite(iface);
    ULONG refcount = InterlockedIncrement(&sprite->refcount);

    TRACE("%p increasing refcount to %u.\n", iface, refcount);

    return refcount;
}

static ULONG WINAPI d3dx10_sprite_Release(ID3DX10Sprite *iface)
{
    struct d3dx10_sprite *sprite = impl_from_ID3DX10Sprite(iface);
    ULONG refcount = InterlockedDecrement(&sprite->refcount);

    TRACE("%p decreasing refcount to %u.\n", iface, refcount);

    if (!refcount)
    {
        ID3D10Device_Release(sprite->device);
        heap_free(sprite);
    }

    return refcount;
}

static HRESULT WINAPI d3dx10_sprite_Begin(ID3DX10Sprite *iface, UINT flags)
{
    FIXME("iface %p, flags %#x stub!\n", iface, flags);

    return E_NOTIMPL;
}

static HRESULT WINAPI d3dx10_sprite_DrawSpritesBuffered(ID3DX10Sprite *iface,
        D3DX10_SPRITE *sprites, UINT count)
{
    FIXME("iface %p, sprites %p, count %u stub!\n", iface, sprites, count);

    return E_NOTIMPL;
}

static HRESULT WINAPI d3dx10_sprite_Flush(ID3DX10Sprite *iface)
{
    FIXME("iface %p stub!\n", iface);

    return E_NOTIMPL;
}

static HRESULT WINAPI d3dx10_sprite_DrawSpritesImmediate(ID3DX10Sprite *iface,
        D3DX10_SPRITE *sprites, UINT count, UINT size, UINT flags)
{
    FIXME("iface %p, sprites %p, count %u, size %u, flags %#x stub!\n",
            iface, sprites, count, size, flags);

    return E_NOTIMPL;
}

static HRESULT WINAPI d3dx10_sprite_End(ID3DX10Sprite *iface)
{
    FIXME("iface %p stub!\n", iface);

    return E_NOTIMPL;
}

static HRESULT WINAPI d3dx10_sprite_GetViewTransform(ID3DX10Sprite *iface, D3DXMATRIX *transform)
{
    FIXME("iface %p, transform %p stub!\n", iface, transform);

    return E_NOTIMPL;
}

static HRESULT WINAPI d3dx10_sprite_SetViewTransform(ID3DX10Sprite *iface, D3DXMATRIX *transform)
{
    FIXME("iface %p, transform %p stub!\n", iface, transform);

    return E_NOTIMPL;
}

static HRESULT WINAPI d3dx10_sprite_GetProjectionTransform(ID3DX10Sprite *iface,
        D3DXMATRIX *transform)
{
    struct d3dx10_sprite *sprite = impl_from_ID3DX10Sprite(iface);

    TRACE("iface %p, transform %p.\n", iface, transform);

    if (!transform)
        return E_FAIL;

    *transform = sprite->projection;

    return S_OK;
}

static HRESULT WINAPI d3dx10_sprite_SetProjectionTransform(ID3DX10Sprite *iface, D3DXMATRIX *transform)
{
    struct d3dx10_sprite *sprite = impl_from_ID3DX10Sprite(iface);

    TRACE("iface %p, transform %p.\n", iface, transform);

    if (!transform)
        return E_FAIL;

    sprite->projection = *transform;

    return S_OK;
}

static HRESULT WINAPI d3dx10_sprite_GetDevice(ID3DX10Sprite *iface, ID3D10Device **device)
{
    struct d3dx10_sprite *sprite = impl_from_ID3DX10Sprite(iface);

    TRACE("iface %p, device %p.\n", iface, device);

    if (!device)
        return E_FAIL;

    *device = sprite->device;
    ID3D10Device_AddRef(*device);

    return S_OK;
}

static const ID3DX10SpriteVtbl d3dx10_sprite_vtbl =
{
    d3dx10_sprite_QueryInterface,
    d3dx10_sprite_AddRef,
    d3dx10_sprite_Release,
    d3dx10_sprite_Begin,
    d3dx10_sprite_DrawSpritesBuffered,
    d3dx10_sprite_Flush,
    d3dx10_sprite_DrawSpritesImmediate,
    d3dx10_sprite_End,
    d3dx10_sprite_GetViewTransform,
    d3dx10_sprite_SetViewTransform,
    d3dx10_sprite_GetProjectionTransform,
    d3dx10_sprite_SetProjectionTransform,
    d3dx10_sprite_GetDevice,
};

HRESULT WINAPI D3DX10CreateSprite(ID3D10Device *device, UINT size, ID3DX10Sprite **sprite)
{
    struct d3dx10_sprite *object;

    TRACE("device %p, size %u, sprite %p.\n", device, size, sprite);

    if (!device || !sprite)
        return D3DERR_INVALIDCALL;

    *sprite = NULL;

    if (!(object = heap_alloc_zero(sizeof(*object))))
        return E_OUTOFMEMORY;

    object->ID3DX10Sprite_iface.lpVtbl = &d3dx10_sprite_vtbl;
    object->refcount = 1;
    object->device = device;
    ID3D10Device_AddRef(device);
    object->projection._11 = 1.0f;
    object->projection._22 = 1.0f;
    object->projection._33 = 1.0f;
    object->projection._44 = 1.0f;

    *sprite = &object->ID3DX10Sprite_iface;

    return S_OK;
}
