#include <ai.h>
#include <string>
AI_SHADER_NODE_EXPORT_METHODS(apTextureRandom)

inline void fast_sincos(float x, float* sine, float* cosine)
{

}

inline void sincos(float x, float* sine, float* cosine)
{
    *sine = std::sin(x);
    *cosine = std::cos(x);
}

inline AtVector rotate(AtVector p, float angle, AtVector a, AtVector b)
{
    AtVector axis = AiV3Normalize(b - a);
    float cosang, sinang;
    sincos(angle, &cosang, &sinang);
    float cosang1 = 1.0 - cosang;
    float x = axis[0], y = axis[1], z = axis[2];
    AtMatrix matrix;
    matrix[0][0] = x * x + (1.0 - x * x) * cosang;
    matrix[0][1] = x * y * cosang1 + z * sinang;
    matrix[0][2] = x * z * cosang1 - y * sinang;
    matrix[0][3] = 0.0;

    matrix[1][0] = x * y * cosang1 - z * sinang;
    matrix[1][1] = y * y + (1.0 - y * y) * cosang;
    matrix[1][2] = y * z * cosang1 + x * sinang;
    matrix[1][3] = 0.0;

    matrix[2][0] = x * z * cosang1 + y * sinang;
    matrix[2][1] = y * z * cosang1 - x * sinang;
    matrix[2][2] = z * z + (1.0 - z * z) * cosang;
    matrix[2][3] = 0.0;

    matrix[3][0] = 0.0;
    matrix[3][1] = 0.0;
    matrix[3][2] = 0.0;
    matrix[3][3] = 1.0;   
    return AiM4VectorByMatrixMult(matrix, p-a) + a;
}

enum apTextureRandomParams
{
	p_default_color,
    p_input_mode,
    p_max_files,
    p_file_path,
    p_max_colors,
    p_color0,
    p_alpha0,
    p_color1,
    p_alpha1,
    p_color2,
    p_alpha2,
    p_color3,
    p_alpha3,
    p_background,
    p_background_alpha,
    p_scale,
    p_seed,
    p_probability,
    p_pos_random,
    p_scale_min,
    p_scale_max,
    p_uniform_scale,
    p_pixel_scale,
    p_rotate_min,
    p_rotate_max,
    p_rotate_steps,
    p_hsv_min,
    p_hsv_max,
    p_alpha_min,
    p_alpha_max,
    p_gamma_min,
    p_gamma_max,
    p_clamp,
    p_auto_gamma,
    p_manual_gamma,
    p_overlap
};

static const char* InputModeNames[] = {
	"Color",
	"File Path",
	NULL
};

struct ShaderData
{
    int input_mode;
    int max_files;
    std::string file_path;
    int file_count;
    int max_colors;
    float scale;
    int seed;
    AtVector probability;
    AtVector2 pos_random;
    AtVector2 scale_min;
    AtVector2 scale_max;
    bool uniform_scale;
    float pixel_scale;
    float rotate_min;
    float rotate_max;
    int rotate_steps;
    AtVector hsv_min;
    AtVector hsv_max;
    float alpha_min;
    float alpha_max;
    float gamma_min;
    float gamma_max;
    bool clamp;
    bool auto_gamma;
    float manual_gamma;
    int overlap;
};

