#include "bella_sdk/bella_nodeapi.h"
#include "bella_sdk/bella_scene.h"

using namespace dl;
using namespace bella_sdk;

//=================================================================================================
// BND (Bella Node Definition) text
//=================================================================================================

static const Version s_libVersion = Version( 0, 0, 1, 0 );

DL_C_EXPORT const char* DL_STDCALL bndText()
{
    static const char* s_bndTextRaw = R"(
    {
        "libInfo": {
            "name":        "Bella Test Nodes",
            "author":      "Diffuse Logic",
            "description": "A test project showing how to implement nodes for Bella.",
            "url":         "diffuselogic.com",
            "prefix":      "test",
            "version":     "@LIB_VERSION@",
            "sdkVersion":  "@SDK_VERSION@",
            "svgIcon": "
                <svg width='288' height='288' viewBox='0 0 288.00 288.00'>
                  <g><rect x='32' y='32' fill='#FF9028' fill-opacity='1' stroke-width='0.2' stroke-linejoin='round' width='224' height='224'/></g>
                </svg>"
        },

        "testChecker": { // Note that this node demonstrates not inheriting any other nodes.
            "label": { "en": "Test Checker" },
            "inputs": {
                "color1": {
                    "type": "rgba",
                    "value": { "r": 0.1, "g": 0.2, "b": 0.3, "a": 1.0 }
                },
                "color2": {
                    "type": "rgba",
                    "value": { "r": 0.3, "g": 0.2, "b": 0.1, "a": 1.0 }
                },
                "factor": {
                    "type": "real",
                    "value": 1.0
                },
                "uvCoord": { "type":  "vec2" }
            },
            "outputs": {
                "outValue": { "type": "real" },
                "outColor": { "type": "rgba" }
            },
            "layout": [
                "color1",
                "color2",
                "factor",
                "uvCoord"
            ],
            "svgIcon": "
                <svg width='288' height='288' viewBox='0 0 288.00 288.00'>
                  <g><rect x='32' y='32' fill='#FF9028' fill-opacity='1' stroke-width='0.2' stroke-linejoin='round' width='224' height='224'/></g>
                </svg>"
        },

        "testChecker3d": {
            "bases": [ "texture3d" ], // This node inherits texture3d, so will automatically have projection support.
            "label": { "en": "Test Checker 3D" },
            "inputs": {
                "color1": {
                    "type": "rgba",
                    "value": { "r": 0.1, "g": 0.2, "b": 0.3, "a": 1.0 }
                },
                "color2": {
                    "type": "rgba",
                    "value": { "r": 0.3, "g": 0.2, "b": 0.1, "a": 1.0 }
                },
                "factor1": {
                    "type": "real",
                    "value": 0.5
                },
                "factor2": {
                    "type": "real",
                    "value": 1.0
                }
            },
            "outputs": {
                "outValue": { "type": "real" }
            },
            "layout": [
                "color1",
                "color2",
                "factor1",
                "factor2",
                {
                    "header": { "en": "Mapping" },
                    "expand": true,
                    "layout": [
                        "uvCoord",
                        "projection",
                        "space",
                        "xform"
                    ]
                }
            ],
            "svgIcon": "
                <svg width='288' height='288' viewBox='0 0 288.00 288.00'>
                  <g><ellipse fill='#FF9028' fill-opacity='1' stroke-width='0.2' stroke-linejoin='round' cx='144' cy='144' rx='112' ry='112'/></g>
                </svg>"
        }
    })";

    // The @LIB_VERSION@ is for your library's version, and the @SDK_VERSION@ comes from the SDK's
    // bella_sdk/api.cpp. This is necessary because Bella refuses to load nodelibs that are built
    // against newer SDKs than itself.

    static String s_bndTextFinal = String( s_bndTextRaw )
        .replace( "@LIB_VERSION@", s_libVersion.toString() )
        .replace( "@SDK_VERSION@", dl::bellaSdkVersion().toString() );
        
    return s_bndTextFinal.buf(); 
}

//=================================================================================================
// libInit/Free
//=================================================================================================

DL_C_EXPORT int DL_STDCALL libInit()
{
    logInfo( "libInit called for the test nodelib." );
    return 0;
}

DL_C_EXPORT int DL_STDCALL libFree()
{
    logInfo( "libFree called for the test nodelib." );
    return 0;
}

//=================================================================================================
// testChecker
//=================================================================================================

