
#include "despair.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <math.h>

#include "base.h"
//#include <kbd.h>
//#include <vga.h>
#include "sincos.h"
#include "llscreen.h"
#include "pix.h"


#define rand() (RND_GetNum()%RAND_MAX)

#define CLOSEST 10

#define DEG_15    0xAAA
#define DEG_30   0x1555
#define DEG_45   0x2000
#define DEG_90   0x4000
#define DEG_180  0x8000
#define DEG_270  0xC000
#define DEG_360 0x10000

#define SINCOS_Mul FPMult

#define PI 3.1415926535
#define SINCOS_INT2RAD(x) (((double)(x))*PI/DEG_180)
#define SINCOS_RAD2INT(x) ((x) * DEG_180 / PI)


int xres, yres;
int tracing = 0;
byte *SQ_texture = NULL;
int global_dn = 0xC000 + 0x2000;

sdword zbuf[320];

//extern byte SQ_Phong[];

typedef struct
{
    sdword r;
    sdword u;
    word normal;
    word theta;
    byte filler[4];
} SQ_TPoint, *SQ_PPoint;

typedef struct
{
    word angle;
} SQ_TSide, *SQ_PSide;

typedef struct
{
    SQ_PPoint points;
    SQ_PSide sides;
    sdword numpoints;
    byte filler[4];
} SQ_TSlice, *SQ_PSlice;

SQ_PSide global_sides;
SQ_PPoint global_points;
SQ_PSlice global_slices;



#define SIDES 16
#define DEG_STEP (2*DEG_180/SIDES)

#define SLICES 200

// Generate a random slice
void SQ_GenSlices(void)
{
    unsigned int n, i;
    SQ_TPoint points[SIDES], points2[SIDES];
    SQ_TSide sides[SIDES];

    for (n = 0; n < SLICES; n++)
    {
        dword base_r = 100;

        base_r = n - (SLICES/2);
        base_r *= base_r;
        base_r = 120 * base_r / (SLICES*SLICES/4);
        base_r = 150 - base_r;
        base_r += SINCOS_Mul(Sin(0x10000UL * 3 * n/SLICES), 10);
        base_r = (long)(base_r / 1.5);

        // Asignar r, theta, u
        for (i = 0; i < SIDES; i++)
        {
            points2[i].r = (long) (base_r*(1 + (15*cos(4.7*i*2*PI/SIDES) + 10*cos(5.1*n*2*PI/SLICES) + 12*sin(4.9*i*2*PI/SIDES) + 5*sin(4.3*n*2*PI/SLICES))/60));
            points[i].theta = i * DEG_STEP ;//+ (dword)DEG_15 * rand()/RAND_MAX;
            points[i].u = points[i].theta;
        }


        // Suavizar r
        for (i = 0; i < SIDES; i++)
        {
            sdword r;

            r =     points2[(i-1) % SIDES].r
                + 2*points2[i].r
                +   points2[(i+1) % SIDES].r;
            points[i].r = points2[i].r;//r/4;
        }

        // Calcular  ngulos
        for (i = 0; i < SIDES; i++) {
            sdword x0, z0, x1, z1, dx, dz;
            double r0, r1, angle;
            word theta0, theta1;

            r0 = points[i].r;
            r1 = points[(i+1) % SIDES].r;
            theta0 = points[i].theta;
            theta1 = points[(i+1) % SIDES].theta;

            x0 = SINCOS_Mul((long)r0, (long)Cos(theta0));
            z0 = SINCOS_Mul((long)r0, (long)Sin(theta0));
            x1 = SINCOS_Mul((long)r0, (long)Cos(theta1));
            z1 = SINCOS_Mul((long)r0, (long)Sin(theta1));
            dx = x1 - x0;
            dz = z1 - z0;
            angle = atan2(dz, dx);

            sides[i].angle = (uint16) SINCOS_RAD2INT(angle);
        }

        // Generar normales
        for (i = 0; i < SIDES; i++) {
            points[i].normal
            = points[i].theta;//-DEG_90 + ((dword)sides[(i - 1) % SIDES].angle + sides[i].angle) / 2;
        }

        // Copiar
        memcpy(global_slices[n].points, points, sizeof(points));
        memcpy(global_slices[n].sides, sides, sizeof(sides));
        global_slices[n].numpoints = SIDES;
    }
}

//extern dword SQ_StartU, SQ_DeltaU, SQ_StartNormal, SQ_DeltaNormal;

static sdword IncZ;
static sdword IncU;

