// Necklace of the Eye v5.1
// roguelike frontend
// Copyright (C) 2010-2012 Zeno Rogue, see 'noteye.h' for details

// streams to transfer

#define T (*this)

int writeUnc, writeCmp, readUnc, readCmp;

struct ImageAlias : Object {
  Image *img;
  string alias;
  };

void NStream::writeInt(int v) { 
  for(int i=0; i<4; i++) writeChar(v >> (i<<3));
  }
int NStream::readInt() {
  int v = 0;
  for(int i=0; i<3; i++) v += ((unsigned char)readChar()) << (i<<3);
  v += ((signed char)readChar()) << 24;
  return v;
  }

// === out stream ===

void NStream::writeChar(char c) {
  writeUnc++;
  if(!outok) {
    zout.next_in = outbuf_in;
    zout.avail_in = 0;
    outok = true;
    zout.zalloc = Z_NULL;
    zout.zfree = Z_NULL;
    zout.opaque = Z_NULL;
    deflateInit(&zout, Z_DEFAULT_COMPRESSION);
    }
  if(zout.avail_in == BUFSIZE) {
    while(zout.avail_in || !zout.avail_out) {
      zout.avail_out = BUFSIZE;
      zout.next_out = outbuf_out;
      deflate(&zout, 0);
      for(int i=0; i<int(BUFSIZE-zout.avail_out); i++) writeCharPrim(outbuf_out[i]), writeCmp++;
      }
    zout.next_in = outbuf_in;
    }
  outbuf_in[zout.avail_in++] = c;
  }

void NStream::flush() {
  while(zout.avail_in || !zout.avail_out) {
    zout.avail_out = BUFSIZE;
    zout.next_out = outbuf_out;
    deflate(&zout, Z_SYNC_FLUSH);
    for(int i=0; i<int(BUFSIZE-zout.avail_out); i++) writeCharPrim(outbuf_out[i]), writeCmp++;
    }
  zout.next_in = outbuf_in;
  }

// FILE * srttest = fopen("strtest", "wt");

char NStream::readChar() {
  proceed(false);
  if(finished) return 0;
  readUnc++;
  zin.avail_out++;
//  fputc(inbuf_out[cblock], srttest);
  return inbuf_out[cblock++];
  }

bool NStream::eof() {
//  fflush(srttest);
  proceed(true);
  return finished;
  }

bool NStream::ready() {
  proceed(true);
  return finished || zin.avail_out < BUFSIZE;
  }

void NStream::proceed(bool check) {
  if(finished) return;
  if(!inok) {
    zin.avail_in = 0;
    zin.avail_out = BUFSIZE;
    inok = true;
    zin.zalloc = Z_NULL;
    zin.zfree = Z_NULL;
    zin.opaque = Z_NULL;
    inflateInit(&zin);
    }
  while(zin.avail_out == BUFSIZE) {
    zin.next_out = inbuf_out; cblock = 0;
    if(!zin.avail_in) {
      if(check && !readyPrim()) {
        return;
        }
      if(eofPrim()) {
        finished = true;
        inflateEnd(&zin);
        return;
        }
      zin.next_in = inbuf_in;
      inbuf_in[0] = readCharPrim(); readCmp++;
      zin.avail_in++;
      }
    int ret = inflate(&zin, Z_SYNC_FLUSH);
    if(ret == Z_DATA_ERROR) {
      fprintf(errfile, "libz data error\n");      
      finished = true;
      }
    }
  }

