/*
 * Copyright 2016 Henri Verbeet 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
 */

/* Hack for MinGW-w64 headers.
 *
 * We want to use WIDL C inline wrappers because some methods
 * in D3D12 interfaces return aggregate objects. Unfortunately,
 * WIDL C inline wrappers are broken when used with MinGW-w64
 * headers because FORCEINLINE expands to extern inline
 * which leads to the "multiple storage classes in declaration
 * specifiers" compiler error.
 */
#ifdef __MINGW32__
#include <_mingw.h>
# ifdef __MINGW64_VERSION_MAJOR
#  undef __forceinline
#  define __forceinline __inline__ __attribute__((__always_inline__,__gnu_inline__))
# endif
#endif

#include <vkd3d_windows.h>
#define WIDL_C_INLINE_WRAPPERS
#define COBJMACROS
#include <vkd3d_d3d12.h>
#include <inttypes.h>
#include <math.h>

#ifdef __WIN32__
#define DEMO_ASM_PUSHSECTION ".section rdata\n\t"
#define DEMO_ASM_POPSECTION ".text\n\t"
#define DEMO_ASM_OBJECT_TYPE(name)
#elif defined(__APPLE__)
#define DEMO_ASM_PUSHSECTION ".pushsection __TEXT,__const\n\t"
#define DEMO_ASM_POPSECTION ".popsection\n\t"
#define DEMO_ASM_OBJECT_TYPE(name)
#else
#define DEMO_ASM_PUSHSECTION ".pushsection .rodata\n\t"
#define DEMO_ASM_POPSECTION ".popsection\n\t"
#define DEMO_ASM_OBJECT_TYPE(name) ".type "name", STT_OBJECT\n\t"
#endif

#if (defined(__WIN32__) && defined(__i386__)) || defined(__APPLE__)
#define DEMO_ASM_NAME(name) "_"#name
#else
#define DEMO_ASM_NAME(name) #name
#endif

#define DEMO_EMBED_ASM(name, file) \
    DEMO_ASM_PUSHSECTION \
    ".global "name"\n\t" \
    DEMO_ASM_OBJECT_TYPE(name) \
    ".balign 8\n\t" \
    name":\n\t" \
    ".incbin \""file"\"\n\t" \
    name"_end:\n\t" \
    ".global "name"_size\n\t" \
    DEMO_ASM_OBJECT_TYPE(name"_size") \
    ".balign 8\n\t" \
    name"_size:\n\t" \
    ".int "name"_end - "name"\n\t" \
    DEMO_ASM_POPSECTION

#define DEMO_EMBED(name, file) \
    extern const unsigned int name##_size; \
    extern const uint8_t name[]; \
    __asm__(DEMO_EMBED_ASM(DEMO_ASM_NAME(name), file))

#if defined(__GNUC__) || defined(__clang__)
# ifdef __MINGW_PRINTF_FORMAT
#  define DEMO_PRINTF_FUNC(fmt, args) __attribute__((format(__MINGW_PRINTF_FORMAT, fmt, args)))
# else
#  define DEMO_PRINTF_FUNC(fmt, args) __attribute__((format(printf, fmt, args)))
# endif
#else
# define DEMO_PRINTF_FUNC(fmt, args)
#endif

#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))

#define DEMO_KEY_UNKNOWN        0x0000
#define DEMO_KEY_ESCAPE         0xff1b
#define DEMO_KEY_LEFT           0xff51
#define DEMO_KEY_UP             0xff52
#define DEMO_KEY_RIGHT          0xff53
#define DEMO_KEY_DOWN           0xff54
#define DEMO_KEY_KP_ADD         0xffab
#define DEMO_KEY_KP_SUBTRACT    0xffad
#define DEMO_KEY_F1             0xffbe

struct demo_vec3
{
    float x, y, z;
};

struct demo_vec4
{
    float x, y, z, w;
};

struct demo_uvec2
{
    uint32_t x, y;
};

struct demo_uvec4
{
    uint32_t x, y, z, w;
};

struct demo_matrix
{
    float m[4][4];
};

struct demo_patch
{
    uint16_t p[4][4];
};

struct demo_swapchain_desc
{
    unsigned int width;
    unsigned int height;
    unsigned int buffer_count;
    DXGI_FORMAT format;
};

typedef uint32_t demo_key;

static inline void demo_vec3_set(struct demo_vec3 *v, float x, float y, float z)
{
    v->x = x;
    v->y = y;
    v->z = z;
}

static inline void demo_vec3_subtract(struct demo_vec3 *v, const struct demo_vec3 *a, const struct demo_vec3 *b)
{
    demo_vec3_set(v, a->x - b->x, a->y - b->y, a->z - b->z);
}

static inline float demo_vec3_dot(const struct demo_vec3 *a, const struct demo_vec3 *b)
{
    return a->x * b->x + a->y * b->y + a->z * b->z;
}

static inline float demo_vec3_length(const struct demo_vec3 *v)
{
    return sqrtf(demo_vec3_dot(v, v));
}

static inline void demo_vec3_scale(struct demo_vec3 *v, const struct demo_vec3 *a, float s)
{
    demo_vec3_set(v, a->x * s, a->y * s, a->z *s);
}

static inline void demo_vec3_normalise(struct demo_vec3 *v, const struct demo_vec3 *a)
{
    demo_vec3_scale(v, a, 1.0f / demo_vec3_length(a));
}

