// Here lies the slow as shit renderer!

#ifndef polymer_h_
# define polymer_h_

# include "baselayer.h"
# include "build.h"
# include "compat.h"
# include "glad/glad.h"
# include "glbuild.h"
# include "hightile.h"
# include "mdsprite.h"
# include "osd.h"
# include "polymost.h"
# include "pragmas.h"

#ifdef __cplusplus
extern "C" {
#endif

#define PR_LINEAR_FOG

// CVARS
extern int32_t      pr_lighting;
extern int32_t      pr_normalmapping;
extern int32_t      pr_specularmapping;
extern int32_t      pr_shadows;
extern int32_t      pr_shadowcount;
extern int32_t      pr_shadowdetail;
extern int32_t      pr_shadowfiltering;
extern int32_t      pr_maxlightpasses;
extern int32_t      pr_maxlightpriority;
extern int32_t      pr_fov;
extern double       pr_customaspect;
extern int32_t      pr_billboardingmode;
extern int32_t      pr_verbosity;
extern int32_t      pr_wireframe;
extern int32_t      pr_buckets;
extern int32_t      pr_gpusmoothing;
extern int32_t      pr_overrideparallax;
extern float        pr_parallaxscale;
extern float        pr_parallaxbias;
extern int32_t      pr_overridespecular;
extern float        pr_specularpower;
extern float        pr_specularfactor;
extern int32_t      pr_highpalookups;
extern int32_t      pr_artmapping;
extern int32_t      pr_overridehud;
extern float        pr_hudxadd;
extern float        pr_hudyadd;
extern float        pr_hudzadd;
extern int32_t      pr_hudangadd;
extern int32_t      pr_hudfov;
extern float        pr_overridemodelscale;
extern int32_t      pr_ati_fboworkaround;
extern int32_t      pr_ati_nodepthoffset;
#ifdef __APPLE__
extern int32_t      pr_ati_textureformat_one;
#endif
extern int32_t      pr_nullrender;

extern int32_t      r_pr_maxlightpasses;
extern int32_t      r_pr_constrained;

// MATERIAL
typedef enum {
                    PR_BIT_HEADER,              // must be first
                    PR_BIT_ANIM_INTERPOLATION,
                    PR_BIT_LIGHTING_PASS,
                    PR_BIT_NORMAL_MAP,
                    PR_BIT_ART_MAP,
                    PR_BIT_DIFFUSE_MAP,
                    PR_BIT_DIFFUSE_DETAIL_MAP,
                    PR_BIT_DIFFUSE_MODULATION,
                    PR_BIT_DIFFUSE_MAP2,
                    PR_BIT_HIGHPALOOKUP_MAP,
                    PR_BIT_SPECULAR_MAP,
                    PR_BIT_SPECULAR_MATERIAL,
                    PR_BIT_MIRROR_MAP,
                    PR_BIT_FOG,
                    PR_BIT_GLOW_MAP,
                    PR_BIT_PROJECTION_MAP,
                    PR_BIT_SHADOW_MAP,
                    PR_BIT_LIGHT_MAP,
                    PR_BIT_SPOT_LIGHT,
                    PR_BIT_POINT_LIGHT,
                    PR_BIT_FOOTER,              // must be just before last
                    PR_BIT_COUNT                // must be last
}                   prbittype;

typedef struct      s_prmaterial {
    // PR_BIT_ANIM_INTERPOLATION
    GLfloat         frameprogress;
    GLfloat*        nextframedata;
    // PR_BIT_NORMAL_MAP
    GLuint          normalmap;
    GLfloat         normalbias[2];
    GLfloat*        tbn;
    // PR_BIT_ART_MAP
    GLuint          artmap;
    GLuint          basepalmap;
    GLuint          lookupmap;
    GLint           shadeoffset;
    GLfloat         visibility;
    // PR_BIT_DIFFUSE_MAP
    GLuint          diffusemap;
    GLfloat         diffusescale[2];
    // PR_BIT_HIGHPALOOKUP_MAP
    GLuint          highpalookupmap;
    // PR_BIT_DIFFUSE_DETAIL_MAP
    GLuint          detailmap;
    GLfloat         detailscale[2];
    // PR_BIT_DIFFUSE_MODULATION
    GLubyte         diffusemodulation[4];
    // PR_BIT_SPECULAR_MAP
    GLuint          specmap;
    // PR_BIT_SPECULAR_MATERIAL
    GLfloat         specmaterial[2];
    // PR_BIT_MIRROR_MAP
    GLuint          mirrormap;
    // PR_BIT_GLOW_MAP
    GLuint          glowmap;
    // PR_BIT_SHADOW_MAP
    GLboolean       mdspritespace;
}                   _prmaterial;

typedef struct      s_prrograminfo {
    GLuint          handle;
    GLint           uniform_colorCorrection;
    // PR_BIT_ANIM_INTERPOLATION
    GLint           attrib_nextFrameData;
    GLint           attrib_nextFrameNormal;
    GLint           uniform_frameProgress;
    // PR_BIT_NORMAL_MAP
    GLint           attrib_T;
    GLint           attrib_B;
    GLint           attrib_N;
    GLint           uniform_eyePosition;
    GLint           uniform_normalMap;
    GLint           uniform_normalBias;
    // PR_BIT_ART_MAP
    GLuint          uniform_artMap;
    GLuint          uniform_basePalMap;
    GLuint          uniform_lookupMap;
    GLuint          uniform_shadeOffset;
    GLuint          uniform_visibility;
    // PR_BIT_DIFFUSE_MAP
    GLint           uniform_diffuseMap;
    GLint           uniform_diffuseScale;
    // PR_BIT_HIGHPALOOKUP_MAP
    GLuint          uniform_highPalookupMap;
    // PR_BIT_DIFFUSE_DETAIL_MAP
    GLint           uniform_detailMap;
    GLint           uniform_detailScale;
    // PR_BIT_SPECULAR_MAP
    GLint           uniform_specMap;
    // PR_BIT_SPECULAR_MATERIAL
    GLint           uniform_specMaterial;
    // PR_BIT_MIRROR_MAP
    GLint           uniform_mirrorMap;
#ifdef PR_LINEAR_FOG
    // PR_BIT_FOG
    GLint           uniform_linearFog;
#endif
    // PR_BIT_GLOW_MAP
    GLint           uniform_glowMap;
    // PR_BIT_PROJECTION_MAP
    GLint           uniform_shadowProjMatrix;
    // PR_BIT_SHADOW_MAP
    GLint           uniform_shadowMap;
    // PR_BIT_LIGHT_MAP
    GLint           uniform_lightMap;
    // PR_BIT_SPOT_LIGHT
    GLint           uniform_spotDir;
    GLint           uniform_spotRadius;
}                   _prprograminfo;

#define             PR_INFO_LOG_BUFFER_SIZE 8192

// Think about changing highPal[Scale|Bias] in the program bit if you change this
#define             PR_HIGHPALOOKUP_BIT_DEPTH 6
#define             PR_HIGHPALOOKUP_DIM (1 << PR_HIGHPALOOKUP_BIT_DEPTH)
#define             PR_HIGHPALOOKUP_DATA_SIZE (4 * PR_HIGHPALOOKUP_DIM * \
                                                   PR_HIGHPALOOKUP_DIM * \
                                                   PR_HIGHPALOOKUP_DIM)