void NStream::writeObj(int x) {
  writeInt(x);
  if(x && !knownout.count(x)) {
    knownout.insert(x);

    Get(Image, IM, x);
    if(IM) {
      writeInt(0x01);
      writeStr(IM->title);
      }

    Get(TileImage, TI, x);
    if(TI) {
      writeInt(0x11);
      writeInt(TI->ox);
      writeInt(TI->oy);
      writeInt(TI->sx);
      writeInt(TI->sy);
      writeInt(TI->trans);
      writeInt(TI->chid);
      writeObj(TI->i->id);
      }
    
    Get(TileMerge, TM, x);
    if(TM) {
      writeInt(TM->over ? 0x18 : 0x12);
      writeObj(TM->t1);
      writeObj(TM->t2);
      }
      
    // 0x13 was old recolor

    Get(TileSpatial, TSF, x);
    if(TSF) {
      writeInt(0x14);
      writeInt(TSF->sf);
      writeObj(TSF->t1);
      }
      
    Get(TileTransform, TT, x);
    if(TT) {
      writeInt(0x15);
      writeInt(TT->dx);
      writeInt(TT->dy);
      writeInt(TT->sx);
      writeInt(TT->sy);
      writeObj(TT->t1);
      }
      
    // 0x16-0x17 are reserved for old tilefills
    
    // note: 0x18 is reserved for TileMerge with over

    Get(TileLayer, TL, x);
    if(TL) {
      writeInt(0x19);
      writeInt(TL->t1);
      writeObj(TL->layerid);
      }
    
    Get(TileFill, TFI, x);
    if(TFI) {
      if(TFI->alpha == 0xffffff)
        writeInt(0x17);
      else if(TFI->alpha == 0x808080)
        writeInt(0x16);
      else
        writeInt(0x20), writeInt(TFI->alpha);
      writeInt(TFI->color);
      }              

    Get(TileRecolor, TR, x);
    if(TR) {
      writeInt(TR->mode == recDefault ? 0x13 : 0x21);
      writeInt(TR->color);
      writeObj(TR->t1);
      if(TR->mode != recDefault) writeObj(TR->mode);
      }
    }
  }

void NStream::writeScr(Screen *s) {
  writeInt(s->sx);
  writeInt(s->sy);
  for(int i=0; i<s->sy*s->sx; i++) writeObj(s->v[i]);
  }

void NStream::readScr(Screen *s) {
  int sx = readInt(), sy = readInt();
  s->setSize(sx, sy);
  for(int i=0; i<sy*sx; i++) s->v[i] = readObj();
  }

#ifdef USELUA
static lua_State *LS_image;

int imagenotfound(string s) {
  lua_State *L = LS_image;
  lua_pushvalue(L, -1);
  lua_pushstring(LS_image, s.c_str());
  if (lua_pcall(LS_image, 1, 1, 0) != 0) {
    fprintf(errfile, "error running imagenotfound: %s\n", lua_tostring(LS_image, -1));
    halt();
    }
  int res =luaInt(-1);
  lua_pop(LS_image, 1);
  byId<Image> (res);
  return res;
  }
  
#endif

int NStream::readObj() {
  int id = readInt();
  if(id == 0) return 0;
  if(!knownin.count(id)) {
    int type = readInt();
    int t1, ox, oy, c;
    if(type == 0x1) {
      string s = readStr();
      printf("Image titled %s\n", s.c_str()); fflush(stderr);
      int res = 0;
      for(int i=1; i<size(objs); i++) {
        Image *img = dbyId<Image> (i);
        if(img && img->title == s) { res = i; break; }
        ImageAlias *alias = dbyId<ImageAlias> (i);
        if(alias && alias->alias == s) { res = alias->img->id; break; }
        }
      if(!res) {
        res = imagenotfound(s);
        }
      knownin[id] = res;
      }
    else if(type == 0x11) {
      TileImage *TI = new TileImage;
      TI->ox = readInt();
      TI->oy = readInt();
      TI->sx = readInt();
      TI->sy = readInt();
      TI->trans = readInt();
      TI->chid = readInt();
      TI->i = byId<Image> (readObj());
      knownin[id] = registerObject(TI);
      }
    else if(type == 0x12 || type == 0x18) {
      ox = readObj(); oy = readObj();
      knownin[id] = addMerge(ox, oy, type == 0x18);
      }
    else if(type == 0x13) {
      c = readInt(); t1 = readObj();
      knownin[id] = addRecolor(t1, c, recDefault);
      }
    else if(type == 0x14) {
      TileSpatial TS;
      TS.sf = readInt();
      TS.t1 = readObj();
      knownin[id] = registerTile(TS);
      }
    else if(type == 0x19) {
      TileLayer TL;
      TL.t1 = readInt();
      TL.layerid = readObj();
      knownin[id] = registerTile(TL);
      }
    else if(type == 0x15) {
      TileTransform TT;
      TT.dx = readInt();
      TT.dy = readInt();
      TT.sx = readInt();
      TT.sy = readInt();
      TT.t1 = readObj();
      knownin[id] = registerTile(TT);
      }
    else if(type == 0x16) {
      knownin[id] = addFill(readInt(), 0x808080);
      }
    else if(type == 0x17) {
      knownin[id] = addFill(readInt(), 0xffffff);
      }
    else if(type == 0x19) {
      t1 = readInt(); ox = readInt();
      knownin[id] = addLayer(t1, ox);
      }
    else if(type == 0x20) {
      ox = readInt(); t1 = readInt(); 
      knownin[id] = addFill(t1, ox);
      }
    else if(type == 0x21) {
      c = readInt(); t1 = readObj(); ox = readInt();
      knownin[id] = addRecolor(t1, c, ox);
      }
    else {
      printf("unknown type %2x\n", type);
      exit(1);
      }
    }
  return knownin[id];
  }

