/*
 * This file is part of libplacebo.
 *
 * libplacebo 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.
 *
 * libplacebo 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 libplacebo. If not, see <http://www.gnu.org/licenses/>.
 */

#pragma once

#include <stdio.h>
#include <limits.h>

#include "common.h"
#include "cache.h"
#include "log.h"
#include "gpu.h"

#include <libplacebo/shaders.h>

// This represents an identifier (e.g. name of function, uniform etc.) for
// a shader resource. Not human-readable.

typedef unsigned short ident_t;
#define $           "_%hx"
#define NULL_IDENT  0u

#define sh_mkident(id, name) ((ident_t) id)
#define sh_ident_tostr(id)   pl_asprintf(sh->tmp, $, id)

enum {
    IDENT_BITS     = 8 * sizeof(ident_t),
    IDENT_MASK     = (uintptr_t) USHRT_MAX,
    IDENT_SENTINEL = (uintptr_t) 0x20230319 << IDENT_BITS,
};

// Functions to pack/unpack an identifier into a `const char *` name field.
// Used to defer string templating of friendly names until actually necessary
static inline const char *sh_ident_pack(ident_t id)
{
    return (const char *)(uintptr_t) (IDENT_SENTINEL | id);
}

static inline ident_t sh_ident_unpack(const char *name)
{
    uintptr_t uname = (uintptr_t) name;
    assert((uname & ~IDENT_MASK) == IDENT_SENTINEL);
    return uname & IDENT_MASK;
}

enum pl_shader_buf {
    SH_BUF_PRELUDE, // extra #defines etc.
    SH_BUF_HEADER,  // previous passes, helper function definitions, etc.
    SH_BUF_BODY,    // partial contents of the "current" function
    SH_BUF_FOOTER,  // will be appended to the end of the current function
    SH_BUF_COUNT,
};

enum pl_shader_type {
    SH_AUTO,
    SH_COMPUTE,
    SH_FRAGMENT
};

struct sh_info {
    // public-facing struct
    struct pl_shader_info_t info;

    // internal fields
    void *tmp;
    pl_rc_t rc;
    pl_str desc;
    PL_ARRAY(const char *) steps;
};

struct pl_shader_t {
    pl_log log;
    void *tmp; // temporary allocations (freed on pl_shader_reset)
    struct sh_info *info;
    pl_str data; // pooled/recycled scratch buffer for small allocations
    PL_ARRAY(pl_shader_obj) obj;
    bool failed;
    bool mutable;
    ident_t name;
    enum pl_shader_sig input, output;
    int output_w;
    int output_h;
    bool transpose;
    pl_str_builder buffers[SH_BUF_COUNT];
    enum pl_shader_type type;
    bool flexible_work_groups;
    int group_size[2];
    size_t shmem;
    enum pl_sampler_type sampler_type;
    char sampler_prefix;
    unsigned short prefix; // pre-processed version of res.params.id
    unsigned short fresh;

    // Note: internally, these `pl_shader_va` etc. use raw ident_t fields
    // instead of `const char *` wherever a name is required! These are
    // translated to legal strings either in `pl_shader_finalize`, or inside
    // the `pl_dispatch` shader compilation step.
    PL_ARRAY(struct pl_shader_va) vas;
    PL_ARRAY(struct pl_shader_var) vars;
    PL_ARRAY(struct pl_shader_desc) descs;
    PL_ARRAY(struct pl_shader_const) consts;

    // cached result of `pl_shader_finalize`
    struct pl_shader_res result;
};

// Free temporary resources associated with a shader. Normally called by
// pl_shader_reset(), but used internally to reduce memory waste.
void sh_deref(pl_shader sh);

// Same as `pl_shader_finalize` but doesn't generate `sh->res`, instead returns
// the string builder to be used to finalize the shader. Assumes the caller
// will access the shader's internal fields directly.
pl_str_builder sh_finalize_internal(pl_shader sh);

