#include "despair.h"
#include "music.h"
#include <math.h>

#include "vga.h"
#include "jclib.h"
#include "sincos.h"
#include "llscreen.h"
#include "llkey.h"
#include "timer.h"

#define WIDTH  320
#define HEIGHT 200

#define NUM_ANGLES 4096
static float cos_table[NUM_ANGLES];
#define FCos(a) (cos_table[((word)(a))>>4])
#define FSin(a) (FCos((a)+16384))

#define EPSILON 1E-15

static byte *g_img;

static void (*OldTimer)(void);



// RGB 0..63
static void CalcPal(byte *pal, int r, int g, int b)
{
    int i;
    float dr, dg, db;
    float cr, cg, cb;

    cr = cg = cb = 0;
    dr = r / 63.0f;
    dg = g / 63.0f;
    db = b / 63.0f;

    for (i = 0; i < 64; i++)
    {
        pal[i*3+0] = (byte)cr;
        pal[i*3+1] = (byte)cg;
        pal[i*3+2] = (byte)cb;
        cr += dr;
        cg += dg;
        cb += db;
    }

    cr = (float)r;
    cg = (float)g;
    cb = (float)b;
    dr = (63 - r) / 63.0f;
    dg = (63 - g) / 63.0f;
    db = (63 - b) / 63.0f;

    for (i = 64; i < 128; i++)
    {
        pal[i*3+0] = (byte)cr;
        pal[i*3+1] = (byte)cg;
        pal[i*3+2] = (byte)cb;
        cr += dr;
        cg += dg;
        cb += db;
    }
}

#if 0
void Disturb(byte *img)
{
    byte *p = img;
    int i;

    for (i = 0; i < 16000; i++)
    {
        dword val = RND_GetNum();
        int j;

        for (j = 0; j < 4; j++)
        {
            if (*p <= 0x7F - 15)
            {
                *p += val & 15;
            }
            val >>= 8;
            p++;
        }
    }
}
#endif
extern void RenderBandScan(byte *dest, int np, int value);
//#pragma aux RenderBandScan parm [EDI] [ECX] [EBX] modify [EAX]

static void RenderHorizontalBand(float cy, float w)
{
    int s0, s1;
    byte *dest, *end;

    s0 = (int)(cy - w / 2);
    s1 = (int)(cy + w / 2);

    if (s0 >= HEIGHT || s1 < 0)
        return;
    if (s0 < 0)
        s0 = 0;
    if (s1 > HEIGHT)
        s1 = HEIGHT;

    dest = g_img + WIDTH * s0;
    end = g_img + WIDTH * s1;

    while (dest < end)
    {
        RenderBandScan(dest, WIDTH, 16*0x01010101);
        dest += WIDTH;
    }
}

struct line
{
    float x0, y0;
    float dx, dy;
};

static float IntersectWithYEqualK(struct line *p, float k)
{
    if (fabs(p->dy) < EPSILON)
        return 0;
    return p->x0 + (k - p->y0) * p->dx / p->dy;
}

static float IntersectWithXEqualK(struct line *p, float k)
{
    if (fabs(p->dx) < EPSILON)
        return 0;
    return p->y0 + (k - p->x0) * p->dy / p->dx;
}

