#include "ve2.h"
#include "texgen.h"
#include "camera.h"
#include "fbo.h"
#include "postfx.h"
#include "sync.h"

#define USE_GLOW 1

SCENE_HAS_PARAM(Magnet, FadeOut);

#define GRID_SIZE    32
#define GRID_SIZE_X  GRID_SIZE
#define GRID_SIZE_Y  (GRID_SIZE + (GRID_SIZE+2)/4 - (GRID_SIZE+8)/16)

#define PHYS_TIME_STEP    (1.0 / 600)
#define MAGNETISM         ((float)(10.0 * GRID_SIZE * PHYS_TIME_STEP))
#define MAGNET_SPEED      0.5f
#define MAGNET_ACCEL      0.0625f
#define MAGNET_RANGE      (0.4f * GRID_SIZE)
#define SPOT_SIZE         (GRID_SIZE / 8.0f)
#define SPOT_ATTENUATION  (1.0f * (float)PHYS_TIME_STEP)
#define DOT_SCALE         0.4f

#define CAMSWITCH_1_TIME   (POINT11-POINT10)
#define CAMSWITCH_1_DUR    4.0
#define CAMSWITCH_2_TIME   (POINT12-POINT10)
#define CAMSWITCH_2_DUR    2.0

typedef struct {
    float pos[2];
    float dir[2];
    float spot;
    float light;
} grid_point_t;

#define GRID_POINT_COUNT (GRID_SIZE_X * GRID_SIZE_Y)
static grid_point_t points[GRID_POINT_COUNT];

static float norm2(float *v) {
    float n = isqrtf(v[0]*v[0] + v[1]*v[1]);
    v[0] *= n;
    v[1] *= n;
    return n;
}

static double t_phys;
static GLuint texDot;


static void GetDotPos(float *pos, double t_raw) {
    float t = (float)t_raw;
    t *= 1.0f + t * MAGNET_ACCEL;
    t *= MAGNET_SPEED;
    pos[0] = MAGNET_RANGE * (
             0.654f * sinf(0.842f * t + 2.345f)
           + 0.321f * sinf(1.233f * t + 6.789f)
           + 0.025f * sinf(4.732f * t + 4.321f));
    pos[1] = MAGNET_RANGE * (
             0.543f * sinf(1.427f * t + 0.123f)
           + 0.210f * sinf(0.923f * t + 4.567f)
           + 0.247f * sinf(2.184f * t + 8.901f));
}


static void UpdateGrid(float *dot) {
    grid_point_t *p = points;
    int i;
    float d[2], r, n;
    for (i = GRID_POINT_COUNT;  i;  --i, ++p) {
        // compute distance vector
        d[0] = dot[0] - p->pos[0];
        d[1] = dot[1] - p->pos[1];

        // compute distance, normalize distance vector
        r = d[0]*d[0] + d[1]*d[1];
        #define MIN_DIST (1.0f / 65536)
        if (r < MIN_DIST) r = MIN_DIST;

        // compute weight and update distance vector
        n = MAGNETISM / (r*r) * isqrtf(r);
        p->dir[0] += d[0] * n;
        p->dir[1] += d[1] * n;
        norm2(p->dir);

        // update lightness
        r = sqrtf(r) * (1.0f / SPOT_SIZE);
        r = sqrtf(r);
        if (r >= 1.0f) r = 1.0f;
        n = 1.0f - r;
        r *= 1.0f - SPOT_ATTENUATION;
        p->light = r * p->light + n * 2.0f;
    }
}

static void DrawGrid(void) {
    grid_point_t *p = points;
    int i;
    float r, g, b, l, dx, dy;
    glBegin(GL_QUADS);
    for (i = GRID_POINT_COUNT;  i;  --i, ++p) {
        // set up color stuff
        l = p->light;
        r = 0.75f + 0.25f * p->dir[0];
        g = 0.75f + 0.25f * p->dir[1];
        if (l > 1.0f) {
            l -= 1.0f;
            b = 1.0f - l;
            r = b * r + l;
            g = b * g + l;
            b = 1.0f;
        } else
            b = 0.5f + 0.5f * l;
        l = p->spot * (float)FadeOut;
        glColor3f(r*l, g*l, b*l);

        // compute side vector and draw the thing
        dx = DOT_SCALE * (p->dir[0] + p->dir[1]);
        dy = DOT_SCALE * (p->dir[1] - p->dir[0]);
        glTexCoord2i(0,0);  glVertex2f(p->pos[0] + dx, p->pos[1] + dy);  
        glTexCoord2i(0,1);  glVertex2f(p->pos[0] + dy, p->pos[1] - dx); 
        glTexCoord2i(1,1);  glVertex2f(p->pos[0] - dx, p->pos[1] - dy);
        glTexCoord2i(1,0);  glVertex2f(p->pos[0] - dy, p->pos[1] + dx);  
    }
    glEnd();
}



