#include <stdio.h>
#include <malloc.h>
#include <conio.h>
#include <sys/farptr.h>
#include <crt0.h>

#include "fixed.h"
#include "vga.h"
#include "pcx.h"
#include "kb.h"


void   __crt0_load_environment_file(char *_app_name) { }
char **__crt0_glob_function(char *_arg) { return 0; }
void   __crt0_setup_arguments(void) { }

typedef struct {
  char *texture[4];
  int texturewidth[4];
  int textureheight[4];
} rcMapBkInfo;

typedef struct {
  char *map;
  int width;
  int height;
  fixed playerx, playery;
  fixed playerstartx, playerstarty;
  fixed playerangle, playerstartangle;
  fixed playerheight, wallheight;
  rcMapBkInfo mapbkinfo[256];
} rcMapType;

int rcScreenWidth, rcScreenHeight, rcLineWidth;

char myMap[20][20] = {
 { 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20 },
 { 20, 0, 0, 0, 0,20, 0, 0, 0, 0, 0, 0, 0,20,30, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,20, 0, 0, 0, 0, 0, 0, 0,20,30, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,20,20, 0,20, 0, 0, 0,10,20,20,20,20,20, 0,20 },
 { 20, 0, 0, 0, 0,20, 0, 0,20, 0,30, 0,10,20, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,20, 0, 0,20, 0, 0, 0,10,20, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,20, 0, 0,20, 0, 0, 0, 0,20, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,20, 0, 0,20,20,20,20,20,20, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,20, 0, 0, 0, 0, 0,10, 0, 0, 0,30,30,20,20,20 },
 { 20, 0, 0, 0, 0,20, 0, 0, 0,10, 0, 0, 0,10, 0,30,30, 0, 0,20 },
 { 20, 0, 0, 0, 0,20, 0, 0,20,20,20,20,20,20, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,10, 0, 0, 0, 0, 0, 0, 0,20, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,10, 0, 0, 0, 0, 0, 0, 0,20, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,10, 0, 0, 0, 0, 0, 0, 0,10,20, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0,10,20,20,20,20,20,20, 0,20,20, 0, 0, 0, 0,20 },
 { 20, 0, 0,30, 0,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0,30, 0,10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0,30,30,30, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,20 },
 { 20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,20 },
 { 20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20,20 }
};

typedef struct {
  fixed xinfo;
  char mapID;
  char whichface;
  fixed distance;
  fixed yintersect, xintersect;
} rcVScanlineinfo;

