#include <ultra64.h>

#include "sm64.h"
#include "moving_texture.h"
#include "area.h"
#include "camera.h"
#include "rendering_graph_node.h"
#include "engine/math_util.h"
#include "memory.h"
#include "save_file.h"
#include "segment2.h"
#include "engine/surface_collision.h"
#include "geo_misc.h"
#include "rendering_graph_node.h"
#include "object_list_processor.h"

/**
 * This file contains functions for generating display lists with moving textures
 * (abbreviated movtex). This is used for water, sand, haze, mist and treadmills.
 * Each mesh using this system has the animated vertices stored as an array of shorts.
 * The first entry is the texture movement speed. After that the vertices are stored
 * in one of two layouts: one without per-vertex color attributes and one with.
 * [speed, v0(x,y,z, s,t)      , v1(x,y,z, s,t)      , ...]
 * [speed, v0(x,y,z, r,g,b s,t), v1(x,y,z, r,g,b s,t), ...]
 * x, y, z = vertex position as integers
 * s, t = texture coordinates as 6.10 fixed point number. That means coordinates in
 * range [0, 1024] are a unique part of the image, after that it repeats the image.
 *
 * The first vertex 'v0' is special because all subsequent vertices inherit its
 * texture offset. So to animate e.g. a treadmill, the speed component arr[0] is
 * simply added to the s component arr[7] every frame and the texture scrolls
 * horizontally over the entire mesh without changing the rest of the array.
 * Note that while the system allows many kinds of vertex animations, in
 * practice the only animation used is horizontally scrolling textures.
 *
 * After updating the base mesh, the vertices are converted to the format the RSP
 * understands and a display list is generated. The RSP can buffer 16 vertices at
 * a time, and this code assumes everything fits in one buffer, so every moving
 * texture mesh must have at most 16 vertices. As a result some meshes are split
 * up into multiple parts, like the sand pathway inside the pyramid which has 3
 * parts. The water stream in the Cavern of the Metal Cap fits in one mesh.
 *
 * Apart from this general system, there is also a simpler system for flat
 * quads with a rotating texture. This is often used for water, but also
 * for mist, toxic haze and lava inside the volcano. One quad is described
 * by the struct MovtexQuad, and multiple MovtexQuads form a MovtexQuadCollection.
 * A geo node has an id that corresponds to the id of a certain MovtexQuadCollection,
 * which will then be matched with the id of entries in gEnvironmentRegions to get the
 * y-position. The x and z coordinates are stored in the MovtexQuads themself,
 * so the water rectangle is separate from the actually drawn rectangle.
 */

// First entry in array is texture movement speed for both layouts
#define MOVTEX_ATTR_SPEED 0

// Different layouts for vertices
#define MOVTEX_LAYOUT_NOCOLOR 0
#define MOVTEX_LAYOUT_COLORED 1

// Attributes for movtex vertices
#define MOVTEX_ATTR_X 1
#define MOVTEX_ATTR_Y 2
#define MOVTEX_ATTR_Z 3

// For MOVTEX_LAYOUT_NOCOLOR only
#define MOVTEX_ATTR_NOCOLOR_S 4
#define MOVTEX_ATTR_NOCOLOR_T 5

// For MOVTEX_LAYOUT_COLORED only
#define MOVTEX_ATTR_COLORED_R 4
#define MOVTEX_ATTR_COLORED_G 5
#define MOVTEX_ATTR_COLORED_B 6
#define MOVTEX_ATTR_COLORED_S 7
#define MOVTEX_ATTR_COLORED_T 8

/**
 * An object containing all info for a mesh with moving textures.
 * Contains the vertices that are animated, but also the display list which
 * determines the connectivity, as well as the texture, texture blend color
 * and drawing layer.
 */
struct MovtexObject {
    /// number that geo nodes have as parameter to refer to this mesh
    u32 geoId;
    /// which texture to use for this mesh, index into gMovtexIdToTexture
    s32 textureId;
    /// amount of moving vertices
    s32 vtx_count;
    /// segmented address to movtex mesh with vertices
    void *movtexVerts;
    /// display list inserted before moving triangles
    Gfx *beginDl;
    /// display list inserted after moving triangles
    Gfx *endDl;
    /// display list with the actual moving texture triangles.
    /// Assumes the animated vertices are buffered and correct texture is set
    Gfx *triDl;
    // if the list does not have per-vertex colors, all vertices have these colors
    u8 r;      /// red
    u8 g;      /// green
    u8 b;      /// blue
    u8 a;      /// alpha
    s32 layer; /// the drawing layer for this mesh
};

/// Counters to make textures move iff the game is not paused.
s16 gMovtexCounter = 1;
s16 gMovtexCounterPrev = 0;

// Vertex colors for rectangles. Used to give mist a tint
#define MOVTEX_VTX_COLOR_DEFAULT 0 // no tint (white vertex colors)
#define MOVTEX_VTX_COLOR_YELLOW 1  // used for Hazy Maze Cave toxic haze
#define MOVTEX_VTX_COLOR_RED 2     // used for Shifting Sand Land around the Tox box maze

s8 gMovtexVtxColor = MOVTEX_VTX_COLOR_DEFAULT;

/// The height at which Mario entered the last painting. Used for Wet-Dry World only.
float gPaintingMarioYEntry = 0.0f;

/// Variable to ensure the initial Wet-Dry World water level is set only once
s32 gWdwWaterLevelSet = FALSE;

extern u8 ssl_quicksand[];
extern u8 ssl_pyramid_sand[];
extern u8 ttc_yellow_triangle[];

/**
 * An array for converting a movtex texture id to a pointer that can
 * be passed to gDPSetTextureImage.
 */
u8 *gMovtexIdToTexture[] = { texture_waterbox_water,     texture_waterbox_mist,
                             texture_waterbox_jrb_water, texture_waterbox_unknown_water,
                             texture_waterbox_lava,      ssl_quicksand,
                             ssl_pyramid_sand,           ttc_yellow_triangle };

