// Necklace of the Eye v3.0
// roguelike frontend
// Copyright (C) 2010-2011 Zeno Rogue, see 'noteye.h' for details

#define HASHMAX 65535

#define COLMOD 65519

void TileImage::debug() {
  printf("%d: image from %d\n", id, i->id);
  }

int TileMerge::hash() const {
  return (t1 ^ (t2 * 13157)) & HASHMAX;
  }

void Deb(int x) { 
  Get(Tile, T, x); 
  if(T) T->debug();
  else printf("%d: not a tile\n", x);
  }


void TileMerge::debug() {
  printf("%d: merge %d + %d\n", id, t1, t2);
  Deb(t1); Deb(t2);
  }

int TileFill::hash() const {
  return (alpha + (color % COLMOD)) & HASHMAX;
  }

void TileFill::debug() {
  printf("%d: color %8x alpha %8x\n", id, color, alpha);
  }

int TileRecolor::hash() const {
  return ((color % COLMOD) ^ (mode + t1 * 13157)) & HASHMAX;
  }

void TileRecolor::debug() {
  printf("%d: recolor %d, color %8x mode %d\n", id, t1, color, mode);
  Deb(t1);
  }

int TileSpatial::hash() const {
  return (t1+sf) & HASHMAX;
  }

void TileSpatial::debug() {
  printf("%d: %d, spatial %8x\n", id, t1, sf);
  Deb(t1);
  }

int TileLayer::hash() const {
  return (t1+layerid) & HASHMAX;
  }

void TileLayer::debug() {
  printf("%d: %d, layer %d\n", id, t1, layerid);
  Deb(t1);
  }

int TileTransform::hash() const {
  return (t1+1973) & HASHMAX;
  }

void TileTransform::debug() { printf("transform\n"); }

int TileFreeform::hash() const {
  return (t1+par->id) & HASHMAX;
  }

void TileFreeform::debug() { 
  printf("%d: freeform %d\n",id, t1);
  Deb(t1);
  }

Tile *hashtab[HASHMAX+1];

int addTile(Image *i, int ox, int oy, int sx, int sy, int trans) {
  TileImage *T = new TileImage;
  T->i = i;
  T->ox = ox;
  T->oy = oy;
  T->sx = sx;
  T->sy = sy;
  T->trans = trans;
  T->chid = '?';
  return registerObject(T);
  }

//--

bool eq(const TileMerge& a, const TileMerge& b) {
  return a.t1 == b.t1 && a.t2 == b.t2 && a.over == b.over;
  }

bool eq(const TileFill& a, const TileFill& b) {
  return a.color == b.color && a.alpha == b.alpha;
  }

bool eq(const TileRecolor& a, const TileRecolor& b) {
  return a.color == b.color && a.t1 == b.t1 && a.mode == b.mode;
  }

bool eq(const TileSpatial& a, const TileSpatial& b) {
  return a.t1 == b.t1 && a.sf == b.sf;
  }

bool eq(const TileLayer& a, const TileLayer& b) {
  return a.t1 == b.t1 && a.layerid == b.layerid;
  }

bool eq(const TileTransform& a, const TileTransform& b) {
  return a.t1 == b.t1 && a.dx == b.dx && a.dy == b.dy && a.sx == b.sx && a.sy == b.sy &&
    a.dz == b.dz && a.rot == b.rot;
  }

bool eq(const TileFreeform& a, const TileFreeform& b) {
  return a.t1 == b.t1 && a.par == b.par;
  }

int hashok = 0, hashcol = 0;

template<class T> int registerTile(const T& x) {

  int hsh = x.hash();
  
  Tile** hs (&(hashtab[hsh]));
  Tile** hso = hs;
  while(*hs) {
    T* y = dynamic_cast<T*> (*hs);
    if(y && eq(x,*y)) { 
      hashok++; 
      if(hs != hso) {
        // move to the front
        Tile *nih = (*hs)->nextinhash;
        (*hs)->nextinhash = *hso;
        *hso = *hs;
        *hs = nih;
        }
      return y->id; 
      }
    else {
      hashcol++;
      hs = &(*hs)->nextinhash;
      }
    }
    
  T *xc = new T;
  *xc = x;
  xc->preprocess();
  xc->nextinhash = *hso;
  *hso = xc;
  return registerObject(xc);
  }

