// Necklace of the Eye v3.0
// roguelike frontend
// Copyright (C) 2010-2011 Zeno Rogue, see 'noteye.h' for details

#include <complex>
#include <math.h>

int viewx0, viewx1, viewy0, viewy1, viewxm, viewym, viewxs, viewys;
SDL_Surface *viewimg;
Image *viewimage;

int xscale(double x, double y) {
  int res = int(viewxm + viewxs * x/y);
  return res;
  }

int yscale(double z, double y) {
  int res = int(viewym + viewys * z/y);
  return res;
  }

typedef complex<double> cd;

cd facevec;
cd facevecc;

cd rot(int x, int y) { return cd(x,y) * facevecc; }

cd p[257];  

bool shiftdown;

void renderTileImage(TileImage *w, cd p0, cd p1, double z0, double z1) {
  
  if(imag(p0) <= 0 && imag(p1) <= 0) return;

  /* fprintf(debug, "(%lf,%lf) - (%lf,%lf) [%d:%d]\n",
    real(p0), imag(p0), real(p1), imag(p1), z0, z1);
  
  fflush(debug); */
    
  for(int xt=0; xt<=w->sx; xt++) {
    p[xt] = p0 + (p1-p0) * (xt / (w->sx + .0));
    }

  viewimage->setLock(true);
  w->i->setLock(true);
  
  int ay = 0;
  if(shiftdown) {
    for(ay=w->sy-1; ay >= 0; ay--) for(int ax=0; ax<w->sx; ax++)
      if(!istrans(qpixel(w->i->s, w->ox+ax, w->oy+ay), w->trans)) goto ayfound;
  
    return;
    ayfound: ay = w->sy - ay - 1;
    }

  for(int xt=0; xt<w->sx; xt++) {
    if(imag(p[xt]) <= 0 || imag(p[xt+1]) <= 0) continue;
                              
    int sx0 = xscale(real(p[xt]), imag(p[xt]));
    int sx1 = xscale(real(p[xt+1]), imag(p[xt+1]));
    
    int syt0 = yscale(z0, imag(p[xt]));
    int syb0 = yscale(z1, imag(p[xt]));
    int syt1 = yscale(z0, imag(p[xt+1]));
    int syb1 = yscale(z1, imag(p[xt+1]));
    
    /* fprintf(debug, "t%-2d (%3d,%3d) - (%3d,%3d) - (%3d,%3d) - (%3d,%3d)\n",
      xt,
      sx0, syt0, sx1, syt1, sx1, syb1, sx0, syb0
      ); */
    
    int dx0 = sx0 >= viewx0 ? sx0 : viewx0;
    int dx1 = sx1 < viewx1 ? sx1 : viewx1;
    
    for(int x=dx0; x<dx1; x++) {
      int dyt = syt0 + (syt1-syt0) * (x-dx0) / (sx1-sx0);
      int dyb = syb0 + (syb1-syb0) * (x-dx0) / (sx1-sx0);
      
      int y0 = dyt < viewy0 ? viewy0 : dyt;
      int y1 = dyb > viewy1 ? viewy1 : dyb;
      
      for(int y=y0; y<y1; y++) {
        int yt = ((y-dyt) * w->sy) / (dyb-dyt);
        if(yt<ay) continue;
        yt += w->sy - 1 - w->sy + 1;
        if(yt < 0) yt = 0;
        if(yt >= w->sy) yt = w->sy-1;
        int pix = qpixel(w->i->s, w->ox+xt, w->oy+yt-ay);
        if(w->trans == transAlpha) 
          alphablend(qpixel(viewimg, x, y), pix);
        else if(!istrans(pix, w->trans))
          qpixel(viewimg, x, y) = pix;
        }
      }
    }
  
  }