void rcCastRay(rcMapType *map, fixed angle, rcVScanlineinfo *v) {
  fixed x, y, z, dx, dy, dz;
  unsigned long dst;
  char mapID;
  dst = 0xFFFFFFFF;
  dx = fixedCos(angle + map->playerangle);
  if (dx) {  
    dy = fixedDiv(fixedSin(angle + map->playerangle),dx);
    dz = fixedDiv(IntToFixed(1),dx);
  } 
  if (dx > 0) {
    x = (map->playerx | 0x0000FFFF) + 1;
    z = fixedMul(dz,x - map->playerx);
    y = map->playery + fixedMul(dy,x - map->playerx);
    x >>= 16;
    while (x < map->width) {
      if ((mapID = map->map[x + ((y>>16)*map->width)]) != 0) {
        v->xintersect = x<<16; v->yintersect = y;
        v->whichface = 0; v->mapID = mapID;
        v->xinfo = y & 0x0000FFFF;
        dst = z; 
        x = map->width; continue;
      }
      z += dz; y += dy; x ++;
      if (y < 0 || (y>>16) > map->height) x = map->width;
    }
  }
  if (dx < 0) {
    x = (map->playerx & 0xFFFF0000);
    z = fixedMul(dz,x - map->playerx);
    y = map->playery + fixedMul(dy,x - map->playerx);
    x >>= 16;
    while (x > 0) {
      if ((mapID = map->map[x - 1 + ((y>>16)*map->width)]) != 0) {
        if (z < dst) {
          v->xintersect = x<<16; v->yintersect = y;
          v->whichface = 1; v->mapID = mapID;
          v->xinfo = y & 0x0000FFFF;
          dst = z; 
        }
        x = 0; continue;
      }
      z -= dz; y -= dy; x --;
      if (y < 0 || (y>>16) > map->height) x = 0; 
    }
  }
  dy = fixedSin(angle + map->playerangle);
  if (dy) { 
    dx = fixedDiv(fixedCos(angle + map->playerangle),dy);
    dz = fixedDiv(IntToFixed(1),dy);
  }
  if (dy > 0) {
    y = (map->playery | 0x0000FFFF) + 1;
    x = map->playerx + fixedMul(dx,y - map->playery);
    z = fixedMul(dz,y - map->playery);
    y >>= 16;
    while (y < map->height) {
      if ((mapID = map->map[(x>>16) + (y*map->width)]) != 0) {
        if (z < dst) {
          v->xintersect = x; v->yintersect = y<<16;
          v->mapID = mapID; v->whichface = 2;
          v->xinfo = x & 0x0000FFFF;
          dst = z;
        }
        y = map->height; continue;
      }
      z += dz; x += dx; y ++;
      if (x < 0 || (x>>16) > map->width) y = map->height;
    }
  }
  if (dy < 0) {
    y = map->playery & 0xFFFF0000;
    x = map->playerx + fixedMul(dx,y - map->playery);
    z = fixedMul(dz,y - map->playery);
    y >>= 16;
    while (y > 0) {
      if ((mapID = map->map[(x>>16) + ((y-1)*map->width)]) != 0) {
        if (z < dst) {
          v->xintersect = x; v->yintersect = y<<16;
          v->mapID = mapID; v->whichface = 3;
          v->xinfo = x & 0x0000FFFF;
          dst = z;
        }
        y = 0; continue;
      }
      z -= dz; x -= dx; y --;
      if (x < 0 || (x>>16) > map->width) y = 0;
    }
  }
  if (dst == 0xFFFFFFFF) {
    v->distance = 4<<16;
    v->mapID = 0;
    v->whichface = 4;
  }
  else v->distance = fixedMul(dst, fixedCos(angle));
}

void rcScalevline(char *out, int outend, int outstart, char *in, int inx, 
                  int inwidth, int inheight) {
  fixed Y = 0;
  fixed dY = fixedDiv(inheight, outend-outstart);
  int outlen;
  in += (inx * inwidth);
  if (outend > rcScreenHeight) outend = rcScreenHeight;
  if (outstart < 0) {
    Y -= dY * outstart; 
    outstart = 0;
  }
  outlen = outend-outstart;
  if (outlen <= 0) return; 
  asm("
    pushl %%ebp
    movl %%eax, %%ebp
    subl %%ecx, %%edi
    0:
    movl %%ebx, %%eax
    addl %%ecx, %%edi
    shrl $16, %%eax
    addl %%edx, %%ebx
    decl %%ebp
    movb (%%esi, %%eax), %%al
    movb %%al, (%%edi)
    jnz 0b   
    popl %%ebp
  "
  ::"a" (outlen), "D" (out), "S" (in), "b" (Y), "d" (dY), "c" (rcLineWidth)
  : "%eax", "%ebx", "%ecx", "%edx", "%esi", "%edi", "%ebp");
}