int addMerge(int t1, int t2, bool over) {
  // an optimization for merging zeros
  if(t1 == 0) return t2;
  if(t2 == 0) return t1;

  TileMerge T;
  T.t1 = t1;
  T.t2 = t2;
  T.over = over;
  
  return registerTile(T);
  }

int addLayer(int t1, int layerid) {
  if(t1 == 0) return 0;
  TileLayer TL;
  TL.t1 = t1;
  TL.layerid = layerid;
  return registerTile(TL);
  }

int addSpatial(int t1, int sf) {
  if(t1 == 0) return 0;
  TileSpatial TSp;
  TSp.t1 = t1;
  TSp.sf = sf;
  return registerTile(TSp);
  }

int addTransform(int t1, int dx, int dy, int sx, int sy, int dz, int rot) {
  TileTransform TT;
  TT.t1 = t1;
  TT.dx = dx; TT.dy = dy;
  TT.sx = sx; TT.sy = sy;
  TT.dz = dz; TT.rot = rot;
  return registerTile(TT);
  }

int addFreeform(int t1, FreeFormParam *p) {
  if(t1 == 0) return 0;
  TileFreeform TFF;
  TFF.t1 = t1;
  TFF.par = p;
  return registerTile(TFF);
  }

//--

#define rectrans 0xDEBEEF

void TileRecolor::preprocess() {
  Get(TileImage, TIC, t1);
  if(TIC) {
    int sx = TIC->sx, sy = TIC->sy;
    Image *i = new Image(sx, sy, TIC->trans == transAlpha ? 0 : rectrans);
    for(int y=0; y<sy; y++) for(int x=0; x<sx; x++) {
      int pix = qpixel(TIC->i->s, TIC->ox+x, TIC->oy+y);
      if(istrans(pix, TIC->trans)) continue;
      recolor(pix, color, mode);
      qpixel(i->s, x, y) = pix;
      }
    i->id = -1;
    cache = addTile(i, 0, 0, sx, sy, TIC->trans == transAlpha ? transAlpha : rectrans);
    cachechg = TIC->i->changes;
    }
  else
    cache = 0;
  }

int addFill(int color, int alpha) {
  TileFill TF;
  TF.color = color;
  TF.alpha = alpha;
  return registerTile(TF);
  }

int addRecolor(int t1, int color, int mode) {
  // optimizations first
  if(color == -1) return t1;
  if(t1 == 0) return 0;
  
  Get(TileRecolor, TR, t1);
  if(TR && TR->mode == mode) return addRecolor(TR->t1, color, mode);

  Get(TileFill, TF, t1);
  if(TF) return addFill(color, TF->alpha);

  TileRecolor T;
  T.t1 = t1;
  T.color = color;
  T.mode = mode;
  
  return registerTile(T);
  }

int getCol(int x) {
  if(!x) return -1;
  
  Get(TileImage, TI, x);
  if(TI) return -1;

  Get(TileRecolor, TR, x);
  if(TR) return TR->color;
  
  Get(TileMerge, TM, x);
  if(TM) return getCol(TM->over ? TM->t1 : TM->t2);
  
  return 0;
  }

int getImage(int x) {
  if(!x) return 0;
  
  Get(TileImage, TI, x);
  if(TI) return TI->i->id;

  Get(TileRecolor, TR, x);
  if(TR) return getImage(TR->t1);
  
  Get(TileMerge, TM, x);
  if(TM) {
    int u = getImage(TM->t2);
    if(u) return u;
    return getImage(TM->t1);
    }
  
  return 0;
  }