void renderChar(int ch, cd p0, cd p1, double z0, double z1) {
  if(ch == 0) return;

  TileImage *TI = dbyId<TileImage> (ch);
  if(TI) renderTileImage(TI, p0, p1, z0, z1);

  TileMerge *TM = dbyId<TileMerge> (ch);
  if(TM) 
    renderChar(TM->t1, p0, p1, z0, z1),
    renderChar(TM->t2, p0, p1, z0, z1);

  TileTransform *TT = dbyId<TileTransform> (ch);
  if(TT) {
    cd shift(TT->dx / 1440., TT->dz/1440.);
    double scale = TT->sx/1440.;
    cd rot = cd(scale * cos(TT->rot*M_PI/180), scale * sin(TT->rot*M_PI/180));
    cd np0 = p0 + (p1-p0) * shift;
    cd np1 = p0 + (p1-p0) * shift + (p1-p0) * rot;
    

    double nz0 = z0 + (z1-z0) * (TT->dy / 1440.);
    double nz1 = z0 + (z1-z0) * (TT->dy / 1440.) + (z1-z0) * (TT->sy/1440.);
    
    renderChar(TT->t1, np0, np1, nz0, nz1);
    }
  }

struct fpoint4 {
  double x, y, z;
  fpoint4() {}
  fpoint4(double X, double Y, double Z) {x=X; y=Y; z=Z;}
};

fpoint4 operator + (const fpoint4& a, const fpoint4& b) {
  return fpoint4(a.x+b.x, a.y+b.y, a.z+b.z);
}

fpoint4 operator - (const fpoint4& a) {
  return fpoint4(-a.x, -a.y, -a.z);
  }

fpoint4 operator * (fpoint4 t, double a) {
  return fpoint4(t.x*a, t.y*a, t.z*a);
  }

fpoint4 rot4(int x, int y, int z) {
  cd xy = cd(x,y) * facevecc;
  return fpoint4(real(xy), imag(xy), z);
  }

int min3(int a, int b, int c) { return min(a, min(b,c)); }
int max3(int a, int b, int c) { return max(a, max(b,c)); }

int cross(int x1,int y1,int x2,int y2,int x3,int y3) {
  return (x2-x1)*(y3-y1)-(x3-x1)*(y2-y1);
  }

void renderAffineTriangle(int pix, int trans, fpoint4 orig, fpoint4 ox, fpoint4 oy) {
  if(orig.y <= 0 && orig.y+ox.y <= 0 && orig.y+oy.y <= 0)
    return;
    
  int ax = xscale(orig.x, orig.y);
  int ay = yscale(orig.z, orig.y);
  
  fpoint4 orih = orig + ox;
  
  int bx = xscale(orih.x, orih.y);
  int by = yscale(orih.z, orih.y);

  orih = orig + oy;

  int cx = xscale(orih.x, orih.y);
  int cy = yscale(orih.z, orih.y);
  
  int xmi = min3(ax,bx,cx);
  int xma = max3(ax,bx,cx);
  int ymi = min3(ay,by,cy);
  int yma = max3(ay,by,cy);
  
  if(cross(ax,ay,bx,by,cx,cy) < 0) swap(bx,cx), swap(by,cy);
  
  if(xmi < -100 || xma > 1000 || ymi < -100 || yma > 1000) return;
  
  for(int x=max(xmi,viewx0); x<=xma && x <= viewx1; x++)
  for(int y=max(ymi,viewy0); y<=yma && y <= viewy1; y++)
    if(cross(ax,ay,bx,by,x,y) >= 0)
    if(cross(bx,by,cx,cy,x,y) >= 0)
    if(cross(cx,cy,ax,ay,x,y) >= 0) {
      if(trans == transAlpha) 
        alphablend(qpixel(viewimg, x, y), pix);
      else 
        qpixel(viewimg, x, y) = pix;
      }
  }

void renderAffinePixel(int pix, int trans, fpoint4 orig, fpoint4 ox, fpoint4 oy) {
  if(istrans(pix, trans)) return;
  renderAffineTriangle(pix,trans,orig,ox,oy);
  renderAffineTriangle(pix,trans,orig+ox+oy,-ox,-oy);
  }