// Helper functions for convenience
#define SH_PARAMS(sh) ((sh)->info->info.params)
#define SH_GPU(sh) (SH_PARAMS(sh).gpu)
#define SH_CACHE(sh) pl_gpu_cache(SH_GPU(sh))

// Returns the GLSL version, defaulting to desktop 130.
struct pl_glsl_version sh_glsl(const pl_shader sh);

#define SH_FAIL(sh, ...) do {    \
        sh->failed = true;       \
        PL_ERR(sh, __VA_ARGS__); \
    } while (0)

// Attempt enabling compute shaders for this pass, if possible
bool sh_try_compute(pl_shader sh, int bw, int bh, bool flex, size_t mem);

// Attempt merging a secondary shader into the current shader. Returns NULL if
// merging fails (e.g. incompatible signatures); otherwise returns an identifier
// corresponding to the generated subpass function.
//
// If successful, the subpass shader is set to an undefined failure state and
// must be explicitly reset/aborted before being re-used.
ident_t sh_subpass(pl_shader sh, pl_shader sub);

// Helpers for adding new variables/descriptors/etc. with fresh, unique
// identifier names. These will never conflict with other identifiers, even
// if the shaders are merged together.
ident_t sh_fresh(pl_shader sh, const char *name);

// Add a new shader var and return its identifier
ident_t sh_var(pl_shader sh, struct pl_shader_var sv);

// Helper functions for `sh_var`
ident_t sh_var_int(pl_shader sh, const char *name, int val, bool dynamic);
ident_t sh_var_uint(pl_shader sh, const char *name, unsigned int val, bool dynamic);
ident_t sh_var_float(pl_shader sh, const char *name, float val, bool dynamic);
ident_t sh_var_mat3(pl_shader sh, const char *name, pl_matrix3x3 val);
#define SH_INT_DYN(val)   sh_var_int(sh, "const", val, true)
#define SH_UINT_DYN(val)  sh_var_uint(sh, "const", val, true)
#define SH_FLOAT_DYN(val) sh_var_float(sh, "const", val, true)
#define SH_MAT3(val) sh_var_mat3(sh, "mat", val)

// Add a new shader desc and return its identifier.
ident_t sh_desc(pl_shader sh, struct pl_shader_desc sd);

// Add a new shader constant and return its identifier.
ident_t sh_const(pl_shader sh, struct pl_shader_const sc);

// Helper functions for `sh_const`
ident_t sh_const_int(pl_shader sh, const char *name, int val);
ident_t sh_const_uint(pl_shader sh, const char *name, unsigned int val);
ident_t sh_const_float(pl_shader sh, const char *name, float val);
#define SH_INT(val)     sh_const_int(sh, "const", val)
#define SH_UINT(val)    sh_const_uint(sh, "const", val)
#define SH_FLOAT(val)   sh_const_float(sh, "const", val)

// Add a new shader va and return its identifier
ident_t sh_attr(pl_shader sh, struct pl_shader_va sva);

// Helper to add a a vec2 VA from a pl_rect2df. Returns NULL_IDENT on failure.
ident_t sh_attr_vec2(pl_shader sh, const char *name, const pl_rect2df *rc);

// Bind a texture under a given transformation and make its attributes
// available as well. If an output pointer for one of the attributes is left
// as NULL, that attribute will not be added. Returns NULL on failure. `rect`
// is optional, and defaults to the full texture if left as NULL.
//
// Note that for e.g. compute shaders, the vec2 out_pos might be a macro that
// expands to an expensive computation, and should be cached by the user.
ident_t sh_bind(pl_shader sh, pl_tex tex,
                enum pl_tex_address_mode address_mode,
                enum pl_tex_sample_mode sample_mode,
                const char *name, const pl_rect2df *rect,
                ident_t *out_pos, ident_t *out_pt);