void Prepare_Magnet(void) {
    int x, y;
    grid_point_t *p = points;

    // create grid texture
    TGimage *A, *B;
    A = tgCreate(256,256, TG_FORMAT_GRAY);
    tgGlow(A, 0.98, 0.99);
    B = tgCreateCompatible(A);
    tgGlow(B, 0.60, 0.61);
    tgBlend(A, B, TG_BLEND_MODE_SUBTRACT, 256);
    tgRect(A, 102,0, 51,128, 0);
    texDot = tgCompileGL(A, 1);
    tgFree(A);
    tgFree(B);
    SetAnisotropy(4);

    // create magnet grid
    randSeed(823475);
    for (y = 0;  y < GRID_SIZE_Y;  ++y) {
        float py = (y - (0.5f * GRID_SIZE_Y)) * 0.866025403784439f;
        float px = (-0.5f * GRID_SIZE_X + 0.25f) + ((y & 1) * 0.5f);
        for (x = GRID_SIZE_X;  x;  --x, ++p) {
            float r = sqrtf(px*px + py*py);
            p->pos[0] = px;
            p->pos[1] = py;
            p->dir[0] = (float)randFloatAroundZero(1.0);
            p->dir[1] = (float)randFloatAroundZero(1.0);
            norm2(p->dir);
            r = 1.0f - (r / (0.5f * GRID_SIZE));
            if (r < 0.0f) r = 0.0f;
            if (r > 1.0f) r = 1.0f;
            p->spot = sqrtf(r);
            p->light = 0.0f;
            px += 1.0f;
        }
    }
    UpdateProgress();

    FadeOut = 1.0;
}


void Init_Magnet(void) {
    SetPerspective(45.0, 0.1, 100.0);
    t_phys = 0.0f;
}


void Render_Magnet(void) {
    camera_t cam, cam_follow, cam_look, cam_overview;
    float curr[2], ref[2];

    UPDATE_PARAM(Magnet, FadeOut);
    if (FadeOut < 0.0) FadeOut = 0.0;

    // set up cameras
    GetDotPos(curr, t);
    GetDotPos(ref, t - 2.0);
    camera_load(&cam_follow,
        ref[0], ref[1], 10.0,
        curr[0], curr[1], 0.0,
        0.0, 0.0, 1.0
    );
    camera_load(&cam_look,
        -0.3 * GRID_SIZE_X, -0.3 * GRID_SIZE_Y, 10.0,
        0.3 * GRID_SIZE_X * sin(0.1 * t),
        0.3 * GRID_SIZE_X * cos(0.1 * t), -5.0,
        0.0, 0.0, 1.0
    );
    camera_load(&cam_overview,
        0.0, 0.0, 0.6 * (GRID_SIZE_X + GRID_SIZE_Y),
        0.0, 0.0, 0.0,
        0.0, 1.0, 0.0
    );
    if (t < CAMSWITCH_1_TIME)
        cam = cam_follow;
    else if (t < (CAMSWITCH_1_TIME + CAMSWITCH_1_DUR))
        camera_blend_smooth(&cam, &cam_follow, &cam_look, (t - CAMSWITCH_1_TIME) * (1.0 / CAMSWITCH_1_DUR));
    else if (t < CAMSWITCH_2_TIME)
        cam = cam_look;
    else if (t < (CAMSWITCH_2_TIME + CAMSWITCH_2_DUR))
        camera_blend_smooth(&cam, &cam_look, &cam_overview, (t - CAMSWITCH_2_TIME) * (1.0 / CAMSWITCH_2_DUR));
    else
        cam = cam_overview;

    // update physics
    while (t_phys < t) {
        float dot[2];
        GetDotPos(dot, t_phys);
        UpdateGrid(dot);
        t_phys += PHYS_TIME_STEP;
    }

    // main
#if USE_GLOW
    glsSet(GLS_ADDITIVE_BLEND);
    fboBeginDraw(ScreenBuffer, 0);
#endif
    glClear(GL_COLOR_BUFFER_BIT);
    glLoadIdentity();
    camera_apply(&cam);
    glsSet(GLS_ADDITIVE_BLEND | GLS_TEXTURE);
    glBindTexture(GL_TEXTURE_2D, texDot);
    DrawGrid();
#if USE_GLOW
    fboEndDraw();
    glsSet(0);
    postfxGlow(ScreenBuffer, 0, 1, 2, 1, -1, 3.75f, 5.0f);
#endif
}


void Uninit_Magnet(void) {
}


void Destroy_Magnet(void) {
}