//ABPORT:Use C version instead!
extern void DumpScanASM(byte *dest, sdword *zb, sdword z, dword u, int n)
{
	#ifdef USE_ASM
  __asm
  {
     mov  edi, dest
     mov ebx, zb
     mov ecx, z
     mov eax, u
     mov edx, n
     PUSH EBP         
     LEA EDI,[EDI+EDX]
     NEG EDX          
     MOV EBP,EAX      
     MOV ESI,[IncZ]   
     MOV EAX,[EDI+EDX]
                      
  l1:                 
     MOV EAX,[EBX]    
     ADD EBX,4        
     CMP ECX,EAX      
     JLE c1         
     MOV ESI,[SQ_texture]
     MOV EAX,EBP      
     MOV [EBX-4],ECX  
     SHR EAX,8        
     MOV AL,[ESI+EAX] 
     MOV ESI,[IncZ]   
     MOV [EDI+EDX],AL 
  c1:               
     MOV EAX,[IncU]   
     ADD ECX,ESI      
     ADD EBP,EAX      
     INC EDX          
     JNZ l1         
     POP EBP          
  }
  #endif
}
/*#pragma aux DumpScanASM parm [EDI] [EBX] [ECX] [EAX] [EDX] modify [ESI] = \
"   PUSH EBP         " \
"   LEA EDI,[EDI+EDX]" \
"   NEG EDX          " \
"   MOV EBP,EAX      " \
"   MOV ESI,[IncZ]   " \
"   MOV EAX,[EDI+EDX] " \
"                    " \
"@@l1:               " \
"   MOV EAX,[EBX]    " \
"   ADD EBX,4        " \
"   CMP ECX,EAX      " \
"   JLE @@c1         " \
"   MOV ESI,[SQ_texture] " \
"   MOV EAX,EBP      " \
"   MOV [EBX-4],ECX  " \
"   SHR EAX,8        " \
"   MOV AL,[ESI+EAX] " \
"   MOV ESI,[IncZ]   " \
"   MOV [EDI+EDX],AL " \
"@@c1:               " \
"   MOV EAX,[IncU]   " \
"   ADD ECX,ESI      " \
"   ADD EBP,EAX      " \
"   INC EDX          " \
"   JNZ @@l1         " \
"   POP EBP          "
*/

void SQ_RenderScan(
    byte *scan,
    sdword x0, sdword x1,
    dword u0, dword u1,
    sdword z0, sdword z1
    )
{
    int cur_u, delta_u;
    sdword cur_z, delta_z;
    sdword *zb;

    if (x0 >= xres)
        return;
    if (x1 < 0)
        return;
    if (x0 < 0)
        x0 = 0;
    if (x1 >= xres)
        x1 = xres - 1;

    if (x0 >= x1)
        return;

    scan += x0;
    zb   = zbuf + x0;

    if (u1 < u0)
        u1 += 0x00010000;

    cur_u = u0;
    delta_u = (u1 - u0) / (x1 - x0);
    cur_z = z0 << 8;
    delta_z = ((z1 - z0)<<8) / (x1 - x0);

	//ABPORT: This code section was in the comments, it does the same as DumpScanASM
    for (; x0 < x1; x0++)
    {
        if (*zb < cur_z) {
            *zb = cur_z;
            *scan = SQ_texture[(byte)(cur_u >> 8)];
        }
        scan++;
        zb++;
        cur_u += delta_u;
        cur_z += delta_z;
    }

    IncU = delta_u;
    IncZ = delta_z;
    //DumpScanASM(scan, zb, cur_z, cur_u, x1-x0);
}

typedef struct
{
    sdword x, z;
    word normal;
    byte filler[6];
} TProjPoints;


void SQ_RenderSlice(SQ_PSlice pSlice, byte *scan, sdword dx, sdword dz, word angle)
{
    unsigned int i,
        vis_sides[SIDES], num_vissides = 0,
        vis_points[SIDES], num_vispoints = 0,
        nvispoint[SIDES];
    TProjPoints proj_points[SIDES];
    int xr2 = xres/2;

    memset(nvispoint, 0, sizeof(nvispoint));

    // Do the culling
    for (i = 0; i < SIDES; i++)
    {
        word result_ang;

        result_ang = pSlice->sides[i].angle + angle;
        if (result_ang > 0 || result_ang < DEG_180) {
            vis_sides[num_vissides++] = i;
            nvispoint[i]++;
            nvispoint[(i+1)%SIDES]++;
        }
    }

    //if (tracing) VGA_PutColor(0,0,0,63);

      // Marcar puntos visibles
/*
    for (i = 0; i < num_vissides; i++)
    {
        bool found;
        int point_to_mark;

        point_to_mark = vis_sides[i];
        found = 0;
        for (j = 0; j < num_vispoints; j++)
            if (vis_points[j] == point_to_mark)
            {
                found = 1;
                break;
            }
        if (!found)
            vis_points[num_vispoints++] = point_to_mark;

        point_to_mark = (vis_sides[i] + 1) % SIDES;
        found = 0;
        for (j = 0; j < num_vispoints; j++)
            if (vis_points[j] == point_to_mark)
            {
                found = 1;
                break;
            }
        if (!found)
            vis_points[num_vispoints++] = point_to_mark;
    }
*/
    for (i = 0; i < SIDES; i++)
        if (nvispoint > 0)
            vis_points[num_vispoints++] = i;

    //if (tracing) VGA_PutColor(0,32,32,32);
    // Proyectar puntos visibles
    for (i = 0; i < num_vispoints; i++)
    {
        SQ_PPoint ppoint;
        TProjPoints *projp;
        sdword x, z;

        ppoint = pSlice->points + vis_points[i];
        x = SINCOS_Mul(ppoint->r, Cos(ppoint->theta + angle)) + dx;
        z = SINCOS_Mul(ppoint->r, Sin(ppoint->theta + angle)) + dz;

        if (z < CLOSEST)
            z = CLOSEST;

        projp = proj_points + vis_points[i];
        projp->x = xr2 + x;//* 256 / z;
        projp->z = z;
        projp->normal = ppoint->normal + angle;
    }
    //if (tracing) VGA_PutColor(0,0,0,0);
    for (i = 0; i < num_vissides; i++)
    {
        int l, k;
        TProjPoints *pp0, *pp1;

        l = vis_sides[i];
        k = (l + 1) % SIDES;
        pp0 = proj_points + l;
        pp1 = proj_points + k;

        SQ_RenderScan(
            scan,
            pp0->x,
            pp1->x,
            pSlice->points[l].u,
            pSlice->points[k].u,
            pp0->z,
            pp1->z);
    }
}


