#include <stdio.h>
#include <math.h>
#include <allegro.h>
#include <conio.h>
#include <stdbool.h>

#define try bool __HadError=false;
#define catch(x) ExitJmp:if(__HadError)
#define throw(x) __HadError=true;goto ExitJmp;
#include "INCSTR.H"
extern int STRMDAT_ARR[];
struct vec2 {
   float x;
   float y;
};
struct vec3 {
   float x;
   float y;
   float z;
};
struct vec4 {
   float x;
   float y;
   float z;
   float w;
};

#define DEG(n)    ((n) * 180.0 / M_PI)
#define degreesToRadians(angleDegrees) ((angleDegrees) * M_PI / 180.0)
#define radiansToDegrees(angleRadians) ((angleRadians) * 180.0 / M_PI)
#define FPS_INT 2
float curtime;
void draw_tri(BITMAP *bmp, MATRIX_f *camera, float x0, float y0, float z0, float x1, float y1, float z1, float x2, float y2, float z2, float u0, float v0, float u1, float v1, float u2, float v2,  BITMAP *tex);
void draw_tri_ACCURATE(BITMAP *bmp, MATRIX_f *camera, float x0, float y0, float z0, float x1, float y1, float z1, float x2, float y2, float z2, float u0, float v0, float u1, float v1, float u2, float v2,  BITMAP *tex);
int viewport_w = 320;
int viewport_h = 200;
int fov = 44;
float aspect = 1.33f;
float xpos = 0;
float ypos = -2;
float zpos = -4;
float heading = 0;
float pitch = 0;
float roll = 0;
int fps;
int framecount;
BITMAP *textures[TEX_NUM];
float xfront, yfront, zfront;
float xup, yup, zup;
long dynamicPointer = 0;

BITMAP *buffer;
ZBUFFER *zbuf;
SAMPLE *sample;
PALETTE pal;
int c, w, h, bpp;
RGB_MAP rgb_table;
COLOR_MAP light_table;
COLOR_MAP trans_table;
PALETTE palette;

int imin(int a, int b);
int imax(int a, int b);
float min(float a, float b);
float max(float a, float b);

void rotateX( float *x, float *y, float *z, float angle) {
   float _x=*x;
   float _y=*y;
   float _z=*z;
   angle = fmod(angle, 360.0f);
   *x = _x;
   *y = _y*cos(angle) - _z*sin(angle);
   *z = _y*sin(angle) + _z*cos(angle);
} 
void rotateY( float *x, float *y, float *z, float angle) {
   float _x=*x;
   float _y=*y;
   float _z=*z;
   angle = fmod(angle, 360.0f);
   //angle = -angle;
   *x = _z*sin(angle) + _x*cos(angle);
   *y = _y;
   *z = _z*cos(angle) - _x*sin(angle);
} 
void rotateZ( float *x, float *y, float *z, float angle) {
   float _x=*x;
   float _y=*y;
   float _z=*z;
   angle = fmod(angle, 360.0f);
   angle = -angle;
   *x = _x*cos(angle) - _y*sin(angle);
   *y = _x*sin(angle) + _y*cos(angle);
   *z = _z;
}