// Incrementally build up a buffer by adding new variable elements to the
// buffer, resizing buf.buffer_vars if necessary. Returns whether or not the
// variable could be successfully added (which may fail if you try exceeding
// the size limits of the buffer type). If successful, the layout is stored
// in *out_layout (may be NULL).
bool sh_buf_desc_append(void *alloc, pl_gpu gpu,
                        struct pl_shader_desc *buf_desc,
                        struct pl_var_layout *out_layout,
                        const struct pl_var new_var);

size_t sh_buf_desc_size(const struct pl_shader_desc *buf_desc);


// Underlying function for appending text to a shader
#define sh_append(sh, buf, ...) \
    pl_str_builder_addf((sh)->buffers[buf], __VA_ARGS__)

#define sh_append_str(sh, buf, str) \
    pl_str_builder_str((sh)->buffers[buf], str)

#define GLSLP(...) sh_append(sh, SH_BUF_PRELUDE, __VA_ARGS__)
#define GLSLH(...) sh_append(sh, SH_BUF_HEADER, __VA_ARGS__)
#define GLSL(...)  sh_append(sh, SH_BUF_BODY, __VA_ARGS__)
#define GLSLF(...) sh_append(sh, SH_BUF_FOOTER, __VA_ARGS__)

// Attach a description to a shader
void sh_describef(pl_shader sh, const char *fmt, ...)
    PL_PRINTF(2, 3);

static inline void sh_describe(pl_shader sh, const char *desc)
{
    PL_ARRAY_APPEND(sh->info, sh->info->steps, desc);
};

// Requires that the share is mutable, has an output signature compatible
// with the given input signature, as well as an output size compatible with
// the given size requirements. Errors and returns false otherwise.
bool sh_require(pl_shader sh, enum pl_shader_sig insig, int w, int h);

// Shader resources

enum pl_shader_obj_type {
    PL_SHADER_OBJ_INVALID = 0,
    PL_SHADER_OBJ_COLOR_MAP,
    PL_SHADER_OBJ_SAMPLER,
    PL_SHADER_OBJ_DITHER,
    PL_SHADER_OBJ_LUT,
    PL_SHADER_OBJ_AV1_GRAIN,
    PL_SHADER_OBJ_FILM_GRAIN,
    PL_SHADER_OBJ_RESHAPE,
};

struct pl_shader_obj_t {
    enum pl_shader_obj_type type;
    pl_rc_t rc;
    pl_gpu gpu;
    void (*uninit)(pl_gpu gpu, void *priv);
    void *priv;
};

// Returns (*ptr)->priv, or NULL on failure
void *sh_require_obj(pl_shader sh, pl_shader_obj *ptr,
                     enum pl_shader_obj_type type, size_t priv_size,
                     void (*uninit)(pl_gpu gpu, void *priv));

#define SH_OBJ(sh, ptr, type, t, uninit) \
    ((t*) sh_require_obj(sh, ptr, type, sizeof(t), uninit))

// Initializes a PRNG. The resulting string will directly evaluate to a
// pseudorandom, uniformly distributed vec3 from [0.0,1.0]. Since this
// algorithm works by mutating a state variable, if the user wants to use the
// resulting PRNG inside a subfunction, they must add an extra `inout prng_t %s`
// with the contents of `state` to the signature. (Optional)
//
// If `temporal` is set, the PRNG will vary across frames.
ident_t sh_prng(pl_shader sh, bool temporal, ident_t *state);

// Backing memory type
enum sh_lut_type {
    SH_LUT_AUTO = 0, // pick whatever makes the most sense
    SH_LUT_TEXTURE,  // upload as texture
    SH_LUT_UNIFORM,  // uniform array
    SH_LUT_LITERAL,  // constant / literal array in shader source (fallback)
};

// Interpolation method
enum sh_lut_method {
    SH_LUT_NONE = 0,    // no interpolation, integer indices
    SH_LUT_LINEAR,      // linear interpolation, vecN indices in range [0,1]
    SH_LUT_CUBIC,       // (bi/tri)cubic interpolation
    SH_LUT_TETRAHEDRAL, // tetrahedral interpolation for vec3, equivalent to
                        // SH_LUT_LINEAR for lower dimensions
};