extern Gfx castle_grounds_dl_waterfall[];
extern s16 castle_grounds_movtex_tris_waterfall[];
extern s16 ssl_movtex_tris_pyramid_sand_pathway_front[];
extern Gfx ssl_dl_pyramid_sand_pathway_begin[];
extern Gfx ssl_dl_pyramid_sand_pathway_end[];
extern Gfx ssl_dl_pyramid_sand_pathway_front_end[];
extern s16 ssl_movtex_tris_pyramid_sand_pathway_floor[];
extern Gfx ssl_dl_pyramid_sand_pathway_floor_begin[];
extern Gfx ssl_dl_pyramid_sand_pathway_floor_end[];
extern s16 ssl_movtex_tris_pyramid_sand_pathway_side[];
extern Gfx ssl_dl_pyramid_sand_pathway_side_end[];
extern s16 bitfs_movtex_tris_lava_first_section[];
extern Gfx bitfs_dl_lava_sections[];
extern s16 bitfs_movtex_tris_lava_second_section[];
extern s16 bitfs_movtex_tris_lava_floor[];
extern Gfx bitfs_dl_lava_floor[];
extern s16 lll_movtex_tris_lava_floor[];
extern Gfx lll_dl_lava_floor[];
extern s16 lll_movtex_tris_lavafall_volcano[];
extern Gfx lll_dl_lavafall_volcano[];
extern s16 cotmc_movtex_tris_water[];
extern Gfx cotmc_dl_water_begin[];
extern Gfx cotmc_dl_water_end[];
extern Gfx cotmc_dl_water[];
extern s16 ttm_movtex_tris_begin_waterfall[];
extern Gfx ttm_dl_waterfall[];
extern s16 ttm_movtex_tris_end_waterfall[];
extern s16 ttm_movtex_tris_begin_puddle_waterfall[];
extern Gfx ttm_dl_bottom_waterfall[];
extern s16 ttm_movtex_tris_end_puddle_waterfall[];
extern s16 ttm_movtex_tris_puddle_waterfall[];
extern Gfx ttm_dl_puddle_waterfall[];
extern s16 ssl_movtex_tris_pyramid_quicksand[];
extern Gfx ssl_dl_quicksand_begin[];
extern Gfx ssl_dl_quicksand_end[];
extern Gfx ssl_dl_pyramid_quicksand[];
extern s16 ssl_movtex_tris_pyramid_corners_quicksand[];
extern Gfx ssl_dl_pyramid_corners_quicksand[];
extern s16 ssl_movtex_tris_sides_quicksand[];
extern Gfx ssl_dl_sides_quicksand[];
extern s16 ttc_movtex_tris_big_surface_treadmill[];
extern Gfx ttc_dl_surface_treadmill_begin[];
extern Gfx ttc_dl_surface_treadmill_end[];
extern Gfx ttc_dl_surface_treadmill[];
extern s16 ttc_movtex_tris_small_surface_treadmill[];
extern s16 ssl_movtex_tris_quicksand_pit[];
extern Gfx ssl_dl_quicksand_pit_begin[];
extern Gfx ssl_dl_quicksand_pit_end[];
extern Gfx ssl_dl_quicksand_pit[];
extern s16 ssl_movtex_tris_pyramid_quicksand_pit[];
extern Gfx ssl_dl_pyramid_quicksand_pit_begin[];
extern Gfx ssl_dl_pyramid_quicksand_pit_end[];

/**
 * MovtexObjects that have no color attributes per vertex (though the mesh
 * as a whole can have a blend color).
 */
struct MovtexObject gMovtexNonColored[] = {
    // Inside the pyramid there is a sand pathway with the 5 secrets on it.
    // pathway_front is the highest 'sand fall', pathway_floor is the horizontal
    // sand stream and pathway_side is the lower 'sand fall'.
    { MOVTEX_PYRAMID_SAND_PATHWAY_FRONT, TEX_PYRAMID_SAND_SSL, 8,
      ssl_movtex_tris_pyramid_sand_pathway_front, ssl_dl_pyramid_sand_pathway_begin,
      ssl_dl_pyramid_sand_pathway_end, ssl_dl_pyramid_sand_pathway_front_end, 0xff, 0xff, 0xff, 0xff,
      LAYER_TRANSPARENT_INTER },
    { MOVTEX_PYRAMID_SAND_PATHWAY_FLOOR, TEX_PYRAMID_SAND_SSL, 8,
      ssl_movtex_tris_pyramid_sand_pathway_floor, ssl_dl_pyramid_sand_pathway_floor_begin,
      ssl_dl_pyramid_sand_pathway_floor_end, ssl_dl_pyramid_sand_pathway_front_end, 0xff, 0xff, 0xff,
      0xff, LAYER_OPAQUE_INTER },
    { MOVTEX_PYRAMID_SAND_PATHWAY_SIDE, TEX_PYRAMID_SAND_SSL, 6,
      ssl_movtex_tris_pyramid_sand_pathway_side, ssl_dl_pyramid_sand_pathway_begin,
      ssl_dl_pyramid_sand_pathway_end, ssl_dl_pyramid_sand_pathway_side_end, 0xff, 0xff, 0xff, 0xff,
      LAYER_TRANSPARENT_INTER },

    // The waterfall outside the castle
    { MOVTEX_CASTLE_WATERFALL, TEXTURE_WATER, 15, castle_grounds_movtex_tris_waterfall,
      dl_waterbox_rgba16_begin, dl_waterbox_end, castle_grounds_dl_waterfall, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT_INTER },

    // Bowser in the Fire Sea has lava at 3 heights, lava_floor is the lowest
    // and lava_second_section is the highest
    { MOVTEX_BITFS_LAVA_FIRST, TEXTURE_LAVA, 4, bitfs_movtex_tris_lava_first_section,
      dl_waterbox_rgba16_begin, dl_waterbox_end, bitfs_dl_lava_sections, 0xff, 0xff, 0xff, 0xff,
      LAYER_OPAQUE },
    { MOVTEX_BITFS_LAVA_SECOND, TEXTURE_LAVA, 4, bitfs_movtex_tris_lava_second_section,
      dl_waterbox_rgba16_begin, dl_waterbox_end, bitfs_dl_lava_sections, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT },
    { MOVTEX_BITFS_LAVA_FLOOR, TEXTURE_LAVA, 9, bitfs_movtex_tris_lava_floor, dl_waterbox_rgba16_begin,
      dl_waterbox_end, bitfs_dl_lava_floor, 0xff, 0xff, 0xff, 0xb4, LAYER_TRANSPARENT },