void draw_tri(
   BITMAP *bmp, 
   MATRIX_f *camera, 
   float x0,
   float y0,
   float z0,
   float u0,
   float v0,
   float x1,
   float y1,
   float z1,
   float u1,
   float v1,
   float x2,
   float y2,
   float z2,
   float u2,
   float v2,
   BITMAP *tex) {
      V3D_f _v[3], _vout[6], _vtmp[6];
      V3D_f *v[3], *vout[6], *vtmp[6];
      int flags[3], out[6];
      int c, vc;

      for (c=0; c<3; c++)
         v[c] = &_v[c];

      for (c=0; c<6; c++) {
         vout[c] = &_vout[c];
         vtmp[c] = &_vtmp[c];
      }

      v[0]->x = x0;
      v[0]->y = y0;
      v[0]->z = z0;
      v[0]->u = u0;
      v[0]->v = v0;

      v[1]->x = x1;
      v[1]->y = y1;
      v[1]->z = z1;
      v[1]->u = u1;
      v[1]->v = v1;

      v[2]->x = x2;
      v[2]->y = y2;
      v[2]->z = z2;
      v[2]->u = u2;
      v[2]->v = v2;

      for (c=0; c<3; c++) {
         apply_matrix_f(camera, v[c]->x, v[c]->y, v[c]->z,
            &v[c]->x, &v[c]->y, &v[c]->z);

         flags[c] = 0;
         if (v[c]->x < -v[c]->z) {
            flags[c] |= 1;
         }
         else if (v[c]->x > v[c]->z) {
            flags[c] |= 2;
         }

         if (v[c]->y < -v[c]->z) {
            flags[c] |= 4;
         }
         else if (v[c]->y > v[c]->z) {
            flags[c] |= 8;
         }

         if (v[c]->z < 0.001) {
            flags[c] |= 16;
         }
               
      }

      if (flags[0]>0 && flags[1]>0 && flags[2]>0) {
         return;
      }

      if (flags[0]>0 || flags[1]>0 || flags[2]>0) {
         vc = clip3d_f(POLYTYPE_ATEX_MASK, 0.1, 0.1 , 3, (AL_CONST V3D_f **)v, vout, vtmp, out);

         if (vc <= 2) {
            return;
         }
      }

      vout[0] = v[0];
      vout[1] = v[1];
      vout[2] = v[2];

      vc = 3;
      
      for (c=0; c<vc; c++) {
         persp_project_f(vout[c]->x, vout[c]->y, vout[c]->z, &vout[c]->x, &vout[c]->y);
         //vout[c]->z = min(max(vout[c]->y, 0.1f), 100.0f);
      }

      polygon3d_f(bmp, POLYTYPE_ATEX_MASK | POLYTYPE_ZBUF, tex, vc, vout);
}
void draw_trifast(
   BITMAP *bmp, 
   MATRIX_f *camera, 
   float x0,
   float y0,
   float z0,
   float u0,
   float v0,
   float x1,
   float y1,
   float z1,
   float u1,
   float v1,
   float x2,
   float y2,
   float z2,
   float u2,
   float v2,
   BITMAP *tex) {
      V3D_f _v[3], _vout[6], _vtmp[6];
      V3D_f *v[3], *vout[6], *vtmp[6];
      int flags[3], out[6];
      int c, vc;

      for (c=0; c<3; c++)
         v[c] = &_v[c];

      for (c=0; c<6; c++) {
         vout[c] = &_vout[c];
         vtmp[c] = &_vtmp[c];
      }

      v[0]->x = x0;
      v[0]->y = y0;
      v[0]->z = z0;
      v[0]->u = u0;
      v[0]->v = v0;

      v[1]->x = x1;
      v[1]->y = y1;
      v[1]->z = z1;
      v[1]->u = u1;
      v[1]->v = v1;

      v[2]->x = x2;
      v[2]->y = y2;
      v[2]->z = z2;
      v[2]->u = u2;
      v[2]->v = v2;

      for (c=0; c<3; c++) {
         apply_matrix_f(camera, v[c]->x, v[c]->y, v[c]->z,
            &v[c]->x, &v[c]->y, &v[c]->z);

         flags[c] = 0;
         if (v[c]->x < -v[c]->z) {
            flags[c] |= 1;
         }
         else if (v[c]->x > v[c]->z) {
            flags[c] |= 2;
         }

         if (v[c]->y < -v[c]->z) {
            flags[c] |= 4;
         }
         else if (v[c]->y > v[c]->z) {
            flags[c] |= 8;
         }

         if (v[c]->z < 0.001) {
            flags[c] |= 16;
         }
               
      }

      if (flags[0]>0 && flags[1]>0 && flags[2]>0) {
         return;
      }

      if (flags[0]>0 || flags[1]>0 || flags[2]>0) {
         vc = clip3d_f(POLYTYPE_ATEX, 0.1, 0.1 , 3, (AL_CONST V3D_f **)v, vout, vtmp, out);

         if (vc <= 2) {
            return;
         }
      }

      vout[0] = v[0];
      vout[1] = v[1];
      vout[2] = v[2];

      vc = 3;
      
      for (c=0; c<vc; c++) {
         persp_project_f(vout[c]->x, vout[c]->y, vout[c]->z, &vout[c]->x, &vout[c]->y);
         //vout[c]->z = min(max(vout[c]->y, 0.1f), 100.0f);
      }

      polygon3d_f(bmp, POLYTYPE_ATEX | POLYTYPE_ZBUF, tex, vc, vout);
}
void dynamicData(float c) {
   float x,y,z,r,g,b,textureId,lookatx,lookaty,lookatz,u,v,historyTime,len;
   while(1) {
      if(DYMAMIC_STR_ARR[dynamicPointer+0] > curtime) {
            xpos = -DYMAMIC_STR_ARR[dynamicPointer+1];
            ypos = -DYMAMIC_STR_ARR[dynamicPointer+2];
            zpos = DYMAMIC_STR_ARR[dynamicPointer+3];
            lookatx = -DYMAMIC_STR_ARR[dynamicPointer+4];
            lookaty = -DYMAMIC_STR_ARR[dynamicPointer+5];
            lookatz = DYMAMIC_STR_ARR[dynamicPointer+6];
            xfront = -(lookatx - xpos);
            yfront = lookaty - ypos;
            zfront = lookatz - zpos;
            normalize_vector_f( &xfront, &yfront, &zfront );
            xup = DYMAMIC_STR_ARR[dynamicPointer+7];
            yup = DYMAMIC_STR_ARR[dynamicPointer+8];
            zup = DYMAMIC_STR_ARR[dynamicPointer+9];
            /*
            textprintf_ex(buffer, font, 0, 8*0, makecol(255,255,255), -1, "xpos: %f", xpos);
            textprintf_ex(buffer, font, 0, 8*1, makecol(255,255,255), -1, "xpos: %f", ypos);
            textprintf_ex(buffer, font, 0, 8*2, makecol(255,255,255), -1, "xpos: %f", zpos);
            textprintf_ex(buffer, font, 0, 8*3, makecol(255,255,255), -1, "lookatx: %f", lookatx);
            textprintf_ex(buffer, font, 0, 8*4, makecol(255,255,255), -1, "lookaty: %f", lookaty);
            textprintf_ex(buffer, font, 0, 8*5, makecol(255,255,255), -1, "lookatz: %f", lookatz);
            textprintf_ex(buffer, font, 0, 8*6, makecol(255,255,255), -1, "xfront: %f", xfront);
            textprintf_ex(buffer, font, 0, 8*7, makecol(255,255,255), -1, "yfront: %f", yfront);
            textprintf_ex(buffer, font, 0, 8*8, makecol(255,255,255), -1, "zfront: %f", zfront);
            textprintf_ex(buffer, font, 0, 8*6, makecol(255,255,255), -1, "xup: %f", xup);
            textprintf_ex(buffer, font, 0, 8*7, makecol(255,255,255), -1, "yup: %f", yup);
            textprintf_ex(buffer, font, 0, 8*8, makecol(255,255,255), -1, "zup: %f", zup);
            */
            return;
      } else {
         dynamicPointer += 10;
      }
   }
} 