int getChar(int x) {
  if(!x) return -1;
  
  Get(TileImage, TI, x);
  if(TI) return TI->chid;

  Get(TileRecolor, TR, x);
  if(TR) return getChar(TR->t1);
  
  Get(TileMerge, TM, x);
  if(TM) return getChar(TM->over ? TM->t1 : TM->t2);
  
  return 0;
  }

int getBak(int x) {
  //Get(TileRecolor, TR, x);
  //if(TR) return TR->color;
  
  Get(TileFill, TF, x);
  if(TF) return TF->color;
  
  Get(TileMerge, TM, x);
  if(TM) return getBak(TM->t1);
  
  return -1;
  }

int tileSetFont(int x, Font *f) {
  Get(TileImage, TI, x);
  if(TI && TI->chid >= 0 && TI->chid < 256) return f->ti[TI->chid];

  Get(TileRecolor, TR, x);
  if(TR) return addRecolor(tileSetFont(TR->t1, f), TR->color, TR->mode);
  
  Get(TileMerge, TM, x);
  if(TM) return addMerge(tileSetFont(TM->t1,f), tileSetFont(TM->t2,f), TM->over);
  
  return x;
  }

int distillLayer(int x, int layerid) {
  Get(TileMerge, TM, x);
  if(TM) return 
    addMerge( distillLayer(TM->t1, layerid), distillLayer(TM->t2, layerid), TM->over);

  Get(TileLayer, TL, x);
  if(TL) { if(TL->layerid == layerid) return TL->t1; else return 0; }
  
  Get(TileTransform, TT, x);
  if(TT) {
    int t1 = distillLayer(TT->t1, layerid);
    if(!t1) return 0;
    TileTransform TT2 = *TT;
    TT2.t1 = t1;
    return registerTile(TT2);
    }
  
  Get(TileSpatial, TS, x);
  if(TS) return addSpatial(distillLayer(TS->t1, layerid), TS->sf);
  
  if(layerid == 0) return x;
  return 0;
  }

int distill(int x, int sp) {

  Get(TileImage, TI, x);
  if(TI) return x;
  
  Get(TileRecolor, TR, x);
  if(TR && TR->cache) {
    Get(TileImage, TIR, TR->t1);
    if(TIR->i->changes != TR->cachechg) {
      // invalidate cache!
      Get(TileImage, TIRC, TR->cache);
      delete TIRC->i;
      deleteobj(TR->cache);
      TR->cache = 0;
      TR->preprocess();
      }
    }
  if(TR && TR->cache) return TR->cache;
  if(TR) {
    int ds = distill(TR->t1, sp);
    Get(TileMerge, TM, ds);
    if(TM) {
      return addMerge( distill(addRecolor(TM->t1, TR->color, TR->mode), sp), distill(addRecolor(TM->t2, TR->color, TR->mode), sp), TM->over);
      }
    
    Get(TileTransform, TT, ds);
    if(TT) {
      TileTransform TT2 = *TT;
      TT2.t1 = distill(addRecolor(TT->t1, TR->color, TR->mode), sp);
      return registerTile(TT2);
      }

    Get(TileRecolor, TRe, ds);
    if(TRe) {
      return addRecolor(TRe->cache, TR->color, TR->mode);
      }

    int i = addRecolor(ds, TR->color, TR->mode);
    return distill(i, sp);
    }
  
  Get(TileSpatial, TS, x);
  if(TS && (TS->sf & sp)) return distill(TS->t1, sp);
  
  Get(TileLayer, TL, x);
  if(TL) return distill(TL->t1, sp);
  
  Get(TileMerge, TM, x);
  if(TM) return addMerge( distill(TM->t1, sp), distill(TM->t2, sp), TM->over);
  
  Get(TileFill, TF, x);
  if(TF) return x;
  
  Get(TileTransform, TT, x);
  if(TT) {
    TileTransform TT2 = *TT;
    TT2.t1 = distill(TT->t1, sp);
    return registerTile(TT2);
    }
  
  Get(TileFreeform, TFF, x);
  if(TFF) {
    int di = distill(TFF->t1, sp);
    return addFreeform(di, TFF->par);
    }
  
  return 0;
  }