NOFStream :: ~NOFStream() {
  if(outok) {
    flush(); 
    deflateEnd(&zout); 
    }
  if(f) fclose(f);
  }

#ifdef USELUA
int lh_writefile(lua_State *L) {
  checkArg(L, 1, "writefile");
  NOFStream *S = new NOFStream;
  S->f = fopen(luaStr(1), "wb");
  if(!S->f) { delete S; return retInt(L, 0); }
  return retObject(L, S);
  }

int lh_readfile(lua_State *L) {
  checkArg(L, 1, "readfile");
  NIFStream *S = new NIFStream;
  S->f = fopen(luaStr(1), "rb");
  if(!S->f) { 
    delete S; 
    return retInt(L, 0); 
    }
  return retObject(L, S);
  }

int lh_writescr(lua_State *L) {
  checkArg(L, 2, "writescr");
  NStream *S = luaO(1, NStream);
  Screen *SC = luaO(2, Screen);
  S->writeScr(SC);
  return 0;
  }

int lh_readscr(lua_State *L) {
  checkArg(L, 3, "readscr");
  NStream *S = luaO(1, NStream);
  Screen *SC = luaO(2, Screen);
  LS_image = L;
  S->readScr(SC);
  return 0;
  }

int lh_writeint(lua_State *L) {
  checkArg(L, 2, "writeint");
  luaO(1, NStream)->writeInt(luaInt(2));
  return 0;
  }

int lh_writestr(lua_State *L) {
  checkArg(L, 2, "writestr");
  luaO(1, NStream)->writeStr(luaStr(2));
  return 0;
  }

int lh_readint(lua_State *L) {
  checkArg(L, 1, "readint");
  lua_pushinteger(L, luaO(1, NStream)->readInt());
  return 1;
  }

int lh_readstr(lua_State *L) {
  checkArg(L, 1, "readstr");
  string s = luaO(1, NStream)->readStr();
  lua_pushstring(L, s.c_str());
  return 1;
  }

int lh_eof(lua_State *L) {
  checkArg(L, 1, "neof");
  lua_pushboolean(L, luaO(1, NStream)->eof());
  return 1;
  }

int lh_flush(lua_State *L) {
  checkArg(L, 1, "nflush");
  luaO(1, NStream)->flush();
  return 0;
  }

int lh_finish(lua_State *L) {
  checkArg(L, 1, "nfinish");
  luaO(1, NStream)->finish();
  return 0;
  }

int lh_ready(lua_State *L) {
  checkArg(L, 1, "nready");
  lua_pushboolean(L, luaO(1, NStream)->ready());
  return 1;
  }
#endif

// == network ==

#ifdef AVOID_NET

#define NETWORK 0

#ifdef USELUA
int lh_connect(lua_State *L) {
  checkArg(L, 2, "connect");
  return retInt(L, 0);
  }

int lh_accept(lua_State *L) {
  checkArg(L, 1, "accept");
  return retInt(L, 0);
  }