void polydraw(BITMAP *bmp, MATRIX_f camera, float *SOB_ARR, float *SID_ARR, float VNU_ARR[][5], BITMAP *tex) {
   long vertex_Stream_pos = 0;
   float streamtime = 0.0f;
   int success = 0;
   float sx = 0.0f, sy = 0.0f, sz = 0.0f;
long poly_index = 0;
   while(1==1) {
      float streamtime2 = SOB_ARR[poly_index];
      if(streamtime2 + 10.0f < curtime) {
         poly_index=poly_index+13*100;
      } else if(streamtime2 + 1.0f < curtime) {
         poly_index=poly_index+13*10;
      } else if(streamtime2 < curtime) {
         poly_index=poly_index+13;
      } else if(streamtime2 >= curtime) {
         /*textprintf_ex(bmp, font, 0, 150, makecol(0, 0, 0), -1,
           "streamtime2: %f", streamtime2);*/
         break;
      }
   }
   float px = SOB_ARR[poly_index+1];
   float py = -SOB_ARR[poly_index+2];
   float pz = SOB_ARR[poly_index+3];
   sx = SOB_ARR[poly_index+4];
   sy = -SOB_ARR[poly_index+5];
   sz = SOB_ARR[poly_index+6];
   float rx = degreesToRadians(SOB_ARR[poly_index+7]);
   float ry = degreesToRadians(SOB_ARR[poly_index+8]);
   float rz = degreesToRadians(SOB_ARR[poly_index+9]);
   success = 0;
   streamtime = 0.0f;
   while(success == 0) {
      streamtime = SID_ARR[vertex_Stream_pos + 0];
      int length = SID_ARR[vertex_Stream_pos + 1];
      if(streamtime >= curtime) {
         for(int i = vertex_Stream_pos + 2; i<vertex_Stream_pos + 2 + length; i ++) {
            int starti = imax(SID_ARR[i],0);
            int endi = SID_ARR[i]+3*12;
            for(int j=starti; j<endi; j+=3) {
               float x0 = VNU_ARR[j + 0][0];
               float y0 = VNU_ARR[j + 0][1];
               float z0 = VNU_ARR[j + 0][2];
               float u0 = VNU_ARR[j + 0][3];
               float v0 = VNU_ARR[j + 0][4];
               float x1 = VNU_ARR[j + 1][0];
               float y1 = VNU_ARR[j + 1][1];
               float z1 = VNU_ARR[j + 1][2];
               float u1 = VNU_ARR[j + 1][3];
               float v1 = VNU_ARR[j + 1][4];
               float x2 = VNU_ARR[j + 2][0];
               float y2 = VNU_ARR[j + 2][1];
               float z2 = VNU_ARR[j + 2][2];
               float u2 = VNU_ARR[j + 2][3];
               float v2 = VNU_ARR[j + 2][4];
               x0 *= sx;
               y0 *= sy;
               z0 *= sz;
               x1 *= sx;
               y1 *= sy;
               z1 *= sz;
               x2 *= sx;
               y2 *= sy;
               z2 *= sz;
               u0 *= 256.0f;
               v0 *= 256.0f;
               u1 *= 256.0f;
               v1 *= 256.0f;
               u2 *= 256.0f;
               v2 *= 256.0f;
               rotateX( &x0, &y0, &z0, rx);
               rotateY( &x0, &y0, &z0, ry);
               rotateZ( &x0, &y0, &z0, rz);
               rotateX( &x1, &y1, &z1, rx);
               rotateY( &x1, &y1, &z1, ry);
               rotateZ( &x1, &y1, &z1, rz);
               rotateX( &x2, &y2, &z2, rx);
               rotateY( &x2, &y2, &z2, ry);
               rotateZ( &x2, &y2, &z2, rz);
               x0 += px;
               y0 += py;
               z0 += pz;
               x1 += px;
               y1 += py;
               z1 += pz;
               x2 += px;
               y2 += py;
               z2 += pz;

               draw_tri(bmp, &camera, 
                     x0,
                     y0,
                     z0,
                     u0,
                     v0,
                     x1,
                     y1,
                     z1,
                     u1,
                     v1,
                     x2,
                     y2,
                     z2,
                     u2,
                     v2, tex);
            }
         }
         success = 1;
      }
      vertex_Stream_pos += length + 2;
   }
}
void polydrawfast(BITMAP *bmp, MATRIX_f camera, float *SOB_ARR, float *SID_ARR, float VNU_ARR[][5], BITMAP *tex) {
   long vertex_Stream_pos = 0;
   float streamtime = 0.0f;
   int success = 0;
   float sx = 0.0f, sy = 0.0f, sz = 0.0f;
   while(1==1) {
      float streamtime2 = SOB_ARR[poly_index];
      if(streamtime2 + 10.0f < curtime) {
         poly_index=poly_index+13*100;
      } else if(streamtime2 + 1.0f < curtime) {
         poly_index=poly_index+13*10;
      } else if(streamtime2 < curtime) {
         poly_index=poly_index+13;
      } else if(streamtime2 >= curtime) {
         /*textprintf_ex(bmp, font, 0, 150, makecol(0, 0, 0), -1,
           "streamtime2: %f", streamtime2);*/
         break;
      }
   }
   float px = SOB_ARR[poly_index+1];
   float py = -SOB_ARR[poly_index+2];
   float pz = SOB_ARR[poly_index+3];
   sx = SOB_ARR[poly_index+4];
   sy = -SOB_ARR[poly_index+5];
   sz = SOB_ARR[poly_index+6];
   float rx = degreesToRadians(SOB_ARR[poly_index+7]);
   float ry = degreesToRadians(SOB_ARR[poly_index+8]);
   float rz = degreesToRadians(SOB_ARR[poly_index+9]);
   success = 0;
   streamtime = 0.0f;
   while(success == 0) {
      streamtime = SID_ARR[vertex_Stream_pos + 0];
      int length = SID_ARR[vertex_Stream_pos + 1];
      if(streamtime >= curtime) {
         for(int i = vertex_Stream_pos + 2; i<vertex_Stream_pos + 2 + length; i ++) {
            int starti = imax(SID_ARR[i],0);
            int endi = SID_ARR[i]+3*12;
            int VNM = 0;
            if(rx != 0 && ry != 0 && rz != 0) {
               for(int j=starti; j<endi; j+=3) {
                  float x0 = VNU_ARR[j + 0][0];
                  float y0 = VNU_ARR[j + 0][1];
                  float z0 = VNU_ARR[j + 0][2];
                  float u0 = VNU_ARR[j + 0][3];
                  float v0 = VNU_ARR[j + 0][4];
                  float x1 = VNU_ARR[j + 1][0];
                  float y1 = VNU_ARR[j + 1][1];
                  float z1 = VNU_ARR[j + 1][2];
                  float u1 = VNU_ARR[j + 1][3];
                  float v1 = VNU_ARR[j + 1][4];
                  float x2 = VNU_ARR[j + 2][0];
                  float y2 = VNU_ARR[j + 2][1];
                  float z2 = VNU_ARR[j + 2][2];
                  float u2 = VNU_ARR[j + 2][3];
                  float v2 = VNU_ARR[j + 2][4];
                  x0 *= sx;
                  y0 *= sy;
                  z0 *= sz;
                  x1 *= sx;
                  y1 *= sy;
                  z1 *= sz;
                  x2 *= sx;
                  y2 *= sy;
                  z2 *= sz;
                  u0 *= 256.0f;
                  v0 *= 256.0f;
                  u1 *= 256.0f;
                  v1 *= 256.0f;
                  u2 *= 256.0f;
                  v2 *= 256.0f;
                  rotateX( &x0, &y0, &z0, rx);
                  rotateY( &x0, &y0, &z0, ry);
                  rotateZ( &x0, &y0, &z0, rz);
                  rotateX( &x1, &y1, &z1, rx);
                  rotateY( &x1, &y1, &z1, ry);
                  rotateZ( &x1, &y1, &z1, rz);
                  rotateX( &x2, &y2, &z2, rx);
                  rotateY( &x2, &y2, &z2, ry);
                  rotateZ( &x2, &y2, &z2, rz);
                  x0 += px;
                  y0 += py;
                  z0 += pz;
                  x1 += px;
                  y1 += py;
                  z1 += pz;
                  x2 += px;
                  y2 += py;
                  z2 += pz;

                  draw_trifast(bmp, &camera,
                     x0,
                     y0,
                     z0,
                     u0,
                     v0,
                     x1,
                     y1,
                     z1,
                     u1,
                     v1,
                     x2,
                     y2,
                     z2,
                     u2,
                     v2, 
                     tex);
               }
            } else {
               for(int j=starti; j<endi; j+=3) {
                  float x0 = VNU_ARR[j + 0][0];
                  float y0 = VNU_ARR[j + 0][1];
                  float z0 = VNU_ARR[j + 0][2];
                  float u0 = VNU_ARR[j + 0][3];
                  float v0 = VNU_ARR[j + 0][4];
                  float x1 = VNU_ARR[j + 1][0];
                  float y1 = VNU_ARR[j + 1][1];
                  float z1 = VNU_ARR[j + 1][2];
                  float u1 = VNU_ARR[j + 1][3];
                  float v1 = VNU_ARR[j + 1][4];
                  float x2 = VNU_ARR[j + 2][0];
                  float y2 = VNU_ARR[j + 2][1];
                  float z2 = VNU_ARR[j + 2][2];
                  float u2 = VNU_ARR[j + 2][3];
                  float v2 = VNU_ARR[j + 2][4];
                  x0 *= sx;
                  y0 *= sy;
                  z0 *= sz;
                  x1 *= sx;
                  y1 *= sy;
                  z1 *= sz;
                  x2 *= sx;
                  y2 *= sy;
                  z2 *= sz;
                  u0 *= 256.0f;
                  v0 *= 256.0f;
                  u1 *= 256.0f;
                  v1 *= 256.0f;
                  u2 *= 256.0f;
                  v2 *= 256.0f;
                  x0 += px;
                  y0 += py;
                  z0 += pz;
                  x1 += px;
                  y1 += py;
                  z1 += pz;
                  x2 += px;
                  y2 += py;
                  z2 += pz;

                  draw_trifast(bmp, &camera,
                     x0,
                     y0,
                     z0,
                     u0,
                     v0,
                     x1,
                     y1,
                     z1,
                     u1,
                     v1,
                     x2,
                     y2,
                     z2,
                     u2,
                     v2, 
                     tex);
               }
            }
         }
         success = 1;
      }
      vertex_Stream_pos += length + 2;
   }
}
/* draw everything */
void render(BITMAP *bmp)
{
   MATRIX_f roller, camera;
   int x, y, w, h;
   clear_to_color(bmp, makecol(12, 12, 12));
   dynamicData(curtime/75.0f);
   
   x = 0;
   y = 0;
   w = 320;
   h = 200;
   
   set_projection_viewport(x, y, w, h);
   set_clip_rect(bmp, x, y, x+w-1, y+h-1);
   
   get_vector_rotation_matrix_f(&roller, xfront, yfront, zfront, roll*128.0/M_PI);
   apply_matrix_f(&roller, 0, -1, 0, &xup, &yup, &zup);
   
   get_camera_matrix_f(&camera,
           -xpos, ypos, zpos,
           xfront, yfront, zfront,  
           xup, yup, zup,
           fov,
           aspect);
   microrun();
   set_clip_rect(bmp, 0, 0, bmp->w, bmp->h);
   int textcol = makecol(255,255,255);
   /*
   textprintf_ex(bmp, font, 0, 118, textcol, -1,
     "fps: %d", fps);*/
}