void renderAffineImage(TileImage *w, fpoint4 orig, fpoint4 ox, fpoint4 oy) {
  
  if(orig.y <= 0 && orig.y+ox.y <= 0 && orig.y+oy.y <= 0 && orig.y+ox.y+oy.y <= 0)
    return;

  viewimage->setLock(true);
  w->i->setLock(true);
  
  double dx = 1./w->sx;
  double dy = 1./w->sy;
  
  ox = ox * dx;
  oy = oy * dy;
  
  for(int ax=0; ax<w->sx; ax++) for(int ay=0; ay<w->sy; ay++)
    renderAffinePixel(
      qpixel(w->i->s, w->ox+ax, w->oy+ay),
      w->trans,
      orig+ox*ax+oy*ay,
      ox,oy
      );
  }

void renderAffine(int ch, fpoint4 orig, fpoint4 dx, fpoint4 dy, fpoint4 dz) {
  if(ch == 0) return;

  TileImage *TI = dbyId<TileImage> (ch);
  if(TI) renderAffineImage(TI, orig, dx, dy);

  TileMerge *TM = dbyId<TileMerge> (ch);
  if(TM)                 
    renderAffine(TM->t1, orig, dx, dy, dz),
    renderAffine(TM->t2, orig, dx, dy, dz);

  TileFreeform *TFF = dbyId<TileFreeform> (ch);
  if(TFF) {
    fpoint4 norig = 
      orig * TFF->par->d[0][0] + 
      dx   * TFF->par->d[0][1] + 
      dy   * TFF->par->d[0][2] + 
      dz   * TFF->par->d[0][3];
    fpoint4 ndx = 
      orig * TFF->par->d[1][0] + 
      dx   * TFF->par->d[1][1] + 
      dy   * TFF->par->d[1][2] + 
      dz   * TFF->par->d[1][3];
    fpoint4 ndy = 
      orig * TFF->par->d[2][0] + 
      dx   * TFF->par->d[2][1] + 
      dy   * TFF->par->d[2][2] + 
      dz   * TFF->par->d[2][3];
    fpoint4 ndz = 
      orig * TFF->par->d[3][0] + 
      dx   * TFF->par->d[3][1] + 
      dy   * TFF->par->d[3][2] + 
      dz   * TFF->par->d[3][3];
    renderAffine(TFF->t1, norig, ndx, ndy, ndz);
    }
  }

void imagepixel(int &pix, TileImage *w, int x, int y) {
  int lpix = qpixel(w->i->s, w->ox + ((x * w->sx) >> 8), w->oy + ((y * w->sy) >> 8));
  if(w->trans == transAlpha) alphablend(pix, lpix);
  else {
    if(istrans(lpix, w->trans)) return;
    pix = lpix;
    }
  }

void cellpixel(int &pix, int ch, int x, int y) {
  if(ch == 0) return;

  TileImage *TI = dbyId<TileImage> (ch);
  if(TI) { imagepixel(pix, TI, x, y); return; }

  TileMerge *TM = dbyId<TileMerge> (ch);
  if(TM) {
    cellpixel(pix, TM->t1, x, y),
    cellpixel(pix, TM->t2, x, y);
    return;
    }

  TileFill *TF = dbyId<TileFill> (ch);
  if(TF) pix = TF->color;
  }