struct sh_lut_params {
    pl_shader_obj *object;

    // Type of the LUT we intend to generate.
    //
    // Note: If `var_type` is PL_VAR_*INT, `method` must be SH_LUT_NONE.
    enum pl_var_type var_type;
    enum sh_lut_type lut_type;
    enum sh_lut_method method;

    // For SH_LUT_TEXTURE, this can be used to override the texture's internal
    // format, in which case it takes precedence over the default for `type`.
    pl_fmt fmt;

    // LUT dimensions. Unused dimensions may be left as 0.
    int width;
    int height;
    int depth;
    int comps;

    // If true, the LUT will always be regenerated, even if the dimensions have
    // not changed.
    bool update;

    // Alternate way of triggering shader invalidations. If the signature
    // does not match the LUT's signature, it will be regenerated.
    uint64_t signature;

    // If set to true, shader objects will be preserved and updated in-place
    // rather than being treated as read-only.
    bool dynamic;

    // If set , generated shader objects are automatically cached in this
    // cache. Requires `signature` to be set (and uniquely identify the LUT).
    pl_cache cache;

    // Will be called with a zero-initialized buffer whenever the data needs to
    // be computed, which happens whenever the size is changed, the shader
    // object is invalidated, or `update` is set to true.
    //
    // Note: Interpretation of `data` is according to `type` and `fmt`.
    void (*fill)(void *data, const struct sh_lut_params *params);
    void *priv;

    // Debug tag to track LUT source
    pl_debug_tag debug_tag;
};

#define sh_lut_params(...) (&(struct sh_lut_params) {   \
        .debug_tag = PL_DEBUG_TAG,                      \
        __VA_ARGS__                                     \
    })

// Makes a table of values available as a shader variable, using an a given
// method (falling back if needed). The resulting identifier can be sampled
// directly as %s(pos), where pos is a vector with the right number of
// dimensions. `pos` must be an integer vector within the bounds of the array,
// unless the method is `SH_LUT_LINEAR`, in which case it's a float vector that
// gets interpolated and clamped as needed. Returns NULL on error.
ident_t sh_lut(pl_shader sh, const struct sh_lut_params *params);

static inline uint8_t sh_num_comps(uint8_t mask)
{
    pl_assert((mask & 0xF) == mask);
    return __builtin_popcount(mask);
}

static inline const char *sh_float_type(uint8_t mask)
{
    switch (sh_num_comps(mask)) {
    case 1: return "float";
    case 2: return "vec2";
    case 3: return "vec3";
    case 4: return "vec4";
    }

    pl_unreachable();
}

static inline const char *sh_int_type(uint8_t mask)
{
    switch (sh_num_comps(mask)) {
    case 1: return "int";
    case 2: return "ivec2";
    case 3: return "ivec3";
    case 4: return "ivec4";
    }

    pl_unreachable();
}

static inline const char *sh_uint_type(uint8_t mask)
{
    switch (sh_num_comps(mask)) {
    case 1: return "uint";
    case 2: return "uvec2";
    case 3: return "uvec3";
    case 4: return "uvec4";
    }

    pl_unreachable();
}

static inline const char *sh_bool_type(uint8_t mask)
{
    switch (sh_num_comps(mask)) {
    case 1: return "bool";
    case 2: return "bvec2";
    case 3: return "bvec3";
    case 4: return "bvec4";
    }

    pl_unreachable();
}

static inline const char *sh_swizzle(uint8_t mask)
{
    static const char * const swizzles[0x10] = {
        NULL, "r",  "g",  "rg",  "b",  "rb",  "gb",  "rgb",
        "a",  "ra", "ga", "rga", "ba", "rba", "gba", "rgba",
    };

    pl_assert(mask <= PL_ARRAY_SIZE(swizzles));
    return swizzles[mask];
}