typedef struct      s_prprogrambit {
    const char*           vert_def;
    const char*           vert_prog;
    const char*           frag_def;
    const char*           frag_prog;
}                   _prprogrambit;

typedef struct      s_prbucket {
    // index
    int16_t         tilenum;
    char            pal;

    _prmaterial     material;
    int32_t         invalidmaterial;

    // geom indices
    GLuint*          indices;
    uint32_t         count;
    uint32_t         buffersize;
    GLuint*          indiceoffset;

    struct s_prbucket* next;
}                   _prbucket;

#include "prlights.h"

// RENDER TARGETS
typedef struct      s_prrt {
    GLenum          target;
    GLuint          color;
    GLuint          z;
    GLuint          fbo;
    int32_t         xdim, ydim;
}                   _prrt;

// BUILD DATA

typedef struct      s_prvert {
    GLfloat         x;
    GLfloat         y;
    GLfloat         z;
    GLfloat         u;
    GLfloat         v;
    GLubyte         r;
    GLubyte         g;
    GLubyte         b;
    GLubyte         a;
}                   _prvert;

typedef struct      s_prplane {
    // geometry
    _prvert*        buffer;
    int32_t         vertcount;
    GLuint          vbo;
    uint32_t        mapvbo_vertoffset;
    _prbucket*      bucket;
    // attributes
    GLfloat         tbn[3][3];
    GLfloat         plane[4];
    _prmaterial     material;
    // elements
    GLushort*       indices;
    int32_t         indicescount;
    GLuint          ivbo;
    // lights
    int16_t         lights[PR_MAXPLANELIGHTS];
    uint16_t        lightcount;
}                   _prplane;