void rcRenderVScanline(rcMapType *map, char *mem, rcVScanlineinfo *v) {
  int starty, endy, y;
  if (!v->distance) return;
  starty = (rcScreenHeight>>1) - 
       ((rcScreenHeight * (map->wallheight - map->playerheight)) / v->distance);
  endy = (rcScreenHeight>>1) + ((rcScreenHeight * (map->playerheight)) 
        / v->distance);
  if (endy < 1) return;
  if (starty > rcScreenHeight) return;
  if (starty > 0) {
    asm(" 
      movb %0, %%al
      0: 
      movb %%al, (%%edi)
      addl %%ebx, %%edi
      decl %%ecx
      jnz 0b
      ":
      :"g" (251), "b" (rcLineWidth), "D" (mem), "c" (starty)
      :"%eax", "%ebx", "%ecx", "%edi");
    mem += rcLineWidth * starty;
  }
  rcScalevline(mem, endy, starty, 
               map->mapbkinfo[(int) v->mapID].texture[(int)v->whichface],
               fixedMul(v->xinfo, 
                 map->mapbkinfo[(int)v->mapID].textureheight[(int)v->whichface]
                   -1),
               map->mapbkinfo[(int) v->mapID].textureheight[(int) v->whichface],
               map->mapbkinfo[(int) v->mapID].texturewidth[(int)v->whichface]);
  mem += (endy-starty) * rcLineWidth;
  y = rcScreenHeight - endy;
  if (y > 0) {
     asm(" 
        movb %0, %%al
        0: 
        movb %%al, (%%edi)
        addl %%ebx, %%edi
        decl %%ecx
        jnz 0b
      ":
      :"g" (250), "b" (rcLineWidth), "D" (mem), "c" (y)
      :"%eax", "%ebx", "%ecx", "%edi");  
  }
}

void rcRenderMap(rcMapType *map, char *mem) {
  fixed angle = IntToFixed(-45);
  fixed dangle = fixedDiv(90, rcScreenWidth);
  rcVScanlineinfo v;
  int x;
  for (x = 0; x < rcScreenWidth; x++) {
    vgaSetXModeWriteMask(1<<(x&3));
    rcCastRay(map,(fixedSin(angle) * 45),&v);
    rcRenderVScanline(map, mem,&v);
    if ((x & 3) == 3) mem++;
    angle += dangle;
  }
}

char *LoadTxr(char *filename, char *pal, rcMapType *map, int num, int add) {
  int texturewidth, textureheight;
  char *texture, *pal2;
  if (pcxLoad(filename,&texturewidth, &textureheight, &pal2, &texture) < 0) 
    return NULL;
  vgaMemcpy(pal,pal2,768);
  free(pal2);
  vgaMemadd(texture,add,texturewidth*textureheight);
  map->mapbkinfo[num].texture[0] = texture;
  map->mapbkinfo[num].texture[1] = texture;
  map->mapbkinfo[num].texture[2] = texture;
  map->mapbkinfo[num].texture[3] = texture;
  map->mapbkinfo[num].texturewidth[0] = texturewidth;
  map->mapbkinfo[num].texturewidth[1] = texturewidth;
  map->mapbkinfo[num].texturewidth[2] = texturewidth;
  map->mapbkinfo[num].texturewidth[3] = texturewidth;
  map->mapbkinfo[num].textureheight[0] = textureheight;
  map->mapbkinfo[num].textureheight[1] = textureheight;
  map->mapbkinfo[num].textureheight[2] = textureheight;
  map->mapbkinfo[num].textureheight[3] = textureheight;
  return texture;
}