node_parameters
{
    AiParameterRGB("default_color", 0, 0, 0);
    AiParameterEnum("input_mode", 0, InputModeNames);

    AiParameterInt("max_files", 0)
    AiParameterStr("file_path", "");

    AiParameterInt("max_colors", 4);
    AiParameterRGB("color0", 1.0, 0, 0);
    AiParameterFlt("alpha0", 1.0)
    AiParameterRGB("color1", 1.0, 1.0, 0);
    AiParameterFlt("alpha1", 1.0)
    AiParameterRGB("color2", 0.0, 0, 1.0);
    AiParameterFlt("alpha2", 1.0)
    AiParameterRGB("color3", 1.0, 0, 1.0);
    AiParameterFlt("alpha3", 1.0)

    AiParameterRGB("background", 0, 0, 0);
    AiParameterFlt("background_alpha", 1.0);
    AiParameterFlt("scale", 1.0);
    AiParameterInt("seed", 39);
    AiParameterVec("probability", 1.0, 0, 0.1);
    AiParameterVec2("pos_random", 0.5, 0.5);
    
    AiParameterVec2("scale_min", 1.0, 1.0);
    AiParameterVec2("scale_max", 1.0, 1.0);
    AiParameterBool("uniform_scale", true);
    AiParameterFlt("pixel_scale", 0.0);

    AiParameterFlt("rotate_min", 0);
    AiParameterFlt("rotate_max", 0);
    AiParameterInt("rotate_steps", 1);

    AiParameterVec("hsv_min", 0.0, 1.0, 1.0);
    AiParameterVec("hsv_max", 0.0, 1.0, 1.0);

    AiParameterFlt("alpha_min", 1.0);
    AiParameterFlt("alpha_max", 1.0);

    AiParameterFlt("gamma_min", 0.0);
    AiParameterFlt("gamma_max", 0.0);

    AiParameterBool("clamp", true);
    AiParameterBool("auto_gamma", true);
    AiParameterFlt("manual_gamma", 1.0);
    AiParameterInt("overlap", 1);
}

node_initialize
{
    ShaderData *data = (ShaderData *)AiMalloc(sizeof(ShaderData));
	AiNodeSetLocalData(node, data);
}

node_update
{
    ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);
    data->input_mode = AiNodeGetInt(node, "input_mode");
    data->file_path = AiNodeGetStr(node, "file_path").c_str();
    data->max_files = AiNodeGetInt(node, "max_files");
    data->max_colors = AiNodeGetInt(node, "max_colors");
    
    if(data->input_mode == 0 && data->max_colors < 1)
        data->max_colors = 1;        
    if(data->input_mode == 0 && data->max_colors > 4)
        data->max_colors = 4;

    data->scale = AiNodeGetFlt(node, "scale");
    data->overlap = AiNodeGetInt(node, "overlap");
    data->seed = AiNodeGetInt(node, "seed");
    data->probability = AiNodeGetVec(node, "probability");
    data->pos_random = AiNodeGetVec2(node, "pos_random");
    data->pixel_scale = AiNodeGetFlt(node, "pixel_scale");
    data->scale_min = AiNodeGetVec2(node, "scale_min");
    data->scale_max = AiNodeGetVec2(node, "scale_max");
    data->uniform_scale = AiNodeGetBool(node, "uniform_scale");
    data->rotate_min = AiNodeGetFlt(node, "rotate_min");
    data->rotate_max = AiNodeGetFlt(node, "rotate_max");
    data->rotate_steps = AiNodeGetInt(node, "rotate_steps");

}

node_finish
{
	ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);
	AiFree(data);
}