void drawFPP(int x, int y, int c) {

  if(!c) return;
  
  fpoint4 orig = rot4(x*32-16, -y*32+16, +16);
  fpoint4 ox = rot4(32, 0, 0);
  fpoint4 oy = rot4(0, -32, 0);
  fpoint4 oz = rot4(0, 0, -32);
  renderAffine(distill(c, spFree), orig, ox, oy, oz);

  shiftdown = false;
  renderChar(distill(c, spWallN), rot(x*32+16, -y*32+16), rot(x*32-16, -y*32+16), -16, +16);
  renderChar(distill(c, spWallW), rot(x*32-16, -y*32+16), rot(x*32-16, -y*32-16), -16, +16);
  renderChar(distill(c, spWallS), rot(x*32-16, -y*32-16), rot(x*32+16, -y*32-16), -16, +16);
  renderChar(distill(c, spWallE), rot(x*32+16, -y*32-16), rot(x*32+16, -y*32+16), -16, +16);
  
  // 12 advance chars
  
  shiftdown = true;
  cd pos = rot(x*32, -y*32);
  renderChar(distill(c, spCenter), pos + cd(-16, 0), pos + cd(16, 0), -16, +16);
  renderChar(distill(c, spMonst), pos + cd(-16, -12), pos + cd(16,-12), -16, +16);
  renderChar(distill(c, spItem), pos + cd(-8, -14), pos + cd(+8,-14), -2, +16);
  }

void drawFPPat(int wax, int way, int facedir, Screen *s) {
  
  static Screen sfloor, sceil;
  sfloor.setSize(s->sx, s->sy);
  sceil. setSize(s->sx, s->sy);

  for(int x=0; x<s->sx; x++)
  for(int y=0; y<s->sy; y++) {
    sfloor.get(x,y) = distill(s->get(x,y), spFloor);
    sceil .get(x,y) = distill(s->get(x,y), spCeil);
    }
  
  facevec = exp(cd(0, (facedir-90) * M_PI/180));
  facevecc = conj(facevec);
  
  for(int y=viewy0; y<viewy1; y++)
  for(int x=viewx0; x<viewx1; x++) {

    int& pix(qpixel(viewimg,x,y));
        
    int ux, uy;

    double sf;
    if(y > viewym) {
      sf = 128.0 / (y-viewym);
      }
    else if(y < viewym) {
      sf = -128.0 / (y-viewym);
      }
    else continue;

    cd u((viewxm-x) * sf, viewys * sf);
    // u *= cd(0, 1);
    // cd u(-scal*sf, (x-viewxm) * sf);
      
    u *= -facevecc;
    ux = int(floor(128+real(u))), uy = int(floor(imag(u)+128));

    int ux0 = ux>>8, uy0 = uy>>8;

#ifdef NDISTILL
    //int flag = y > viewym ? spFloor : spCeil;    
    int C=(s->get(wax+ux0,way+uy0));
#else
    //int flag=-1;
    int C=(y > viewym ? sfloor : sceil).get(wax+ux0,way+uy0);
    #endif
      
    ux &= 255;
    uy &= 255;
    
    cellpixel(pix, C, ux, uy);
    }
  
  for(int ty=20; ty>=0; ty--)
  for(int tx=20; tx>=0; tx--) {
  
    #define D(X,Y) \
      drawFPP(X, Y, s->get(wax+X,way+Y));
    
    if(tx || ty) D(tx, ty);
    if(tx) D(-tx, ty);
    if(ty) D(tx, -ty);
    if(tx && ty) D(-tx, -ty);
    
    #undef D
    }
  }

#ifdef USELUA
int lh_fpp(lua_State *L) {
  lua_pushvalue(L, 1);

  viewimage = byId<Image> (getfield(L, "vimg"));
  viewimg = viewimage->s;
  SDL_LockSurface(viewimg);
  
  viewx0 = getfield(L, "vx0");
  viewy0 = getfield(L, "vy0");
  viewx1 = getfield(L, "vx1");
  viewy1 = getfield(L, "vy1");
  viewxm = getfield(L, "vxm");
  viewym = getfield(L, "vym");
  viewxs = getfield(L, "vxs");
  viewys = getfield(L, "vys");
  
  drawFPPat(luaInt(2), luaInt(3), luaInt(4), luaO(5, Screen));
  
  SDL_UnlockSurface(viewimg);
  
  lua_pop(L, 1);
  return 0;
  }
#endif