    // Lava floor in Lethal Lava Land and the lava fall in the volcano
    //! Note that the lava floor in the volcano is actually a quad.
    // The quad collection index LLL_MOVTEX_VOLCANO_FLOOR_LAVA is actually
    // 2 | MOVTEX_AREA_LLL, suggesting that the lava floor of LLL used to be a
    // quad too, with index 1.
    // It was probably too large however, resulting in overflowing texture
    // coordinates or other artifacts, so they converted it to a movtex
    // mesh with 9 vertices, subdividing the rectangle into 4 smaller ones.
    { MOVTEX_LLL_LAVA_FLOOR, TEXTURE_LAVA, 9, lll_movtex_tris_lava_floor, dl_waterbox_rgba16_begin,
      dl_waterbox_end, lll_dl_lava_floor, 0xff, 0xff, 0xff, 0xc8, LAYER_TRANSPARENT },
    { MOVTEX_VOLCANO_LAVA_FALL, TEXTURE_LAVA, 16, lll_movtex_tris_lavafall_volcano,
      dl_waterbox_rgba16_begin, dl_waterbox_end, lll_dl_lavafall_volcano, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT_INTER },

    // Cavern of the metal Cap has a waterfall source above the switch platform,
    // the stream, around the switch, and the waterfall that's the same as the one
    // outside the castle. They are all part of the same mesh.
    { MOVTEX_COTMC_WATER, TEXTURE_WATER, 14, cotmc_movtex_tris_water, cotmc_dl_water_begin,
      cotmc_dl_water_end, cotmc_dl_water, 0xff, 0xff, 0xff, 0xb4, LAYER_TRANSPARENT_INTER },

    // Tall Tall mountain has water going from the top to the bottom of the mountain.
    { MOVTEX_TTM_BEGIN_WATERFALL, TEXTURE_WATER, 6, ttm_movtex_tris_begin_waterfall,
      dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_waterfall, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT },
    { MOVTEX_TTM_END_WATERFALL, TEXTURE_WATER, 6, ttm_movtex_tris_end_waterfall,
      dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_waterfall, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT },
    { MOVTEX_TTM_BEGIN_PUDDLE_WATERFALL, TEXTURE_WATER, 4, ttm_movtex_tris_begin_puddle_waterfall,
      dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_bottom_waterfall, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT_INTER },
    { MOVTEX_TTM_END_PUDDLE_WATERFALL, TEXTURE_WATER, 4, ttm_movtex_tris_end_puddle_waterfall,
      dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_bottom_waterfall, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT_INTER },
    { MOVTEX_TTM_PUDDLE_WATERFALL, TEXTURE_WATER, 8, ttm_movtex_tris_puddle_waterfall,
      dl_waterbox_rgba16_begin, dl_waterbox_end, ttm_dl_puddle_waterfall, 0xff, 0xff, 0xff, 0xb4,
      LAYER_TRANSPARENT_INTER },
    { 0x00000000, 0x00000000, 0, NULL, NULL, NULL, NULL, 0x00, 0x00, 0x00, 0x00, 0x00000000 },
};

/**
 * MovtexObjects that have color attributes per vertex.
 */
struct MovtexObject gMovtexColored[] = {
    { MOVTEX_SSL_PYRAMID_SIDE, TEX_QUICKSAND_SSL, 12, ssl_movtex_tris_pyramid_quicksand,
      ssl_dl_quicksand_begin, ssl_dl_quicksand_end, ssl_dl_pyramid_quicksand, 0xff, 0xff, 0xff, 0xff,
      LAYER_OPAQUE },
    { MOVTEX_SSL_PYRAMID_CORNER, TEX_QUICKSAND_SSL, 16, ssl_movtex_tris_pyramid_corners_quicksand,
      ssl_dl_quicksand_begin, ssl_dl_quicksand_end, ssl_dl_pyramid_corners_quicksand, 0xff, 0xff, 0xff,
      0xff, LAYER_OPAQUE },
    { MOVTEX_SSL_COURSE_EDGE, TEX_QUICKSAND_SSL, 15, ssl_movtex_tris_sides_quicksand,
      ssl_dl_quicksand_begin, ssl_dl_quicksand_end, ssl_dl_sides_quicksand, 0xff, 0xff, 0xff, 0xff,
      LAYER_OPAQUE },
    { MOVTEX_TREADMILL_BIG, TEX_YELLOW_TRI_TTC, 12, ttc_movtex_tris_big_surface_treadmill,
      ttc_dl_surface_treadmill_begin, ttc_dl_surface_treadmill_end, ttc_dl_surface_treadmill, 0xff,
      0xff, 0xff, 0xff, LAYER_OPAQUE },
    { MOVTEX_TREADMILL_SMALL, TEX_YELLOW_TRI_TTC, 12, ttc_movtex_tris_small_surface_treadmill,
      ttc_dl_surface_treadmill_begin, ttc_dl_surface_treadmill_end, ttc_dl_surface_treadmill, 0xff,
      0xff, 0xff, 0xff, LAYER_OPAQUE },
    { 0x00000000, 0x00000000, 0, NULL, NULL, NULL, NULL, 0x00, 0x00, 0x00, 0x00, 0x00000000 },
};

/**
 * Treated identically to gMovtexColored.
 */