shader_evaluate
{
    ShaderData *data = (ShaderData *)AiNodeGetLocalData(node);
    float scale = AiShaderEvalParamFlt(p_scale);
    AtVector probability = AiShaderEvalParamVec(p_probability);

    float u = sg->u / scale;
    float v = sg->v / scale;

    int ix = int(floor(u));
    int iy = int(floor(v));
    float fx = u - ix;
    float fy = v - iy;
    int max_files = data->max_files;

    //if(data->num_layers > 0 && noise)
    int mod_value = (data->input_mode == 0) ? data->max_colors : data->max_files;

    float gamma = 1.0;
    AtRGB output;
    float alpha = 1.0;
    AtRGB background = AiShaderEvalParamRGB(p_background);
    float background_alpha = AiShaderEvalParamFlt(p_background_alpha);
    output = background * background_alpha;
    alpha = background_alpha;
    for(int xx = -data->overlap; xx <= data->overlap; xx++)
    {
        for(int yy = -data->overlap; yy <= data->overlap; yy++)
        {
            AtVector rnd_point(ix + xx, iy + yy, data->seed);
            // AtVector2 rnd_point(ix + xx, iy + yy);
            rnd_point[0] = rnd_point[0] < 0 ? rnd_point[0]-1 : rnd_point[0];
            rnd_point[1] = rnd_point[1] < 0 ? rnd_point[1]-1 : rnd_point[1];
            rnd_point[2] = rnd_point[2] < 0 ? rnd_point[2]-1 : rnd_point[2];
            
            // rnd_point[2] = rnd_point[2] < 0 ? rnd_point[2]-1 : rnd_point[2];
            AtVector pos = (AiVCellNoise4(rnd_point, 0) - AtVector(0.5, 0.5, 0)) * AtVector(data->pos_random[0], data->pos_random[1], 1.0);
            AtVector lp = AtVector(fx-xx, fy-yy, 0) - pos;

            float prob = probability[0] + AiPerlin3(rnd_point * probability[2]) * probability[1];

            if (pos[2] < prob)
            {
                AtVector scr = AiVCellNoise4(rnd_point, 1);
                int pic = int((AiCellNoise4(rnd_point, 15) * 100)) % mod_value;
                float sx = 1.0;
                float sy = 1.0;
                float scaleX = sx * AiLerp(scr[0], data->scale_min[0], data->scale_max[0]);
                float scaleY = sy * AiLerp(scr[data->uniform_scale ? 0 : 1], data->scale_min[1], data->scale_max[1]);
                if(data->rotate_steps > 1)
                {
                    scr[2] = floor(scr[2]* data->rotate_steps) / (data->rotate_steps - 1);
                }
                float rot = AiLerp(scr[2], data->rotate_min, data->rotate_max);
                lp -= 0.5;
                float radians = rot*(AI_PI/180);
                lp = rotate(lp, radians, AtVector(0, 0, 0), AtVector(0, 0, 1));
                lp /= AtVector(scaleX, scaleY, 1.0);
                lp += 0.5;
                if(lp.x >= 0 && lp.x <= 1.0 && lp.y >= 0 && lp.y <= 1.0)
                {
                    float cache_u = sg->u;
                    float cache_v = sg->v;

                    sg->u = lp.x;
                    sg->v = lp.y;

                    AtRGB rgb;
                    float a = 1.0;

                    if(pic == 0)
                    {    
                        rgb = AiShaderEvalParamRGB(p_color0);
                        a = AiShaderEvalParamFlt(p_alpha0);
                        rgb *= a;

                    }else if(pic == 1)
                    {
                        rgb = AiShaderEvalParamRGB(p_color1);
                        a = AiShaderEvalParamFlt(p_alpha1);
                        rgb *= a;

                    }else if(pic == 2)
                    {
                        rgb = AiShaderEvalParamRGB(p_color2);
                        a = AiShaderEvalParamFlt(p_alpha2);
                        rgb *= a;
                        
                    }else if(pic == 3)
                    {
                        rgb = AiShaderEvalParamRGB(p_color3);
                        a = AiShaderEvalParamFlt(p_alpha3);
                        rgb *= a;
                    }

                    if(a > 0.0)
                    {

                    }
                    
                    alpha = 1.0 - ((1.0 - alpha)*(1.0-a));
                    sg->u = cache_u;
                    sg->v = cache_v;

                    AtRGB result = output * (1.0 - a) + rgb;
                    sg->out.RGBA().r = result.r;
                    sg->out.RGBA().g = result.g;
                    sg->out.RGBA().b = result.b;
                    sg->out.RGBA().a = alpha;
                }
            }
        }
    }
}

node_loader
{
	if (i > 0)
		return false;

	node->methods = apTextureRandom;
	node->output_type = AI_TYPE_RGBA;
	node->name = "apTextureRandom";
	node->node_type = AI_NODE_SHADER;
	strcpy(node->version, AI_VERSION);
	return true;
}