typedef struct      s_prsector {
    // polymer data
    GLfloat*       verts;
    _prplane        floor;
    _prplane        ceil;
    int32_t         indicescount;
    int32_t         oldindicescount;
    // stuff
    float           wallsproffset;
    float           floorsproffset;
    // build sector data
    int32_t         ceilingz, floorz;
    uint16_t        ceilingstat, floorstat;
    int16_t         ceilingpicnum, ceilingheinum;
    int8_t          ceilingshade;
    uint8_t         ceilingpal, ceilingxpanning, ceilingypanning;
    int16_t         floorpicnum, floorheinum;
    int8_t          floorshade;
    uint8_t         floorpal, floorxpanning, floorypanning;
    uint8_t         visibility;

    int16_t         floorpicnum_anim, ceilingpicnum_anim;

    struct          {
        unsigned int    empty       : 1;
        unsigned int    uptodate    : 1;
        unsigned int    invalidtex  : 1;
    }               flags;
    uint32_t        invalidid;
    uint32_t        trackedrev;
}                   _prsector;

typedef struct      s_prwall {
    _prplane        wall;
    _prplane        over;
    _prplane        mask;

    // stuff
    GLfloat*        bigportal;
    //GLfloat*        cap;
    GLuint          stuffvbo;

    // build wall data
    uint16_t        cstat;
    int16_t         picnum, overpicnum;
    int8_t          shade;
    uint8_t         pal, xrepeat, yrepeat, xpanning, ypanning;

    // nextwall data
    int16_t         nwallpicnum, nwallcstat;
    int8_t          nwallxpanning, nwallypanning;
    int8_t          nwallshade;

    int16_t         picnum_anim, overpicnum_anim;

    char            underover;
    uint32_t        invalidid;
    struct          {
        unsigned int    empty       : 1;
        unsigned int    uptodate    : 1;
        unsigned int    invalidtex  : 1;
    }               flags;
    uint32_t        trackedrev;
}                   _prwall;

typedef struct      s_prsprite {
    _prplane        plane;
    uint32_t        hash;
}                   _prsprite;

typedef struct      s_prmirror {
    _prplane        *plane;
    int16_t         sectnum;
    int16_t         wallnum;
}                   _prmirror;

typedef struct      s_prhighpalookup {
    char            *data;
    GLuint          map;
}                   _prhighpalookup;

typedef void    (*animatespritesptr)(int32_t, int32_t, int32_t, int32_t, int32_t);

typedef struct      s_pranimatespritesinfo {
    animatespritesptr animatesprites;
    int32_t         x, y, z, a, smoothratio;
}                   _pranimatespritesinfo;