struct MovtexObject gMovtexColored2[] = {
    { MOVTEX_SSL_SAND_PIT_OUTSIDE, TEX_QUICKSAND_SSL, 8, ssl_movtex_tris_quicksand_pit,
      ssl_dl_quicksand_pit_begin, ssl_dl_quicksand_pit_end, ssl_dl_quicksand_pit, 0xff, 0xff, 0xff,
      0xff, LAYER_OPAQUE },
    { MOVTEX_SSL_SAND_PIT_PYRAMID, TEX_PYRAMID_SAND_SSL, 8, ssl_movtex_tris_pyramid_quicksand_pit,
      ssl_dl_pyramid_quicksand_pit_begin, ssl_dl_pyramid_quicksand_pit_end, ssl_dl_quicksand_pit, 0xff,
      0xff, 0xff, 0xff, LAYER_OPAQUE },
    { 0x00000000, 0x00000000, 0, NULL, NULL, NULL, NULL, 0x00, 0x00, 0x00, 0x00, 0x00000000 },
};

/**
 * Sets the initial water level in Wet-Dry World based on how high Mario
 * jumped into the painting.
 */
Gfx *geo_wdw_set_initial_water_level(s32 callContext, UNUSED struct GraphNode *node, UNUSED Mat4 mtx) {
    s32 i;
    UNUSED u8 unused[] = { 1, 0, 4, 0, 7, 0, 10, 0 };
    s16 wdwWaterHeight;

    // Why was this global variable needed when they could just check for GEO_CONTEXT_AREA_LOAD?
    if (callContext != GEO_CONTEXT_RENDER) {
        gWdwWaterLevelSet = FALSE;
    } else if (callContext == GEO_CONTEXT_RENDER && gEnvironmentRegions != NULL
               && !gWdwWaterLevelSet) {
        if (gPaintingMarioYEntry <= 1382.4) {
            wdwWaterHeight = 31;
        } else if (gPaintingMarioYEntry >= 1600.0) {
            wdwWaterHeight = 2816;
        } else {
            wdwWaterHeight = 1024;
        }
        for (i = 0; i < *gEnvironmentRegions; i++) {
            gEnvironmentRegions[i * 6 + 6] = wdwWaterHeight;
        }
        gWdwWaterLevelSet = TRUE;
    }
    return NULL;
}

/**
 * Update moving texture counters that determine when to update the coordinates.
 * Textures update when gMovtexCounterPrev != gMovtexCounter.
 * This ensures water / sand flow stops when the game pauses.
 */
Gfx *geo_movtex_pause_control(s32 callContext, UNUSED struct GraphNode *node, UNUSED Mat4 mtx) {
    if (callContext != GEO_CONTEXT_RENDER) {
        gMovtexCounterPrev = gAreaUpdateCounter - 1;
        gMovtexCounter = gAreaUpdateCounter;
    } else {
        gMovtexCounterPrev = gMovtexCounter;
        gMovtexCounter = gAreaUpdateCounter;
    }
    return NULL;
}

/**
 * Make a vertex that's part of a quad with rotating texture.
 * verts: array of RSP vertices
 * n: index in 'verts' where the vertex is written
 * x, y, z: position
 * rot: base rotation of the texture
 * rotOffset: gets added to base rotation
 * scale: how often the texture repeats, 1 = no repeat
 */
void movtex_make_quad_vertex(Vtx *verts, s32 index, s16 x, s16 y, s16 z, s16 rot, s16 rotOffset,
                             f32 scale, u8 alpha) {
    s16 s = 32.0 * (32.0 * scale - 1.0) * sins(rot + rotOffset);
    s16 t = 32.0 * (32.0 * scale - 1.0) * coss(rot + rotOffset);

    if (gMovtexVtxColor == MOVTEX_VTX_COLOR_YELLOW) {
        make_vertex(verts, index, x, y, z, s, t, 255, 255, 0, alpha);
    } else if (gMovtexVtxColor == MOVTEX_VTX_COLOR_RED) {
        make_vertex(verts, index, x, y, z, s, t, 255, 0, 0, alpha);
    } else {
        make_vertex(verts, index, x, y, z, s, t, 255, 255, 255, alpha);
    }
}

/**
 * Represents a single flat quad with a rotating texture
 * Stores x and z for 4 vertices, though it is often just a rectangle.
 * Does not store the y-position, since that can be dynamic for water levels.
 */
struct MovtexQuad {
    /// the current texture rotation in this quad
    s16 rot;
    /// gets added to rot every frame
    s16 rotspeed;
    /// the amount of times the texture repeats. 1 = no repeat.
    s16 scale;
    /// Coordinates of vertices
    s16 x1;
    s16 z1;
    s16 x2;
    s16 z2;
    s16 x3;
    s16 z3;
    s16 x4;
    s16 z4;
    s16 rotDir;    /// if 1, it rotates counter-clockwise
    s16 alpha;     /// opacity, 255 = fully opaque
    s16 textureId; /// texture id
};

/// Variable for a little optimization: only set the texture when it differs from the previous texture
s16 gMovetexLastTextureId;

/**
 * Generates and returns a display list for a single MovtexQuad at height y.
 */