int lh_server(lua_State *L) {
  checkArg(L, 1, "server");
  return retInt(L, 0);
  }
#endif

#else

#define NETWORK 1
#include <SDL/SDL_net.h>

struct TCPServer : Object {
  TCPsocket socket;
  ~TCPServer();
  TCPServer(TCPsocket skt) : socket(skt) {}
  };

struct NTCPStream : NStream {
  TCPsocket socket;
  bool closed;
  FILE *f;
  void writeCharPrim(char c);
  char readCharPrim();
  bool readyPrim();
  ~NTCPStream();
  bool eofPrim() { return closed; }
  NTCPStream(TCPsocket skt) : socket(skt), closed(false) {}
  };

NStream *openTCPStream(void *skt) {
  return new NTCPStream(*(TCPsocket*)skt);
  }

void noteye_initnet() {
  static bool todo = true;
  if(todo) {
    todo = false;
    if(SDLNet_Init()==-1) {
      printf("SDLNet_Init: %s\n", SDLNet_GetError());
      exit(2);
      }
    }
  }

SDLNet_SocketSet sktset;

TCPsocket socketinset;

bool NTCPStream::readyPrim() {
  if(!sktset) {
    sktset = SDLNet_AllocSocketSet(1);
    // SDLNet_TCP_AddSocket(sktset,socket);
    }
  if(!sktset) {
    printf("allocsocket error\n");
    exit(1);
    }
  if(SDLNet_TCP_AddSocket(sktset,socket) < 0) return false;
  int ret = SDLNet_CheckSockets(sktset, 0);
  if(SDLNet_TCP_DelSocket(sktset,socket) < 0) return false;
  return ret;
  }

#ifdef USELUA
int lh_server(lua_State *L) {
  checkArg(L, 1, "server");
  IPaddress ip;
  noteye_initnet();
  
  if(SDLNet_ResolveHost(&ip, NULL, luaInt(1)) != 0) {
    printf("SDLNet_ResolveHost: %s\n", SDLNet_GetError());    
    return retInt(L, 0);
    }
  TCPsocket skt = SDLNet_TCP_Open(&ip);
  if(!skt) {
    printf("SDLNet_TCP_Open: %s\n", SDLNet_GetError());
    return retInt(L, 0);
    }

  return retObject(L, new TCPServer(skt));
  }

int lh_accept(lua_State *L) {
  checkArg(L, 1, "accept");
  TCPServer *SERV = luaO(1, TCPServer);
  TCPsocket skt = SDLNet_TCP_Accept(SERV->socket);
  if(!skt) return retInt(L, 0);
  return retObject(L, new NTCPStream(skt));
  }

int lh_connect(lua_State *L) {
  checkArg(L, 2, "connect");
  IPaddress ip;
  
  noteye_initnet();
  if(SDLNet_ResolveHost(&ip, luaStr(1), luaInt(2)) != 0) {
    printf("SDLNet_ResolveHost: %s\n", SDLNet_GetError());    
    return retInt(L, 0);
    }
  TCPsocket skt = SDLNet_TCP_Open(&ip);
  if(!skt) {
    printf("SDLNet_TCP_Open: %s\n", SDLNet_GetError());
    return retInt(L, 0);
    }

  return retObject(L, new NTCPStream(skt));
  }

#endif

#endif


#undef T

#ifndef AVOID_NET
TCPServer::~TCPServer() { if(socket) SDLNet_TCP_Close(socket); }

void NTCPStream::writeCharPrim(char   c) {
  if(SDLNet_TCP_Send(socket,&c,1) < 1) closed = true;
  }

char NTCPStream::readCharPrim() { 
  char c; 
  if(SDLNet_TCP_Recv(socket,&c,1) < 1) closed = true;
  return c;
  }

NTCPStream::~NTCPStream() { SDLNet_TCP_Close(socket); }
#endif

#ifdef USELUA
int lh_imagealias(lua_State *L) {
  checkArg(L, 2, "imagealias");
  ImageAlias *IA = new ImageAlias;
  IA->img = luaO(1, Image);
  IA->alias = luaStr(2);
  return retObject(L, IA);
  }

#endif