static inline void demo_vec3_cross(struct demo_vec3 *v, const struct demo_vec3 *a, const struct demo_vec3 *b)
{
    demo_vec3_set(v, a->y * b->z - a->z * b->y, a->z * b->x - a->x * b->z, a->x * b->y - a->y * b->x);
}

static inline void demo_vec4_set(struct demo_vec4 *v, float x, float y, float z, float w)
{
    v->x = x;
    v->y = y;
    v->z = z;
    v->w = w;
}

static inline void demo_matrix_look_at_rh(struct demo_matrix *m, const struct demo_vec3 *eye,
        const struct demo_vec3 *ref, const struct demo_vec3 *up)
{
    struct demo_vec3 f, s, t, u;

    demo_vec3_subtract(&f, eye, ref);
    demo_vec3_normalise(&f, &f);
    demo_vec3_cross(&s, up, &f);
    demo_vec3_normalise(&s, &s);
    demo_vec3_cross(&u, &f, &s);
    demo_vec3_set(&t, demo_vec3_dot(&s, eye), demo_vec3_dot(&u, eye), demo_vec3_dot(&f, eye));

    *m = (struct demo_matrix)
    {{
        { s.x,  u.x,  f.x, 0.0f},
        { s.y,  u.y,  f.y, 0.0f},
        { s.z,  u.z,  f.z, 0.0f},
        {-t.x, -t.y, -t.z, 1.0f},
    }};
}

static inline void demo_matrix_multiply(struct demo_matrix *out,
        const struct demo_matrix *a, const struct demo_matrix *b)
{
    unsigned int i, j;

    for (i = 0; i < 4; ++i)
    {
        for (j = 0; j < 4; ++j)
        {
            out->m[i][j] = a->m[i][0] * b->m[0][j]
                    + a->m[i][1] * b->m[1][j]
                    + a->m[i][2] * b->m[2][j]
                    + a->m[i][3] * b->m[3][j];
        }
    }
}

static inline void demo_matrix_perspective_rh(struct demo_matrix *m, float w, float h, float z_near, float z_far)
{
    float sx = 2.0 * z_near / w;
    float sy = 2.0 * z_near / h;
    float sz = z_far / (z_near - z_far);
    float d = z_near * sz;

    *m = (struct demo_matrix)
    {{
        {  sx, 0.0f, 0.0f,  0.0f},
        {0.0f,   sy, 0.0f,  0.0f},
        {0.0f, 0.0f,   sz, -1.0f},
        {0.0f, 0.0f,    d,  0.0f},
    }};
}

static inline void demo_rasterizer_desc_init_default(D3D12_RASTERIZER_DESC *desc)
{
    desc->FillMode = D3D12_FILL_MODE_SOLID;
    desc->CullMode = D3D12_CULL_MODE_BACK;
    desc->FrontCounterClockwise = FALSE;
    desc->DepthBias = D3D12_DEFAULT_DEPTH_BIAS;
    desc->DepthBiasClamp = D3D12_DEFAULT_DEPTH_BIAS_CLAMP;
    desc->SlopeScaledDepthBias = D3D12_DEFAULT_SLOPE_SCALED_DEPTH_BIAS;
    desc->DepthClipEnable = TRUE;
    desc->MultisampleEnable = FALSE;
    desc->AntialiasedLineEnable = FALSE;
    desc->ForcedSampleCount = 0;
    desc->ConservativeRaster = D3D12_CONSERVATIVE_RASTERIZATION_MODE_OFF;
}

static inline void demo_blend_desc_init_default(D3D12_BLEND_DESC *desc)
{
    static const D3D12_RENDER_TARGET_BLEND_DESC rt_blend_desc =
    {
        .BlendEnable = FALSE,
        .LogicOpEnable = FALSE,
        .SrcBlend = D3D12_BLEND_ONE,
        .DestBlend = D3D12_BLEND_ZERO,
        .BlendOp = D3D12_BLEND_OP_ADD,
        .SrcBlendAlpha = D3D12_BLEND_ONE,
        .DestBlendAlpha = D3D12_BLEND_ZERO,
        .BlendOpAlpha = D3D12_BLEND_OP_ADD,
        .LogicOp = D3D12_LOGIC_OP_NOOP,
        .RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL,
    };
    unsigned int i;

    desc->AlphaToCoverageEnable = FALSE;
    desc->IndependentBlendEnable = FALSE;
    for (i = 0; i < ARRAY_SIZE(desc->RenderTarget); ++i)
    {
        desc->RenderTarget[i] = rt_blend_desc;
    }
}

static inline HRESULT demo_create_root_signature(ID3D12Device *device,
        const D3D12_ROOT_SIGNATURE_DESC *desc, ID3D12RootSignature **signature)
{
    ID3DBlob *blob;
    HRESULT hr;

    if (FAILED(hr = D3D12SerializeRootSignature(desc, D3D_ROOT_SIGNATURE_VERSION_1, &blob, NULL)))
        return hr;
    hr = ID3D12Device_CreateRootSignature(device, 0, ID3D10Blob_GetBufferPointer(blob),
            ID3D10Blob_GetBufferSize(blob), &IID_ID3D12RootSignature, (void **)signature);
    ID3D10Blob_Release(blob);

    return hr;
}

#ifdef VKD3D_CROSSTEST
#include "demo_d3d12.h"
#else
# ifndef _WIN32
#  define INFINITE VKD3D_INFINITE
# endif
#include "demo_vkd3d.h"
#endif