Gfx *movtex_gen_from_quad(s16 y, struct MovtexQuad *quad) {
    s16 rot;
    s16 rotspeed = quad->rotspeed;
    s16 scale = quad->scale;
    s16 x1 = quad->x1;
    s16 z1 = quad->z1;
    s16 x2 = quad->x2;
    s16 z2 = quad->z2;
    s16 x3 = quad->x3;
    s16 z3 = quad->z3;
    s16 x4 = quad->x4;
    s16 z4 = quad->z4;
    s16 rotDir = quad->rotDir;
    s16 alpha = quad->alpha;
    s16 textureId = quad->textureId;
    Vtx *verts = alloc_display_list(4 * sizeof(*verts));
    Gfx *gfxHead;
    Gfx *gfx;

    if (textureId == gMovetexLastTextureId) {
        gfxHead = alloc_display_list(3 * sizeof(*gfxHead));
    } else {
        gfxHead = alloc_display_list(8 * sizeof(*gfxHead));
    }

    if (gfxHead == NULL || verts == NULL) {
        return NULL;
    }
    gfx = gfxHead;
    if (gMovtexCounter != gMovtexCounterPrev) {
        quad->rot += rotspeed;
    }
    rot = quad->rot;
    if (rotDir == ROTATE_CLOCKWISE) {
        movtex_make_quad_vertex(verts, 0, x1, y, z1, rot, 0, scale, alpha);
        movtex_make_quad_vertex(verts, 1, x2, y, z2, rot, 16384, scale, alpha);
        movtex_make_quad_vertex(verts, 2, x3, y, z3, rot, -32768, scale, alpha);
        movtex_make_quad_vertex(verts, 3, x4, y, z4, rot, -16384, scale, alpha);
    } else { // ROTATE_COUNTER_CLOCKWISE
        movtex_make_quad_vertex(verts, 0, x1, y, z1, rot, 0, scale, alpha);
        movtex_make_quad_vertex(verts, 1, x2, y, z2, rot, -16384, scale, alpha);
        movtex_make_quad_vertex(verts, 2, x3, y, z3, rot, -32768, scale, alpha);
        movtex_make_quad_vertex(verts, 3, x4, y, z4, rot, 16384, scale, alpha);
    }

    // Only add commands to change the texture when necessary
    if (textureId != gMovetexLastTextureId) {
        if (textureId == TEXTURE_MIST) { // an ia16 texture
            if (0) {
            }
            gLoadBlockTexture(gfx++, 32, 32, G_IM_FMT_IA, gMovtexIdToTexture[textureId]);
        } else { // any rgba16 texture
            gLoadBlockTexture(gfx++, 32, 32, G_IM_FMT_RGBA, gMovtexIdToTexture[textureId]);
            if (0) {
            }
        }
        gMovetexLastTextureId = textureId;
    }
    gSPVertex(gfx++, VIRTUAL_TO_PHYSICAL2(verts), 4, 0);
    gSPDisplayList(gfx++, dl_draw_quad_verts_0123);
    gSPEndDisplayList(gfx);
    return gfxHead;
}

/**
 * Generate a display list drawing an array of MoxtexQuad at height 'y'.
 * y: y position of the quads
 * quadArrSegmented: a segmented address to an array of s16. The first number
 * is the number of entries, followed by that number of MovtexQuad structs.
 */
Gfx *movtex_gen_from_quad_array(s16 y, void *quadArrSegmented) {
    s16 *quadArr = segmented_to_virtual(quadArrSegmented);
    s16 numLists = quadArr[0];
    Gfx *gfxHead = alloc_display_list((numLists + 1) * sizeof(*gfxHead));
    Gfx *gfx = gfxHead;
    Gfx *subList;
    s32 i;

    if (gfxHead == NULL) {
        return NULL;
    }
    for (i = 0; i < numLists; i++) {
        // quadArr is an array of s16, so sizeof(MovtexQuad) gets divided by 2
        subList = movtex_gen_from_quad(
            y, (struct MovtexQuad *) (&quadArr[i * (sizeof(struct MovtexQuad) / 2) + 1]));
        if (subList != NULL) {
            gSPDisplayList(gfx++, VIRTUAL_TO_PHYSICAL(subList));
        }
    }
    gSPEndDisplayList(gfx);
    return gfxHead;
}

/**
 * Generate the display list for a list of quads by searching through a collection
 * for a given id.
 * id: id of quad array to generate a list for
 * y: height at which the quads are drawn
 * movetexQuadsSegmented: segmented address to the MovtexQuadCollection array
 * that will be searched.
 */
Gfx *movtex_gen_quads_id(s16 id, s16 y, void *movetexQuadsSegmented) {
    struct MovtexQuadCollection *collection = segmented_to_virtual(movetexQuadsSegmented);
    s32 i = 0;

    while (collection[i].id != -1) {
        if (collection[i].id == id) {
            return movtex_gen_from_quad_array(y, collection[i].quadArraySegmented);
        }
        i++;
    }
    return NULL;
}

extern u8 bbh_movtex_merry_go_round_water_entrance[];
extern u8 bbh_movtex_merry_go_round_water_side[];
extern u8 ccm_movtex_penguin_puddle_water[];
extern u8 inside_castle_movtex_green_room_water[];
extern u8 inside_castle_movtex_moat_water[];
extern u8 hmc_movtex_dorrie_pool_water[];
extern u8 hmc_movtex_toxic_maze_mist[];
extern u8 ssl_movtex_puddle_water[];
extern u8 ssl_movtex_toxbox_quicksand_mist[];
extern u8 sl_movtex_water[];
extern u8 wdw_movtex_area1_water[];
extern u8 wdw_movtex_area2_water[];
extern u8 jrb_movtex_water[];
extern u8 jrb_movtex_intial_mist[];
extern u8 jrb_movtex_sinked_boat_water[];
extern u8 thi_movtex_area1_water[];
extern u8 thi_movtex_area2_water[];
extern u8 castle_grounds_movtex_water[];
extern u8 lll_movtex_volcano_floor_lava[];
extern u8 ddd_movtex_area1_water[];
extern u8 ddd_movtex_area2_water[];
extern u8 wf_movtex_water[];
extern u8 castle_courtyard_movtex_star_statue_water[];
extern u8 ttm_movtex_puddle[];

/**
 * Find the quadCollection for a given quad collection id.
 */