static void RenderBand(
    float cx, float cy,
    float w,
    word angle
)
{
    float c, s, wx, wy, s0, s1, start_y;
    struct line l0, l1;

    angle &= 0x7FFF;

    if (angle < 20)
    {
        RenderHorizontalBand(cy, w);
        return;
    }

    // Calc line params
    c = FCos(angle);
    s = FSin(angle);

    wx = -s;
    wy = c;

    w /= 2;

    l0.x0 = cx - w * wx;
    l0.y0 = cy - w * wy;
    l0.dx = c;
    l0.dy = s;

    l1.x0 = cx + w * wx;
    l1.y0 = cy + w * wy;
    l1.dx = c;
    l1.dy = s;

    s0 = IntersectWithYEqualK(&l0, 0);
    s1 = IntersectWithYEqualK(&l1, 0);

    //REQUIRE(s0 < s1); THIS IS ALWAYS TRUE!!!!!

    if (s0 < WIDTH && s1 > 0)
    {
        start_y = 0;
    }
    else
    {
        if (s0 <= 0)
        {
            start_y = IntersectWithXEqualK(&l1, 0);
            s0 = IntersectWithYEqualK(&l0, start_y);
            s1 = 0;
        }
        else
        {
            start_y = IntersectWithXEqualK(&l0, WIDTH);
            s0 = WIDTH;
            s1 = IntersectWithYEqualK(&l1, start_y);
        }
    }

    if (start_y < 0 || start_y >= HEIGHT)
        return;

    {
        int start, end, y;
        byte *p;
        float dx0;
        float dx1;

        if (fabs(l0.dy) < EPSILON)
        {
            dx0 = 0;
        }
        else
        {
            dx0 = l0.dx / l0.dy;
        }

        if (fabs(l1.dy) < EPSILON)
        {
            dx1 = 0;
        }
        else
        {
            dx1 = l1.dx / l1.dy;
        }

        y = (int)start_y;
        p = g_img + y * WIDTH;

        // Render vertically
        for (y = (int)start_y; y < HEIGHT; y++)
        {
            start = (int)s0;
            end = (int)s1;
            if (end < 0)
                break;
            if (start > WIDTH)
                break;

            if (start < 0)
                start = 0;
            if (end > WIDTH)
                end = WIDTH;

            if (end - start > 0 && end - start <= WIDTH) RenderBandScan(p + start, end - start, 16 * 0x01010101);
            p += WIDTH;
            s0 += dx0;
            s1 += dx1;
        }

    }
}

struct thing
{
    float cx, cy;
    float vx, vy;
    float mass;
    word angle;
};

static int delta = 0;
static int ddelta = 4;

#define NUM_THINGS 16
static struct thing things[NUM_THINGS];

#define GRAVITY 1.0
#define SQ(a) ((a)*(a))

static int finish = 0;

static void AdvanceThings()
{
    struct thing *p = things;
    float ax, ay;
    int i, j;

    // Rotate
    for (i = 0; i < NUM_THINGS; i++)
    {
        p->angle += delta;
        p++;
    }
    p = things;

    for (i = 0; i < NUM_THINGS - 1; i++, p++)
    {
        struct thing *q = things;

        ax = ay = 0;
        for (j = 0; j < NUM_THINGS; j++, q++)
        {
            float f, fx, fy, r2, r, c, s, ir;

            if (i == j)
                continue;

            r2 = SQ(p->cx - q->cx) + SQ(p->cy - q->cy);
            if (r2 < 1000)
                r2 = 1000;
            f = (float) (GRAVITY * p->mass * q->mass / r2);

            r = (float)sqrt(r2);
            ir = 1 / r;

            c = (q->cx - p->cx) * ir;
            s = (q->cy - p->cy) * ir;

            f /= p->mass;

            fx = f * c;
            fy = f * s;

            ax += fx;
            ay += fy;
        }

        p->vx = p->vx + ax;
        p->vy = p->vy + ay;
        p->cx += p->vx;
        p->cy += p->vy;
    }

    delta += ddelta;
    if (ddelta > 0)
    {
        if (delta > 3072)
        {
            delta = 3072;
            ddelta = -4;
        }
    }
    else
    {
        if (delta < -3072)
        {
            delta = -3072;
            ddelta = 4;
        }
    }
}