#ifdef USELUA

int lh_tileMerge(lua_State *L) {
  checkArg(L, 2, "tilemerge");
  return retInt(L, addMerge(luaInt(1), luaInt(2), false));
  }

int lh_tileMergeOver(lua_State *L) {
  checkArg(L, 2, "tilemergeover");
  return retInt(L, addMerge(luaInt(1), luaInt(2), true));
  }

int lh_addTile(lua_State *L) {
  checkArg(L, 6, "addtile");
  return retInt(L, addTile(luaO(1, Image), luaInt(2), luaInt(3), 
    luaInt(4), luaInt(5),
    luaInt(6)
    ));
  }

int lh_gch(lua_State *L) {
  int i = getChar(luaInt(1));
  char c = i == -1 ? 0 : i;
  lua_pushlstring(L, &c, 1);
  return 1;
  }

int lh_gchv(lua_State *L) {
  return retInt(L, getChar(luaInt(1)));
  }

int lh_gco(lua_State *L) {
  return retInt(L, getCol(luaInt(1)));
  }

int lh_gimg(lua_State *L) {
  return retInt(L, getImage(luaInt(1)));
  }

int lh_gba(lua_State *L) {
  return retInt(L, getBak(luaInt(1)));
  }

int lh_gp2(lua_State *L) {
  Get(TileMerge, T, luaInt(1));
  if(!T) return retInt(L, -1);
  return retInt(L, T->t2);
  }

int lh_gavcoba(lua_State *L) {
  retInt(L, getChar(luaInt(1)));
  retInt(L, getCol(luaInt(1)));
  retInt(L, getBak(luaInt(1)));
  return 3;
  }

int lh_tileavcobaf(lua_State *L) {
  int kv = luaInt(1);
  if(kv < 0 || kv >= 256) kv = 32;
  return retInt(L, 
    addMerge(addFill(luaInt(3), 255), addRecolor(luaO(4, Font)->ti[kv], luaInt(2), recDefault), false)
    );
  }

int lh_tileAlpha(lua_State *L) {
  checkArg(L, 2, "tileshadeof");
  int tc = luaInt(1);

  return retInt(L, addFill(tc, luaInt(2)));
  }

int lh_tileRecolor(lua_State *L) {
  checkArg(L, 3, "tilecol");
  return retInt(L, addRecolor(luaInt(1), luaInt(2), luaInt(3)));
  }

//--

int lh_tileSpatial(lua_State *L) {
  checkArg(L, 2, "tilespatial");
  return retInt(L, addSpatial(luaInt(1), luaInt(2)));
  }

int lh_tileLayer(lua_State *L) {
  checkArg(L, 2, "tilelayer");
  return retInt(L, addLayer(luaInt(1), luaInt(2)));
  }

int lh_getlayer(lua_State *L) {
  checkArg(L, 2, "getlayer");
  return retInt(L, distillLayer(luaInt(1), luaInt(2)));
  }

//--

int lh_tileTransform(lua_State *L) {
  checkArg(L, 7, "tilexf");
  return retInt(L, addTransform(
    luaInt(1), 
    int(luaNum(2) * 1440),
    int(luaNum(3) * 1440),
    int(luaNum(4) * 1440),
    int(luaNum(5) * 1440),
    int(luaNum(6) * 1440),
    int(luaNum(7))
    ));
  }

int lh_freeformparam(lua_State *L) {
  checkArg(L, 16, "freeformparam");
  FreeFormParam *P = new FreeFormParam;
  int i=1;
  for(int y=0; y<4; y++) for(int x=0; x<4; x++)
    P->d[y][x] = luaNum(i++);
  return retInt(L, registerObject(P));
  }
  
int lh_tileFreeform(lua_State *L) {
  checkArg(L, 2, "tilefreeform");
  return retInt(L, addFreeform(
    luaInt(1), luaO(2, FreeFormParam)));
  }
  
#endif