void *get_quad_collection_from_id(u32 id) {
    switch (id) {
        case BBH_MOVTEX_MERRY_GO_ROUND_WATER_ENTRANCE:
            return bbh_movtex_merry_go_round_water_entrance;
        case BBH_MOVTEX_MERRY_GO_ROUND_WATER_SIDE:
            return bbh_movtex_merry_go_round_water_side;
        case CCM_MOVTEX_PENGUIN_PUDDLE_WATER:
            return ccm_movtex_penguin_puddle_water;
        case INSIDE_CASTLE_MOVTEX_GREEN_ROOM_WATER:
            return inside_castle_movtex_green_room_water;
        case INSIDE_CASTLE_MOVTEX_MOAT_WATER:
            return inside_castle_movtex_moat_water;
        case HMC_MOVTEX_DORRIE_POOL_WATER:
            return hmc_movtex_dorrie_pool_water;
        case HMC_MOVTEX_TOXIC_MAZE_MIST:
            return hmc_movtex_toxic_maze_mist;
        case SSL_MOVTEX_PUDDLE_WATER:
            return ssl_movtex_puddle_water;
        case SSL_MOVTEX_TOXBOX_QUICKSAND_MIST:
            return ssl_movtex_toxbox_quicksand_mist;
        case SL_MOVTEX_WATER:
            return sl_movtex_water;
        case WDW_MOVTEX_AREA1_WATER:
            return wdw_movtex_area1_water;
        case WDW_MOVTEX_AREA2_WATER:
            return wdw_movtex_area2_water;
        case JRB_MOVTEX_WATER:
            return jrb_movtex_water;
        case JRB_MOVTEX_INTIAL_MIST:
            return jrb_movtex_intial_mist;
        case JRB_MOVTEX_SINKED_BOAT_WATER:
            return jrb_movtex_sinked_boat_water;
        case THI_MOVTEX_AREA1_WATER:
            return thi_movtex_area1_water;
        case THI_MOVTEX_AREA2_WATER:
            return thi_movtex_area2_water;
        case CASTLE_GROUNDS_MOVTEX_WATER:
            return castle_grounds_movtex_water;
        case LLL_MOVTEX_VOLCANO_FLOOR_LAVA:
            return lll_movtex_volcano_floor_lava;
        case DDD_MOVTEX_AREA1_WATER:
            return ddd_movtex_area1_water;
        case DDD_MOVTEX_AREA2_WATER:
            return ddd_movtex_area2_water;
        case WF_MOVTEX_WATER:
            return wf_movtex_water;
        case CASTLE_COURTYARD_MOVTEX_STAR_STATUE_WATER:
            return castle_courtyard_movtex_star_statue_water;
        case TTM_MOVTEX_PUDDLE:
            return ttm_movtex_puddle;
        default:
            return NULL;
    }
}

/**
 * Write to 'gfx' a command to set the current texture format for the given
 * quadCollection.
 */
void movtex_change_texture_format(u32 quadCollectionId, Gfx **gfx) {
    switch (quadCollectionId) {
        case HMC_MOVTEX_TOXIC_MAZE_MIST:
            gSPDisplayList((*gfx)++, dl_waterbox_ia16_begin);
            break;
        case SSL_MOVTEX_TOXBOX_QUICKSAND_MIST:
            gSPDisplayList((*gfx)++, dl_waterbox_ia16_begin);
            break;
        case JRB_MOVTEX_INTIAL_MIST:
            gSPDisplayList((*gfx)++, dl_waterbox_ia16_begin);
            break;
        default:
            gSPDisplayList((*gfx)++, dl_waterbox_rgba16_begin);
            break;
    }
}

/**
 * Geo script responsible for drawing quads with a moving texture at the height
 * of the corresponding water region. The node's parameter determines which quad
 * collection is drawn, see moving_texture.h.
 */
Gfx *geo_movtex_draw_water_regions(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    Gfx *gfxHead = NULL;
    Gfx *gfx = NULL;
    Gfx *subList;
    void *quadCollection;
    struct GraphNodeGenerated *asGenerated;
    s16 numWaterBoxes;
    s16 waterId;
    s16 waterY;
    s32 i;

    if (callContext == GEO_CONTEXT_RENDER) {
        gMovtexVtxColor = MOVTEX_VTX_COLOR_DEFAULT;
        if (gEnvironmentRegions == NULL) {
            return NULL;
        }
        numWaterBoxes = gEnvironmentRegions[0];
        gfxHead = alloc_display_list((numWaterBoxes + 3) * sizeof(*gfxHead));
        if (gfxHead == NULL) {
            return NULL;
        } else {
            gfx = gfxHead;
        }
        asGenerated = (struct GraphNodeGenerated *) node;
        if (asGenerated->parameter == JRB_MOVTEX_INTIAL_MIST) {
            if (gLakituState.goalPos[1] < 1024.0) { // if camera under water
                return NULL;
            }
            if (save_file_get_star_flags(gCurrSaveFileNum - 1, COURSE_JRB - 1) & 1) { // first star in JRB complete
                return NULL;
            }
        } else if (asGenerated->parameter == HMC_MOVTEX_TOXIC_MAZE_MIST) {
            gMovtexVtxColor = MOVTEX_VTX_COLOR_YELLOW;
        } else if (asGenerated->parameter == SSL_MOVTEX_TOXBOX_QUICKSAND_MIST) {
            gMovtexVtxColor = MOVTEX_VTX_COLOR_RED;
        }
        quadCollection = get_quad_collection_from_id(asGenerated->parameter);
        if (quadCollection == NULL) {
            return NULL;
        }

        asGenerated->fnNode.node.flags =
            (asGenerated->fnNode.node.flags & 0xFF) | (LAYER_TRANSPARENT_INTER << 8);

        movtex_change_texture_format(asGenerated->parameter, &gfx);
        gMovetexLastTextureId = -1;
        for (i = 0; i < numWaterBoxes; i++) {
            waterId = gEnvironmentRegions[i * 6 + 1];
            waterY = gEnvironmentRegions[i * 6 + 6];
            subList = movtex_gen_quads_id(waterId, waterY, quadCollection);
            if (subList != NULL)
                gSPDisplayList(gfx++, VIRTUAL_TO_PHYSICAL(subList));
        }
        gSPDisplayList(gfx++, dl_waterbox_end);
        gSPEndDisplayList(gfx);
    }
    return gfxHead;
}

/**
 * Updates a movtex mesh by adding the movtex's speed to the horizontal or
 * vertical texture coordinates depending on 'attr'.
 * movtexVerts: vertices to update
 * attr: which attribute to change
 */
void update_moving_texture_offset(s16 *movtexVerts, s32 attr) {
    s16 movSpeed = movtexVerts[MOVTEX_ATTR_SPEED];
    s16 *curOffset = movtexVerts + attr;

    if (gMovtexCounter != gMovtexCounterPrev) {
        *curOffset += movSpeed;
        // note that texture coordinates are 6.10 fixed point, so this does modulo 1
        if (*curOffset >= 1024) {
            *curOffset -= 1024;
        }
        if (*curOffset <= -1024) {
            *curOffset += 1024;
        }
    }
}