#define pal DestPal

static byte *scr;
static byte *tex;
static byte *back;
static int dx = 0, dz = 400;

static struct {
    float angle;
    sint vel;
} sliceangles[SLICES];

static dword inittime;
static int lasttime = -1;

bool InitSq(dword time)
{
    unsigned int i;

    inittime = time;
    lasttime = time;

    dx = 0;
    dz = 400;

    xres = 320;
    yres = 200;

    scr = LLS_Screen[0];
    global_points = malloc(SIDES * SLICES * sizeof(SQ_TPoint));
    global_sides = malloc(SIDES * SLICES * sizeof(SQ_TSide));
    global_slices = malloc(SLICES * sizeof(SQ_TSlice));

    REQUIRE( (back = NEW(320*200)) != NULL);
    REQUIRE( (tex = NEW(256*256)) != NULL);

    PIX_Load("GFX\\sq.gif", tex, pal, NULL, NULL, FALSE);
    PIX_Load("GFX\\sqtback.gif", back, ScrapPal, NULL, NULL, FALSE);
    for (i = 0; i < 64000; i++)
        back[i] += 128;
    memcpy(pal + 128*3, ScrapPal, 128*3);

    EnhancePal();

    VGA_DumpPalette(pal, 0, 256);

    for (i = 0; i < SLICES; i++)
    {
        global_slices[i].points = global_points + i * SIDES;
        global_slices[i].sides  = global_sides  + i * SIDES;
        sliceangles[i].angle = 0;
    }

    SQ_GenSlices();

    CurFunction = DoSq;

    return TRUE;
}

bool EndSq(dword time)
{
    free(global_points);
    free(global_sides);
    free(global_slices);
    DISPOSE(tex);
    DISPOSE(back);

    CurFunction = NULL;

    return TRUE;
}

// 0 <= i < SLICES
static word window(int i)
{
    static float delta = -SLICES;
    static float ddelta = 0.05f;
    float fval = (float) (((float)(i + delta) / SLICES) * PI);

    delta += ddelta;

    if (fval < 0 || fval > PI)
        return 0;

    fval = (float) sin(fval); // + sin(i)/i;
    return (uint16)(5000 * fval * fval);
}

bool DoSq(dword time)
{
    int i, j;
    int kk;
    int deltatime;

    //if (tracing) VGA_PutColor(0,63,0,0);
    memcpy(scr, back, 320*200);
//    memset(scr, 0, xres*yres);

    time -= inittime;
    deltatime = time - lasttime;
    lasttime = time;
    if (deltatime <= 0)
        return TRUE;

    for (i = 0; i < yres; i++) {
        memset(zbuf, 128, sizeof(zbuf));
        SQ_texture = tex + 256*i;
        SQ_RenderSlice(global_slices + i, scr + xres * i, dx, dz, (uint16) sliceangles[i].angle);
    }

    for (j = 0; j < deltatime; j++) {
        for (kk = 3; kk > 0; kk--) {
            for (i = 0; i < yres-1; i++)
            {
                sliceangles[i].angle -= (sliceangles[i].angle - sliceangles[i+1].angle)/5;
                if (i < yres-2)
                    sliceangles[i].angle -= (sliceangles[i].angle - sliceangles[i+2].angle)/10;
                if (i < yres-3)
                    sliceangles[i].angle -= (sliceangles[i].angle - sliceangles[i+3].angle)/15;
                if (i < yres-4)
                    sliceangles[i].angle -= (sliceangles[i].angle - sliceangles[i+4].angle)/20;
            }
        }
    }
    sliceangles[SLICES-1].angle =  73424.0f*(float)sin(time*PI/415) +
                                   25424.0f*(float)cos(time*PI/525);

    LLS_Update();

    return TRUE;
}

extern bool BreakSq(dword time) {
    inittime -= 50;
    lasttime += 50;
    return TRUE;
}