static void RenderField(
    float cx, float cy,
    float w
)
{
    float x0, x1, y0, y1;
    int y, start, end;
    byte *p;

    w /= 2;

    x0 = cx - w;
    x1 = cx + w;
    y0 = cy - w;
    y1 = cy + w;

    if (x0 > WIDTH || x1 < 0 || y0 > HEIGHT || y0 < 0)
        return;

    if (x0 < 0)
        x0 = 0;
    if (y0 < 0)
        y0 = 0;
    if (x1 > WIDTH)
        x1 = WIDTH;
    if (y1 > HEIGHT)
        y1 = HEIGHT;

    if ((int)y0 >= (int) y1)
        return;

    if ((int)x0 >= (int) x1)
        return;

    start = (int)x0;
    end = (int)x1;
    p = g_img + WIDTH * (int)y0 + (int)x0;
    for (y = (int)y0; y < (int)y1; y++)
    {
        RenderBandScan(p, end - start, 0x10101010);
        p += WIDTH;
    }
}

static void RenderThings()
{
    struct thing *p = things;
    int i;

    for (i = 0; i < NUM_THINGS - 1; i++)
    {
        int cx, cy;

        cx = (int)p->cx; cy = (int)p->cy;
        if (cx >= 0 && cx < WIDTH && cy >= 0 && cy < HEIGHT)
            ;//g_img[cy * WIDTH + cx] = 127;

/*
        if (LLK_Keys[kF])
            RenderField(p->cx, p->cy, 80);
        else
*/
            RenderBand(p->cx, p->cy, 40, p->angle);
        p++;
    }
}

static dword lasttime;

bool DoSat(dword time)
{
    memset(g_img, 0, 64000);
    //VGA_PutColor(0, 63, 0, 0);
    RenderThings();
    //VGA_PutColor(0, 0, 63, 0);
    while (lasttime < time) {
        AdvanceThings();
        lasttime++;
    }
    //VGA_PutColor(0, 0, 0, 63);
//    PollMusic();   // Time up to 20 ticks (don't allow music to stop).
    //VGA_PutColor(0, 63, 63, 0);
    //VGA_PutColor(0, 0, 0, 0);
    if (!InShittyWinblows)
    {
//        _disable();
//        VGA_VSync();
//        _enable();
    }
    LLS_Update();

    return TRUE;
}

bool InitSat(dword time)
{
    int i;

    lasttime = time;
    g_img = LLS_Screen[0];

    for (i = 0; i < NUM_THINGS; i++)
    {
        things[i].cx = (float) (WIDTH/4 + (rand() % (WIDTH/2)));
        things[i].cy = (float) (HEIGHT/4 + (rand() % (HEIGHT/2)));
        //things[i].vx = (rand() / (float)RAND_MAX)/4.0;
        //things[i].vy = (rand() / (float)RAND_MAX)/4.0;
        things[i].vx = 0;
        things[i].vy = 0;
        things[i].mass = 1.0;
        things[i].angle = rand();
    }
    things[NUM_THINGS - 1].cx = WIDTH / 2;
    things[NUM_THINGS - 1].cy = HEIGHT / 2;
    things[NUM_THINGS - 1].vx = 0;
    things[NUM_THINGS - 1].vy = 0;
    things[NUM_THINGS - 1].mass = 80.0;
    things[NUM_THINGS - 1].angle = 0;

    CalcPal(DestPal, 63, 27, 7);
    for (i = 128; i < 256; i++)
    {
        DestPal[i*3] = DestPal[127*3];
        DestPal[i*3+1] = DestPal[127*3+1];
        DestPal[i*3+2] = DestPal[127*3+2];
    }

    VGA_DumpPalette((void*)DestPal, 0, 256);

    for (i = 0; i < NUM_ANGLES; i++)
    {
        cos_table[i] = (float)cos(2 * 3.1415926536 * i / NUM_ANGLES);
    }

//    OldTimer = TIMER_HookFunction;
//    TIMER_HookFunction = NULL;
//    PollMusic();   // Time up to 20 ticks (don't allow music to stop).

    CurFunction = DoSat;

    return TRUE;
}

bool EndSat(dword time)
{
//    TIMER_HookFunction = OldTimer;
    return TRUE;
}

bool BounceSat(dword time) {
    delta = -delta;
    ddelta = -ddelta;
    return TRUE;
}

extern bool SetUseSat(dword time) {
    CurFunction = DoSat;
    return TRUE;
}