// this one has to be provided by the application
extern void G_Polymer_UnInit(void);

// EXTERNAL FUNCTIONS
int32_t polymer_init(void);
void    polymer_uninit(void);
void    polymer_setaspect(int32_t);
void    polymer_glinit(void);
void    polymer_resetlights(void);
void    polymer_loadboard(void);
int32_t polymer_printtext256(int32_t xpos, int32_t ypos, int16_t col, int16_t backcol, const char *name, char fontsize);
void    polymer_fillpolygon(int32_t npoints);
void    polymer_drawrooms(int32_t daposx, int32_t daposy, int32_t daposz, fix16_t daang, int32_t dahoriz, int16_t dacursectnum);
void    polymer_drawmasks(void);
void    polymer_editorpick(void);
void    polymer_inb4rotatesprite(int16_t tilenum, char pal, int8_t shade, int32_t method);
void    polymer_postrotatesprite(void);
void    polymer_drawmaskwall(int32_t damaskwallcnt);
void    polymer_drawsprite(int32_t snum);
void    polymer_setanimatesprites(animatespritesptr animatesprites, int32_t x, int32_t y, int32_t z, int32_t a, int32_t smoothratio);
int16_t polymer_addlight(_prlight *light);
void    polymer_deletelight(int16_t lighti);
void    polymer_invalidatelights(void);
void    polymer_texinvalidate(void);
void    polymer_definehighpalookup(uint8_t basepalnum, uint8_t palnum, char *data);
bool    polymer_havehighpalookup(uint8_t basepalnum, uint8_t palnum);


extern _prsprite    *prsprites[MAXSPRITES];
static inline void polymer_invalidatesprite(int32_t i)
{
    if (prsprites[i])
        prsprites[i]->hash = 0xDEADBEEF;
}

extern GLuint       prartmaps[MAXTILES];
static inline void polymer_invalidateartmap(int32_t tilenum)
{
    if (prartmaps[tilenum])
    {
        glDeleteTextures(1, &prartmaps[tilenum]);
        prartmaps[tilenum] = 0;
    }
}

// Compare with eligible_for_tileshades()
static FORCE_INLINE int32_t polymer_eligible_for_artmap(int32_t tilenum, const pthtyp *pth) { return ((!pth || !pth->hicr) && tilenum < MAXUSERTILES); }
static FORCE_INLINE int     polymer_useartmapping(void) { return pr_artmapping && gltexfiltermode == 0; }

# ifdef POLYMER_C