void main() {
 
  kbKeyType escKey = { 1,0,0 };
  kbKeyType upArrowKey = { 72,1,0 };
  kbKeyType downArrowKey = { 80,1,0 };
  kbKeyType leftArrowKey = { 75,1,0 };
  kbKeyType rightArrowKey = { 77,1,0 };

  rcMapType Map;
  char pal1[768];
  char pal[768];
  char *texture1, *texture2, *texture3;
  int frames = 0;
  int ticks;
  vgaMemset(pal,0,768);
  if ((texture1 = LoadTxr("texture1.pcx",pal1,&Map,20, 0)) == NULL) exit(1);
  vgaMemcpy(pal,pal1,16*3);
  if ((texture2 = LoadTxr("texture2.pcx",pal1,&Map,30,16)) == NULL) exit(1);
  vgaMemcpy(pal + 16*3,pal1,16*3);
  if ((texture3 = LoadTxr("texture3.pcx",pal1,&Map,10,32)) == NULL) exit(1);
  vgaMemcpy(pal + 32*3,pal1,16*3);
  pal[250*3] = 13; pal[250*3+1] = 13; pal[250*3+2] = 13;
  pal[251*3] = 0; pal[251*3+1] = 0; pal[251*3+2] = 30;
  Map.map = (char *) myMap;
  Map.width = 20;
  Map.height = 20;
  Map.wallheight = 1 << 16;
  Map.playerheight = Map.wallheight>>1;
  Map.playerstartx = IntToFixed(2);
  Map.playerstarty = IntToFixed(2);
  Map.playerstartangle = IntToFixed(90);
  Map.playerx = Map.playerstartx;
  Map.playery = Map.playerstarty;
  Map.playerangle = Map.playerstartangle;
  fixedInit();
  if (vgaSetMode(VGA_MODEX_320x200,1) < 0) exit(1);
  vgaSetPalette(pal);
  rcScreenWidth = vgaScreen.Width;
  rcScreenHeight = vgaScreen.Height;
  rcLineWidth = vgaScreen.LineWidth;
  kbSet(1,10);
  kbClearKeys();
  kbMonitorKey(&escKey);
  kbMonitorKey(&leftArrowKey);
  kbMonitorKey(&rightArrowKey);
  kbMonitorKey(&upArrowKey);
  kbMonitorKey(&downArrowKey);
  ticks = _farpeekl(_dos_ds,0x46C);
  while (!escKey.pressed) {
    int qw;
    qw = 0; //((frames & 1) * 
//         (vgaScreen.LineWidth * (vgaScreen.Height)));
    rcRenderMap(&Map,vgaScreen.GraphMem + qw);
//    vgaSetXModeVisibleStart(qw);
    if (leftArrowKey.pressed) Map.playerangle -= IntToFixed(2); 
    if (rightArrowKey.pressed) Map.playerangle += IntToFixed(2);
    if (upArrowKey.pressed) {
      int r = 3;
      Map.playerx += fixedCos(Map.playerangle)>>r;
      Map.playery += fixedSin(Map.playerangle)>>r;
      Map.playerx += fixedCos(Map.playerangle)>>(r+1);
      Map.playery += fixedSin(Map.playerangle)>>(r+1);
      if (Map.map[(Map.playerx>>16) + (Map.playery>>16)*Map.width]) {
        Map.playerx -= fixedCos(Map.playerangle)>>r;
        Map.playery -= fixedSin(Map.playerangle)>>r;
      }
      Map.playerx -= fixedCos(Map.playerangle)>>(r+1);
      Map.playery -= fixedSin(Map.playerangle)>>(r+1);
    }
    if (downArrowKey.pressed) {
      int r = 4;
      Map.playerx -= fixedCos(Map.playerangle)>>r;
      Map.playery -= fixedSin(Map.playerangle)>>r;
      Map.playerx -= fixedCos(Map.playerangle)>>(r+1);
      Map.playery -= fixedSin(Map.playerangle)>>(r+1);
      if (Map.map[(Map.playerx>>16) + (Map.playery>>16)*Map.width]) {
        Map.playerx += fixedCos(Map.playerangle)>>r;
        Map.playery += fixedSin(Map.playerangle)>>r;
      }
      Map.playerx += fixedCos(Map.playerangle)>>(r+1);
      Map.playery += fixedSin(Map.playerangle)>>(r+1);
    }
    frames++;
  }
  ticks = _farpeekl(_dos_ds,0x46C) - ticks;
  free(texture3);
  free(texture2);
  free(texture1);
  kbSet(0,0);
  vgaSetMode(VGA_MODETEXT,1);
  printf("%.2f fps\n",((float) frames) / (((float) ticks) / 18.2));
}