/**
 * Make the first vertex of a moving texture with index 0.
 * This vertex is the base of all vertices with index > 0, which use this
 * vertex's coordinates as base on which to apply offset.
 * The first vertex has offset 0 by definition, simplifying the calculations a bit.
 */
void movtex_write_vertex_first(Vtx *vtx, s16 *movtexVerts, struct MovtexObject *c, s8 attrLayout) {
    s16 x = movtexVerts[MOVTEX_ATTR_X];
    s16 y = movtexVerts[MOVTEX_ATTR_Y];
    s16 z = movtexVerts[MOVTEX_ATTR_Z];
    u8 alpha = c->a;
    u8 r1;
    u8 g1;
    u8 b1;
    s8 r2;
    s8 g2;
    s8 b2;
    s16 s;
    s16 t;

    switch (attrLayout) {
        case MOVTEX_LAYOUT_NOCOLOR:
            r1 = c->r;
            g1 = c->g;
            b1 = c->b;
            s = movtexVerts[MOVTEX_ATTR_NOCOLOR_S];
            t = movtexVerts[MOVTEX_ATTR_NOCOLOR_T];
            make_vertex(vtx, 0, x, y, z, s, t, r1, g1, b1, alpha);
            break;
        case MOVTEX_LAYOUT_COLORED:
            r2 = movtexVerts[MOVTEX_ATTR_COLORED_R];
            g2 = movtexVerts[MOVTEX_ATTR_COLORED_G];
            b2 = movtexVerts[MOVTEX_ATTR_COLORED_B];
            s = movtexVerts[MOVTEX_ATTR_COLORED_S];
            t = movtexVerts[MOVTEX_ATTR_COLORED_T];
            make_vertex(vtx, 0, x, y, z, s, t, r2, g2, b2, alpha);
            break;
    }
}

/**
 * Make a vertex with index > 0. The vertex with index 0 is made in
 * movtex_write_vertex_first and subsequent vertices use vertex 0 as a base
 * for their texture coordinates.
 */
void movtex_write_vertex_index(Vtx *verts, s32 index, s16 *movtexVerts, struct MovtexObject *d,
                               s8 attrLayout) {
    u8 alpha = d->a;
    s16 x;
    s16 y;
    s16 z;
    s16 baseS;
    s16 baseT;
    s16 s;
    s16 t;
    s16 offS;
    s16 offT;
    u8 r1;
    u8 g1;
    u8 b1;
    s8 r2;
    s8 g2;
    s8 b2;

    switch (attrLayout) {
        case MOVTEX_LAYOUT_NOCOLOR:
            x = movtexVerts[index * 5 + MOVTEX_ATTR_X];
            y = movtexVerts[index * 5 + MOVTEX_ATTR_Y];
            z = movtexVerts[index * 5 + MOVTEX_ATTR_Z];
            baseS = movtexVerts[MOVTEX_ATTR_NOCOLOR_S];
            baseT = movtexVerts[MOVTEX_ATTR_NOCOLOR_T];
            offS = movtexVerts[index * 5 + MOVTEX_ATTR_NOCOLOR_S];
            offT = movtexVerts[index * 5 + MOVTEX_ATTR_NOCOLOR_T];
            s = baseS + ((offS * 32) * 32U);
            t = baseT + ((offT * 32) * 32U);
            r1 = d->r;
            g1 = d->g;
            b1 = d->b;
            make_vertex(verts, index, x, y, z, s, t, r1, g1, b1, alpha);
            break;
        case MOVTEX_LAYOUT_COLORED:
            x = movtexVerts[index * 8 + MOVTEX_ATTR_X];
            y = movtexVerts[index * 8 + MOVTEX_ATTR_Y];
            z = movtexVerts[index * 8 + MOVTEX_ATTR_Z];
            baseS = movtexVerts[7];
            baseT = movtexVerts[8];
            offS = movtexVerts[index * 8 + 7];
            offT = movtexVerts[index * 8 + 8];
            s = baseS + ((offS * 32) * 32U);
            t = baseT + ((offT * 32) * 32U);
            r2 = movtexVerts[index * 8 + MOVTEX_ATTR_COLORED_R];
            g2 = movtexVerts[index * 8 + MOVTEX_ATTR_COLORED_G];
            b2 = movtexVerts[index * 8 + MOVTEX_ATTR_COLORED_B];
            make_vertex(verts, index, x, y, z, s, t, r2, g2, b2, alpha);
            break;
    }
}

/**
 * Generate a displaylist for a MovtexObject.
 * 'attrLayout' is one of MOVTEX_LAYOUT_NOCOLOR and MOVTEX_LAYOUT_COLORED.
 */
Gfx *movtex_gen_list(s16 *movtexVerts, struct MovtexObject *movtexList, s8 attrLayout) {
    Vtx *verts = alloc_display_list(movtexList->vtx_count * sizeof(*verts));
    Gfx *gfxHead = alloc_display_list(11 * sizeof(*gfxHead));
    Gfx *gfx = gfxHead;
    s32 i;

    if (verts == NULL || gfxHead == NULL) {
        return NULL;
    }

    movtex_write_vertex_first(verts, movtexVerts, movtexList, attrLayout);
    for (i = 1; i < movtexList->vtx_count; i++) {
        movtex_write_vertex_index(verts, i, movtexVerts, movtexList, attrLayout);
    }

    gSPDisplayList(gfx++, movtexList->beginDl);
    gLoadBlockTexture(gfx++, 32, 32, G_IM_FMT_RGBA, gMovtexIdToTexture[movtexList->textureId]);
    gSPVertex(gfx++, VIRTUAL_TO_PHYSICAL2(verts), movtexList->vtx_count, 0);
    gSPDisplayList(gfx++, movtexList->triDl);
    gSPDisplayList(gfx++, movtexList->endDl);
    gSPEndDisplayList(gfx);
    return gfxHead;
}

/**
 * Function for a geo node that draws a MovtexObject in the gMovtexNonColored list.
 */