void fps_check(void)
{
   fps = framecount*FPS_INT;
   framecount = 0;
}


int main(void)
{
   //system("lame.exe --decode -f --quiet egg.mp3 egg.wav");
   if (allegro_init() != 0)
      return 1;
   install_keyboard();
   install_timer();
   install_sound(DIGI_AUTODETECT, MIDI_AUTODETECT, "A");
   create_trans_table(&trans_table, pal, 255, 0, 255, NULL);
   set_trans_blender(255, 0, 255, 0);
   set_alpha_blender();

   
   set_color_depth(15);
   set_gfx_mode(GFX_AUTODETECT, 320,200,0,0); 

   sample = load_wav("musa.wav");
   all_textures();


   buffer = create_bitmap(320, 200);

   install_int_ex(fps_check, BPS_TO_TIMER(FPS_INT));
   play_sample(sample, 255, 128, 1000, 0);

   zbuf = create_zbuffer(buffer);
   set_zbuffer(zbuf);

   while (!key[KEY_ESC] || curtime > 142.0f) {
      clear_zbuffer(zbuf, 0.);
      render(buffer);
      curtime = ((float)voice_get_position(0))/22050.0f;
      /*
      textprintf_ex(buffer, font, 0, 190, makecol(255,255,255), -1,
        "curtime: %f", curtime);*/
      vsync();
      blit(buffer, screen, 0, 0, 0, 0, SCREEN_W, SCREEN_H);      
      //stretch_blit(buffer, screen, 0, 0, 320, 200, 0, 0, 320, 200); 
      framecount++;
   }

   destroy_bitmap(buffer);
   return 0;
}

END_OF_MAIN()



int imin(int a, int b) {
   if(a>b) return b;
   return a;
}
int imax(int a, int b) {
   if(a<b) return b;
   return a;
}

float min(float a, float b) {
   if(a>b) return b;
   return a;
}
float max(float a, float b) {
   if(a<b) return b;
   return a;
}
float length(struct vec2 r) {
  return sqrt(r.x*r.x+r.y*r.y);
}
struct vec2 vec2(float x, float y) {
   struct vec2 r;
   r.x = x;
   r.y = y;
   return r;
}
struct vec3 vec3(float x, float y, float z) {
   struct vec3 r;
   r.x = x;
   r.y = y;
   r.z = z;
   return r;
}
struct vec4 vec4(float x, float y, float z, float w) {
   struct vec4 r;
   r.x = x;
   r.y = y;
   r.z = z;
   r.w = w;
   return r;
}