namespace {

#undef DL_ITERATE_ATTRS
#define DL_ITERATE_ATTRS(X) \
    X(I, Rgba, color1 ) \
    X(I, Rgba, color2 ) \
    X(I, Real, factor ) \
    X(I, Vec2, uvCoord ) \
    X(O, Real, outValue ) \
    X(O, Rgba, outColor )

struct TestChecker
{
    DL_ITERATE_ATTRS( DL_MAKE_ATTRS );
};

DL_C_EXPORT bool DL_STDCALL testCheckerInit( INode* inode, void** data )
{
    DL_INIT_NODEINFO( TestChecker );
    DL_ITERATE_ATTRS( DL_INIT_ATTRS );
    return true;
}

DL_C_EXPORT bool DL_STDCALL testCheckerFree( const INode* inode, void** data )
{
    DL_FREE_NODEINFO( TestChecker );
}

DL_C_EXPORT bool DL_STDCALL testCheckerPrep( INode* inode, void* data )
{
    DL_PREP_NODEINFO( TestChecker );
    DL_ITERATE_ATTRS( DL_PREP_ATTRS );
    return true;
}

DL_C_EXPORT bool DL_STDCALL testCheckerEval( EvalCtx* ctx, void* output )
{
    DL_EVAL_NODEINFO( TestChecker );
    DL_EVAL_IINFO( factor );

    if ( info->outValue.matches( ctx ) )
        return writeOutput( output, factor );

    if ( info->outColor.matches( ctx ) )
    {
        DL_EVAL_IINFO( color1 );
        DL_EVAL_IINFO( color2 );
        DL_EVAL_IINFO( uvCoord );

        auto choice = ( ( uvCoord.v - floor( uvCoord.v ) ) < 0.5 ) ^
                      ( ( uvCoord.u - floor( uvCoord.u ) ) < 0.5 );

        auto rgba = ( choice & 1 ) ? color2 : color1;
        rgba.rgb = ( Rgb )( rgba.v3 * factor );

        return writeOutput( output, rgba );
    }

    return false; // Someone requested an output we don't have.
}

} // testChecker

//=================================================================================================
// testChecker3d
//=================================================================================================

namespace {

#undef DL_ITERATE_ATTRS
#define DL_ITERATE_ATTRS(X) \
    X(I, Rgba, color1 ) \
    X(I, Rgba, color2 ) \
    X(I, Real, factor1 ) \
    X(I, Real, factor2 ) \
    X(O, Real, outValue )

struct TestChecker3d
{
    Texture3D texture3d;
    DL_ITERATE_ATTRS( DL_MAKE_ATTRS );
};

DL_C_EXPORT bool DL_STDCALL testChecker3dInit( INode* inode, void** data )
{
    DL_INIT_NODEINFO( TestChecker3d );
    DL_ITERATE_ATTRS( DL_INIT_ATTRS );
    DL_ITERATE_TEXTURE_ATTRS( DL_INIT_ATTRS, texture3d.texture );
    DL_ITERATE_TEXTURE3D_ATTRS( DL_INIT_ATTRS, texture3d );
    return true;
}

DL_C_EXPORT bool DL_STDCALL testChecker3dFree( const INode* inode, void** data )
{
    DL_FREE_NODEINFO( TestChecker3d );
}

DL_C_EXPORT bool DL_STDCALL testChecker3dPrep( INode* inode, void* data )
{
    DL_PREP_NODEINFO( TestChecker3d );
    DL_ITERATE_ATTRS( DL_PREP_ATTRS );
    DL_ITERATE_TEXTURE_ATTRS( DL_PREP_ATTRS, texture3d.texture );
    DL_ITERATE_TEXTURE3D_ATTRS( DL_PREP_ATTRS, texture3d );
    return true;
}

DL_C_EXPORT bool DL_STDCALL testChecker3dEval( EvalCtx* ctx, void* output )
{
    DL_EVAL_NODEINFO( TestChecker3d );

    // First just evaluate these, in case outValue has been requested.

    DL_EVAL_IINFO( factor1 );
    DL_EVAL_IINFO( factor2 );

    factor1 *= factor2;

    if ( info->outValue.matches( ctx ) )
        return writeOutput( output, factor1 );

    // It is some other output, so now we will evaluate color1 and color2.

    DL_EVAL_IINFO( color1 );
    DL_EVAL_IINFO( color2 );

    auto uvCoord = info->texture3d.texture.uvCoord.eval( ctx );
    auto choice = ( ( uvCoord.v - floor( uvCoord.v ) ) < 0.5 ) ^
                  ( ( uvCoord.u - floor( uvCoord.u ) ) < 0.5 );

    auto rgba = ( choice & 1 ) ? color2 : color1;
    rgba.rgb = ( Rgb )( rgba.v3 * factor1 );

    // We only handle outValue and otherwise delegate to the base texture.

    return writeTexture( ctx, output, info->texture3d.texture, rgba );
}

} // testChecker3d