Gfx *geo_movtex_draw_nocolor(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    s32 i;
    s16 *movtexVerts;
    struct GraphNodeGenerated *asGenerated;
    Gfx *gfx = NULL;

    if (callContext == GEO_CONTEXT_RENDER) {
        i = 0;
        asGenerated = (struct GraphNodeGenerated *) node;
        while (gMovtexNonColored[i].movtexVerts != 0) {
            if (gMovtexNonColored[i].geoId == asGenerated->parameter) {
                asGenerated->fnNode.node.flags =
                    (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexNonColored[i].layer << 8);
                movtexVerts = segmented_to_virtual(gMovtexNonColored[i].movtexVerts);
                update_moving_texture_offset(movtexVerts, MOVTEX_ATTR_NOCOLOR_S);
                gfx = movtex_gen_list(movtexVerts, &gMovtexNonColored[i],
                                      MOVTEX_LAYOUT_NOCOLOR); // no perVertex colors
                break;
            }
            i++;
        }
    }
    return gfx;
}

/**
 * Function for a geo node that draws a MovtexObject in the gMovtexColored list.
 */
Gfx *geo_movtex_draw_colored(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    s32 i;
    s16 *movtexVerts;
    struct GraphNodeGenerated *asGenerated;
    Gfx *gfx = NULL;

    if (callContext == GEO_CONTEXT_RENDER) {
        i = 0;
        asGenerated = (struct GraphNodeGenerated *) node;
        while (gMovtexColored[i].movtexVerts != 0) {
            if (gMovtexColored[i].geoId == asGenerated->parameter) {
                asGenerated->fnNode.node.flags =
                    (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexColored[i].layer << 8);
                movtexVerts = segmented_to_virtual(gMovtexColored[i].movtexVerts);
                update_moving_texture_offset(movtexVerts, MOVTEX_ATTR_COLORED_S);
                gfx = movtex_gen_list(movtexVerts, &gMovtexColored[i], MOVTEX_LAYOUT_COLORED);
                break;
            }
            i++;
        }
    }
    return gfx;
}

/**
 * Function for a geo node that draws a MovtexObject in the gMovtexColored list,
 * but it doesn't call update_moving_texture_offset since that happens in
 * geo_movtex_update_horizontal. This is for when a MovtexObject has multiple
 * instances (like TTC treadmills) so you don't want the animation speed to
 * increase the more instances there are.
 */
Gfx *geo_movtex_draw_colored_no_update(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    s32 i;
    s16 *movtexVerts;
    struct GraphNodeGenerated *asGenerated;
    Gfx *gfx = NULL;

    if (callContext == GEO_CONTEXT_RENDER) {
        i = 0;
        asGenerated = (struct GraphNodeGenerated *) node;
        while (gMovtexColored[i].movtexVerts != 0) {
            if (gMovtexColored[i].geoId == asGenerated->parameter) {
                asGenerated->fnNode.node.flags =
                    (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexColored[i].layer << 8);
                movtexVerts = segmented_to_virtual(gMovtexColored[i].movtexVerts);
                gfx = movtex_gen_list(movtexVerts, &gMovtexColored[i], MOVTEX_LAYOUT_COLORED);
                break;
            }
            i++;
        }
    }
    return gfx;
}

/**
 * Exact copy of geo_movtex_draw_colored_no_update, but now using the gMovtexColored2 array.
 * Used for the sand pits in SSL, both outside and inside the pyramid.
 */
Gfx *geo_movtex_draw_colored_2_no_update(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    s32 i;
    s16 *movtexVerts;
    struct GraphNodeGenerated *asGenerated;
    Gfx *gfx = NULL;

    if (callContext == GEO_CONTEXT_RENDER) {
        i = 0;
        asGenerated = (struct GraphNodeGenerated *) node;
        while (gMovtexColored2[i].movtexVerts != 0) {
            if (gMovtexColored2[i].geoId == asGenerated->parameter) {
                asGenerated->fnNode.node.flags =
                    (asGenerated->fnNode.node.flags & 0xFF) | (gMovtexColored2[i].layer << 8);
                movtexVerts = segmented_to_virtual(gMovtexColored2[i].movtexVerts);
                gfx = movtex_gen_list(movtexVerts, &gMovtexColored2[i], MOVTEX_LAYOUT_COLORED);
                break;
            }
            i++;
        }
    }
    return gfx;
}

/**
 * Make textures move horizontally by simply adding a number to the 's' texture coordinate.
 * Used for:
 * - treadmills in Tick Tock Clock
 * - sand pits outside and inside the pyramid in Shifting Sand
 * Note that the drawing for these happen in different nodes with functions
 * geo_movtex_draw_colored_no_update and geo_movtex_draw_colored_2_no_update.
 * Usually the updating happens in the same function that draws it, but in
 * these cases the same model has multiple instances, and you don't want the
 * model to update multiple times.
 * Note that the final TTC only has one big treadmill though.
 */
Gfx *geo_movtex_update_horizontal(s32 callContext, struct GraphNode *node, UNUSED Mat4 mtx) {
    void *movtexVerts;

    if (callContext == GEO_CONTEXT_RENDER) {
        struct GraphNodeGenerated *asGenerated = (struct GraphNodeGenerated *) node;

        switch (asGenerated->parameter) {
            case MOVTEX_SSL_SAND_PIT_OUTSIDE:
                movtexVerts = segmented_to_virtual(ssl_movtex_tris_quicksand_pit);
                break;
            case MOVTEX_SSL_SAND_PIT_PYRAMID:
                movtexVerts = segmented_to_virtual(ssl_movtex_tris_pyramid_quicksand_pit);
                break;
            case MOVTEX_TREADMILL_BIG:
                movtexVerts = segmented_to_virtual(ttc_movtex_tris_big_surface_treadmill);
                break;
            case MOVTEX_TREADMILL_SMALL:
                movtexVerts = segmented_to_virtual(ttc_movtex_tris_small_surface_treadmill);
                break;
        }
        update_moving_texture_offset(movtexVerts, MOVTEX_ATTR_COLORED_S);
    }
    return NULL;
}
