/* camerashake.cpp
 * Copyright (C) 2026 PakeMPC
 * This file is a Frei0r plugin.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */


#include <stdlib.h>
#include <string.h>
#if defined(_MSC_VER)
#define _USE_MATH_DEFINES
#endif
#include <math.h>

extern "C" {
    #include <frei0r.h>

    struct CameraShakeInstance {
        double amp_x, amp_y, rotation, zoom, speed, opacity, blur;
        float bg_r, bg_g, bg_b;
        unsigned int width, height;
    };

    int f0r_init(void) { return 1; }
    void f0r_deinit(void) {}

    void f0r_get_plugin_info(f0r_plugin_info_t* info) {
        info->name = "Camera Shake Ultimate";
        info->author = "PakeMPC";
        info->plugin_type = F0R_PLUGIN_TYPE_FILTER;
        info->color_model = F0R_COLOR_MODEL_RGBA8888;
        info->frei0r_version = FREI0R_MAJOR_VERSION;
        info->major_version = 2; info->minor_version = 0;
        info->num_params = 8;
        info->explanation = "Camera movement effect with rotation, opacity, and blur.";
    }

void f0r_get_param_info(f0r_param_info_t* info, int param_index) {
        if (param_index == 0) { info->name = "amplitude_x"; info->type = F0R_PARAM_DOUBLE; }
        else if (param_index == 1) { info->name = "amplitude_y"; info->type = F0R_PARAM_DOUBLE; }
        else if (param_index == 2) { info->name = "rotation"; info->type = F0R_PARAM_DOUBLE; }
        else if (param_index == 3) { info->name = "zoom"; info->type = F0R_PARAM_DOUBLE; }
        else if (param_index == 4) { info->name = "speed"; info->type = F0R_PARAM_DOUBLE; }
        else if (param_index == 5) { info->name = "opacity"; info->type = F0R_PARAM_DOUBLE; }
        else if (param_index == 6) { info->name = "blur"; info->type = F0R_PARAM_DOUBLE; }
        else if (param_index == 7) { info->name = "bg_color"; info->type = F0R_PARAM_COLOR; }
    }

    f0r_instance_t f0r_construct(unsigned int width, unsigned int height) {
        CameraShakeInstance* inst = new CameraShakeInstance();
        inst->amp_x = 50.0; inst->amp_y = 50.0; inst->rotation = 10.0;
        inst->zoom = 100.0; inst->speed = 50.0; inst->opacity = 100.0; inst->blur = 0.0;
        inst->bg_r = 0.0f; inst->bg_g = 0.0f; inst->bg_b = 0.0f;
        inst->width = width; inst->height = height;
        return (f0r_instance_t)inst;
    }

    void f0r_destruct(f0r_instance_t instance) { delete (CameraShakeInstance*)instance; }

    void f0r_set_param_value(f0r_instance_t instance, f0r_param_t param, int param_index) {
        CameraShakeInstance* inst = (CameraShakeInstance*)instance;
        double val = *((double*)param);
        if (param_index == 0) inst->amp_x = val;
        else if (param_index == 1) inst->amp_y = val;
        else if (param_index == 2) inst->rotation = val;
        else if (param_index == 3) inst->zoom = val;
        else if (param_index == 4) inst->speed = val;
        else if (param_index == 5) inst->opacity = val;
        else if (param_index == 6) inst->blur = val;
        else if (param_index == 7) {
            f0r_param_color_t* color = (f0r_param_color_t*)param;
            inst->bg_r = color->r; inst->bg_g = color->g; inst->bg_b = color->b;
        }
    }

void f0r_get_param_value(f0r_instance_t instance, f0r_param_t param, int param_index) {
        CameraShakeInstance* inst = (CameraShakeInstance*)instance;
        if (param_index == 0) *((double*)param) = inst->amp_x;
        else if (param_index == 1) *((double*)param) = inst->amp_y;
        else if (param_index == 2) *((double*)param) = inst->rotation;
        else if (param_index == 3) *((double*)param) = inst->zoom;
        else if (param_index == 4) *((double*)param) = inst->speed;
        else if (param_index == 5) *((double*)param) = inst->opacity;
        else if (param_index == 6) *((double*)param) = inst->blur;
        else if (param_index == 7) {
            f0r_param_color_t* color = (f0r_param_color_t*)param;
            color->r = inst->bg_r; color->g = inst->bg_g; color->b = inst->bg_b;
        }
    }

    void f0r_update(f0r_instance_t instance, double time, const uint32_t* inframe, uint32_t* outframe) {
        CameraShakeInstance* inst = (CameraShakeInstance*)instance;
        int w = inst->width;
        int h = inst->height;

        double speed_factor = inst->speed;
        
        // 1. Zoom (0 to 500) mapped to scale (0.001 to 5.0)
        double scale = inst->zoom / 100.0;
        if (scale < 0.001) scale = 0.001; // Avoid division by zero

        // 2. Shake X and Y (using the direct values ​​up to 500px)
        double shake_x = sin(time * speed_factor) * cos(time * speed_factor * 0.73) * inst->amp_x;
        double shake_y = sin(time * speed_factor * 0.89) * cos(time * speed_factor * 1.1) * inst->amp_y;

        // 3. Rotation
        double max_angle = (inst->rotation / 100.0) * (M_PI / 4.0); // Maximum 45 degree
        double theta = sin(time * speed_factor * 1.15) * max_angle;
        double cos_t = cos(-theta);
        double sin_t = sin(-theta);

        // 4. Blur and Opacity Parameters
        int blur_radius = (int)(inst->blur / 5.0); // Range of 0 to 20 pixels radius
        double alpha_mult = inst->opacity / 100.0;

        double cx = w / 2.0;
        double cy = h / 2.0;

        for (int y = 0; y < h; ++y) {
            for (int x = 0; x < w; ++x) {
                
                // Inverse mapping of rotation and scale matrix
                double dx = (x - cx) / scale;
                double dy = (y - cy) / scale;
                
                double rx = dx * cos_t - dy * sin_t;
                double ry = dx * sin_t + dy * cos_t;

                int base_src_x = (int)(cx + rx + shake_x);
                int base_src_y = (int)(cy + ry + shake_y);

                uint8_t* out_ptr = (uint8_t*)&outframe[y * w + x];

                // Check limits
                if (base_src_x < 0 || base_src_x >= w || base_src_y < 0 || base_src_y >= h) {
                    out_ptr[0] = (uint8_t)(inst->bg_r * 255.0f);
                    out_ptr[1] = (uint8_t)(inst->bg_g * 255.0f);
                    out_ptr[2] = (uint8_t)(inst->bg_b * 255.0f);
                    out_ptr[3] = (uint8_t)(255 * alpha_mult);
                    continue;
                }

                // If there's NO blur, paint directly
                if (blur_radius == 0) {
                    uint8_t* in_ptr = (uint8_t*)&inframe[base_src_y * w + base_src_x];
                    out_ptr[0] = in_ptr[0]; out_ptr[1] = in_ptr[1]; out_ptr[2] = in_ptr[2];
                    out_ptr[3] = (uint8_t)(in_ptr[3] * alpha_mult);
                } 
                // If there is blur, do a "Fast Cross-Blur"
                else {
                    int sum_r = 0, sum_g = 0, sum_b = 0, sum_a = 0;
                    int samples = 0;

                    // Horizontal and vertical sampling (cross shape for yield)
                    for (int i = -blur_radius; i <= blur_radius; i += 2) {
                        // Horizontal
                        int sx = base_src_x + i;
                        if (sx >= 0 && sx < w) {
                            uint8_t* p = (uint8_t*)&inframe[base_src_y * w + sx];
                            sum_r += p[0]; sum_g += p[1]; sum_b += p[2]; sum_a += p[3];
                            samples++;
                        }
                        // Vertical
                        int sy = base_src_y + i;
                        if (sy >= 0 && sy < h && i != 0) { // i!=0 to avoid adding the center twice
                            uint8_t* p = (uint8_t*)&inframe[sy * w + base_src_x];
                            sum_r += p[0]; sum_g += p[1]; sum_b += p[2]; sum_a += p[3];
                            samples++;
                        }
                    }

                    out_ptr[0] = sum_r / samples;
                    out_ptr[1] = sum_g / samples;
                    out_ptr[2] = sum_b / samples;
                    out_ptr[3] = (uint8_t)((sum_a / samples) * alpha_mult);
                }
            }
        }
    }
}
