// SPDX-License-Identifier: CC0-1.0
//
// SPDX-FileContributor: Antonio Niño Díaz, 2008-2024
//
// This file is part of Nitro Engine

#include <NEMain.h>

// Files autogenerated from bin files inside of the data and graphics folders.
#include "teapot_bin.h"
#include "teapot.h"

typedef struct {
    NE_Camera *Camera;
    NE_Model *ModelSpecular, *ModelDiffuse;

    bool wireframe;
} SceneData;

void Draw3DScene1(void *arg)
{
    SceneData *Scene = arg;

    NE_CameraUse(Scene->Camera);

    NE_PolyFormat(Scene->wireframe ? 0 : 31, 0, NE_LIGHT_ALL, NE_CULL_BACK, 0);

    NE_ModelDraw(Scene->ModelSpecular);

    printf("\x1b[22;0H"
           "Polygon count: %d   \n"
           "Vertex count: %d   ",
           NE_GetPolygonCount(),
           NE_GetVertexCount());
}

void Draw3DScene2(void *arg)
{
    SceneData *Scene = arg;

    NE_CameraUse(Scene->Camera);

    NE_PolyFormat(Scene->wireframe ? 0 : 31, 0, NE_LIGHT_ALL, NE_CULL_BACK, 0);

    NE_ModelDraw(Scene->ModelDiffuse);
}

int main(int argc, char *argv[])
{
    SceneData Scene = { 0 };

    irqEnable(IRQ_HBLANK);
    irqSet(IRQ_VBLANK, NE_VBLFunc);
    irqSet(IRQ_HBLANK, NE_HBLFunc);

    NE_InitDual3D();
    NE_InitConsole();

    // Allocate objects
    Scene.Camera = NE_CameraCreate();
    Scene.ModelSpecular = NE_ModelCreate(NE_Static);
    Scene.ModelDiffuse = NE_ModelCreate(NE_Static);
    NE_Material *MaterialSpecular = NE_MaterialCreate();
    NE_Material *MaterialDiffuse = NE_MaterialCreate();

    // Setup camera
    NE_CameraSet(Scene.Camera,
                 0, 0, 3,
                 0, 0, 0,
                 0, 1, 0);

    // Load model
    NE_ModelLoadStaticMesh(Scene.ModelSpecular, teapot_bin);
    NE_ModelClone(Scene.ModelDiffuse, Scene.ModelSpecular);

    // Load texture and clone it. The texture coordinates of the model are
    // outside of [0.0, 1.0], so it is needed to enable wrapping.
    NE_MaterialTexLoad(MaterialSpecular, NE_A1RGB5, 256, 256,
                       NE_TEXGEN_TEXCOORD | NE_TEXTURE_WRAP_S | NE_TEXTURE_WRAP_T,
                       teapotBitmap);
    NE_MaterialClone(MaterialSpecular, MaterialDiffuse);

    NE_ModelSetMaterial(Scene.ModelSpecular, MaterialSpecular);
    NE_ModelSetMaterial(Scene.ModelDiffuse, MaterialDiffuse);

    // Set some properties to the materials

    NE_MaterialSetProperties(MaterialSpecular,
                             RGB15(0, 0, 0),    // Diffuse
                             RGB15(0, 0, 0),    // Ambient
                             RGB15(31, 31, 31), // Specular
                             RGB15(0, 0, 0),    // Emission
                             false, true);      // Vtx color, use shininess table

    NE_MaterialSetProperties(MaterialDiffuse,
                             RGB15(31, 31, 31), // Diffuse
                             RGB15(0, 0, 0),    // Ambient
                             RGB15(0, 0, 0),    // Specular
                             RGB15(0, 0, 0),    // Emission
                             false, false);     // Vtx color, use shininess table

    // Set light color and direction
    NE_LightSet(0, NE_White, 0, 1, 0);
    NE_LightSet(1, NE_Blue, 0, -1, 0);
    NE_LightSet(2, NE_Red, 1, 0, 0);
    NE_LightSet(3, NE_Green, -1, 0, 0);

    NE_ShininessFunction shininess = NE_SHININESS_CUBIC;

    while (1)
    {
        NE_WaitForVBL(0);

        // Get keys information
        scanKeys();
        uint32_t keys = keysHeld();
        uint32_t keys_down = keysDown();

        printf("\x1b[0;0H"
               "PAD: Rotate\n"
               "A: Set wireframe mode\n"
               "L/R: Change shininess\n");

        if (keys & KEY_A)
            Scene.wireframe = true;
        else
            Scene.wireframe = false;

        // Rotate model
        if (keys & KEY_UP)
        {
            NE_ModelRotate(Scene.ModelSpecular, -2, 0, 0);
            NE_ModelRotate(Scene.ModelDiffuse, -2, 0, 0);
        }
        if (keys & KEY_DOWN)
        {
            NE_ModelRotate(Scene.ModelSpecular, 2, 0, 0);
            NE_ModelRotate(Scene.ModelDiffuse, 2, 0, 0);
        }
        if (keys & KEY_RIGHT)
        {
            NE_ModelRotate(Scene.ModelSpecular, 0, 2, 0);
            NE_ModelRotate(Scene.ModelDiffuse, 0, 2, 0);
        }
        if (keys & KEY_LEFT)
        {
            NE_ModelRotate(Scene.ModelSpecular, 0, -2, 0);
            NE_ModelRotate(Scene.ModelDiffuse, 0, -2, 0);
        }

        // Shininess table
        // ---------------

        const char *names[] = {
            [NE_SHININESS_NONE]      = "None     ",
            [NE_SHININESS_LINEAR]    = "Linear   ",
            [NE_SHININESS_QUADRATIC] = "Quadratic",
            [NE_SHININESS_CUBIC]     = "Cubic    ",
            [NE_SHININESS_QUARTIC]   = "Quartic  "
        };
        printf("\nShininess: %s", names[shininess]);

        if (keys_down & KEY_L)
        {
            if (shininess > NE_SHININESS_LINEAR)
                shininess--;
        }
        if (keys_down & KEY_R)
        {
            if (shininess < NE_SHININESS_QUARTIC)
                shininess++;
        }

        NE_ShininessTableGenerate(shininess);

        NE_ProcessDualArg(Draw3DScene1, Draw3DScene2, &Scene, &Scene);
    }

    return 0;
}