// CORE
static void         polymer_displayrooms(int16_t sectnum);
static void         polymer_emptybuckets(void);
static _prbucket*   polymer_findbucket(int16_t tilenum, char pal);
static void         polymer_bucketplane(_prplane* plane);
static void         polymer_drawplane(_prplane* plane);
static inline void  polymer_inb4mirror(_prvert* buffer, const GLfloat* plane);
static void         polymer_animatesprites(void);
static void         polymer_freeboard(void);
// SECTORS
static int32_t      polymer_initsector(int16_t sectnum);
static int32_t      polymer_updatesector(int16_t sectnum);
static int32_t      polymer_buildfloor(int16_t sectnum);
static void         polymer_drawsector(int16_t sectnum, int32_t domasks);
// WALLS
static int32_t      polymer_initwall(int16_t wallnum);
static void         polymer_updatewall(int16_t wallnum);
static void         polymer_drawwall(int16_t sectnum, int16_t wallnum);
// HSR
static void         polymer_computeplane(_prplane* p);
static inline void  polymer_transformpoint(const float* inpos, float* pos, const float* matrix);
static inline void  polymer_pokesector(int16_t sectnum);
static void         polymer_extractfrustum(GLfloat* modelview, GLfloat* projection, float* frustum);
static inline int32_t polymer_planeinfrustum(_prplane const &plane, const float* frustum);
static inline void  polymer_scansprites(int16_t sectnum, tspriteptr_t tsprite, int32_t* spritesortcnt);
static void         polymer_updatesprite(int32_t snum);
// SKIES
static void         polymer_getsky(int16_t picnum, uint8_t pal, int8_t shade);
static void         polymer_drawsky(int16_t tilenum, char palnum, int8_t shade);
static void         polymer_initartsky(void);
static void         polymer_drawartsky(int16_t tilenum, char palnum, int8_t shade);
static void         polymer_drawartskyquad(int32_t p1, int32_t p2, GLfloat height);
static void         polymer_drawskybox(int16_t tilenum, char palnum, int8_t shade);
// MDSPRITES
static void         polymer_drawmdsprite(tspriteptr_t tspr);
static void         polymer_loadmodelvbos(md3model_t* m);
// MATERIALS
static void         polymer_getscratchmaterial(_prmaterial* material);
static _prbucket*   polymer_getbuildmaterial(_prmaterial* material, int16_t tilenum, char pal, int8_t shade, int8_t vis, int32_t cmeth);
static int32_t      polymer_bindmaterial(const _prmaterial *material, const int16_t* lights, int lightcount);
static void         polymer_unbindmaterial(int32_t programbits);
static _prprograminfo *polymer_compileprogram(int32_t programbits);
// LIGHTS
static void         polymer_removelight(int16_t lighti);
static void         polymer_updatelights(void);
static inline void  polymer_resetplanelights(_prplane* plane);
static void         polymer_addplanelight(_prplane* plane, int16_t lighti);
static inline void  polymer_deleteplanelight(_prplane* plane, int16_t lighti);
static int32_t      polymer_planeinlight(_prplane const &plane, _prlight const &light);
static inline void  polymer_invalidateplanelights(_prplane const &plane);
static void         polymer_invalidatesectorlights(int16_t sectnum);
static void         polymer_processspotlight(_prlight* light);
static int          polymer_culllight(int16_t lighti);
static void         polymer_prepareshadows(void);
// RENDER TARGETS
static void         polymer_initrendertargets(int32_t count);

#define INDICE(n) ((p->indices) ? (p->indices[(i+n)%(p->indicescount-1)]) : (((i+n)%(p->vertcount-1))))

#define SWITCH_CULL_DIRECTION { culledface = (culledface == GL_FRONT) ? GL_BACK : GL_FRONT; glCullFace(culledface); }

static FORCE_INLINE GLfloat dot2f(const GLfloat *v1, const GLfloat *v2) { return v1[0] * v2[0] + v1[1] * v2[1]; }
static FORCE_INLINE GLfloat dot3f(const GLfloat *v1, const GLfloat *v2) { return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2]; }
static FORCE_INLINE void relvec2f(const GLfloat *v1, const GLfloat *v2, GLfloat *out)
{
    out[0] = v2[0] - v1[0];
    out[1] = v2[1] - v1[1];
}

// the following from gle/vvector.h

/* ========================================================== */
/* determinant of matrix
 *
 * Computes determinant of matrix m, returning d
 */

#define DETERMINANT_3X3(d, m)                                   \
    {                                                           \
        d = m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]);  \
        d -= m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0]); \
        d += m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); \
    }

/* ========================================================== */
/* i,j,th cofactor of a 4x4 matrix
 *
 */

#define COFACTOR_4X4_IJ(fac, m, i, j)            \
    {                                            \
        int ii[4], jj[4], k;                     \
                                                 \
        /* compute which row, columnt to skip */ \
        for (k = 0; k < i; k++) ii[k] = k;       \
        for (k = i; k < 3; k++) ii[k] = k + 1;   \
        for (k = 0; k < j; k++) jj[k] = k;       \
        for (k = j; k < 3; k++) jj[k] = k + 1;   \
                                                 \
        (fac) = m[ii[0]][jj[0]] * (m[ii[1]][jj[1]]*m[ii[2]][jj[2]]    \
                                 - m[ii[1]][jj[2]]*m[ii[2]][jj[1]]);  \
        (fac) -= m[ii[0]][jj[1]] * (m[ii[1]][jj[0]]*m[ii[2]][jj[2]]   \
                                  - m[ii[1]][jj[2]]*m[ii[2]][jj[0]]); \
        (fac) += m[ii[0]][jj[2]] * (m[ii[1]][jj[0]]*m[ii[2]][jj[1]]   \
                                  - m[ii[1]][jj[1]]*m[ii[2]][jj[0]]); \
        /* compute sign */                       \
        k = i + j;                               \
        if (k != (k / 2) * 2)                    \
            (fac) = -(fac);                      \
    }

/* ========================================================== */
/* determinant of matrix
 *
 * Computes determinant of matrix m, returning d
 */

#define DETERMINANT_4X4(d, m)            \
    {                                    \
        float cofac;                     \
        COFACTOR_4X4_IJ(cofac, m, 0, 0); \
        d = m[0][0] * cofac;             \
        COFACTOR_4X4_IJ(cofac, m, 0, 1); \
        d += m[0][1] * cofac;            \
        COFACTOR_4X4_IJ(cofac, m, 0, 2); \
        d += m[0][2] * cofac;            \
        COFACTOR_4X4_IJ(cofac, m, 0, 3); \
        d += m[0][3] * cofac;            \
    }

/* ========================================================== */
/* compute adjoint of matrix and scale
 *
 * Computes adjoint of matrix m, scales it by s, returning a
 */

#define SCALE_ADJOINT_3X3(a, s, m)                               \
    {                                                            \
        a[0][0] = (s) * (m[1][1] * m[2][2] - m[1][2] * m[2][1]); \
        a[1][0] = (s) * (m[1][2] * m[2][0] - m[1][0] * m[2][2]); \
        a[2][0] = (s) * (m[1][0] * m[2][1] - m[1][1] * m[2][0]); \
                                                                 \
        a[0][1] = (s) * (m[0][2] * m[2][1] - m[0][1] * m[2][2]); \
        a[1][1] = (s) * (m[0][0] * m[2][2] - m[0][2] * m[2][0]); \
        a[2][1] = (s) * (m[0][1] * m[2][0] - m[0][0] * m[2][1]); \
                                                                 \
        a[0][2] = (s) * (m[0][1] * m[1][2] - m[0][2] * m[1][1]); \
        a[1][2] = (s) * (m[0][2] * m[1][0] - m[0][0] * m[1][2]); \
        a[2][2] = (s) * (m[0][0] * m[1][1] - m[0][1] * m[1][0]); \
    }

/* ========================================================== */
/* compute adjoint of matrix and scale
 *
 * Computes adjoint of matrix m, scales it by s, returning a
 */

#define SCALE_ADJOINT_4X4(a, s, m)                 \
    {                                              \
        for (int i = 0; i < 4; i++)                \
            for (int j = 0; j < 4; j++)            \
            {                                      \
                COFACTOR_4X4_IJ(a[j][i], m, i, j); \
                a[j][i] *= s;                      \
            }                                      \
    }

/* ========================================================== */
/* inverse of matrix
 *
 * Compute inverse of matrix a, returning determinant m and
 * inverse b
 */

#define INVERT_3X3(b, det, a)         \
    {                                 \
        DETERMINANT_3X3(det, a);      \
        float tmp = 1.f / (det);      \
        SCALE_ADJOINT_3X3(b, tmp, a); \
    }

/* ========================================================== */
/* inverse of matrix
 *
 * Compute inverse of matrix a, returning determinant m and
 * inverse b
 */

#define INVERT_4X4(b, det, a)         \
    {                                 \
        DETERMINANT_4X4(det, a);      \
        float tmp = 1.f / (det);      \
        SCALE_ADJOINT_4X4(b, tmp, a); \
    }

# endif // !POLYMER_C

#ifdef __cplusplus
}
#endif

#endif // !polymer_h_
