// Necklace of the Eye v3.0
// roguelike frontend
// Copyright (C) 2010-2011 Zeno Rogue, see 'noteye.h' for details

// 'Internal process', used for integration.

#define CURSES_CONSTONLY
#include "noteye-curses.h"

lua_State *internalstate;
lua_State *uithread;

// ----

#include <unistd.h>
#include <fcntl.h>
// #include <pty.h>
#include <errno.h>
#include <ctype.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
// #include <utmp.h>

#include <stdlib.h>

#include <sys/types.h>
// #include <sys/wait.h>


#define MAX_ARGS 16

InternalProcess *P;

#define KEY_F0 256

void initScreen() {
  }

void noteye_refresh() {
  if(uithread) {
    int status = lua_resume(uithread,0);
    if(status != LUA_YIELD) {
      fprintf(errfile, "error: did not yield: %s\n", lua_tostring(internalstate, -1));
      halt();
      }
    }
  
  else {      
    lua_getglobal(internalstate, CALLUI);
    // lua_pushvalue(internalstate, 2);
    if (lua_pcall(internalstate, 0, 1, 0) != 0) {
      fprintf(errfile, "error running mainloopcyc: %s\n", lua_tostring(internalstate, -1));
      halt();
      }
    lua_pop(internalstate, 1);
    }
  }

int halfdelaymode = -1;

void noteye_halfdelay(int i) { halfdelaymode = 100 * i; }
void noteye_cbreak() { halfdelaymode = -1; }

int nextdelay = 0;

int noteye_getch() {
  long long nextdelay;
  if(halfdelaymode >= 0) nextdelay = SDL_GetTicks() + halfdelaymode;
  else nextdelay = 0;
  
  while(P->kbs == P->kbe) {
    if(nextdelay && SDL_GetTicks() >= nextdelay)
      return -1;
    noteye_refresh();
    }
  int ret = P->kbuf[P->kbs];
  P->kbs = (1+P->kbs) % IBUFSIZE;
  return ret;
  }

void noteye_addch(char ch) {
  P->changed = true;
  if(ch == '\n') {
    P->curx = 0;
    if(P->cury < P->s->sy-1) P->cury++;
    return;
    }
  if(P->curx < P->s->sx)
    P->s->get(P->curx, P->cury) = 
        addMerge(
          P->brushback,
          addRecolor(P->f->ti[ch & 0xFF], P->fore, recDefault),
          false
          );
  P->curx++;
  }

void noteye_addstr(const char *buf) { 
  while(*buf) noteye_addch(*buf), buf++;
  }

void noteye_endwin() { }

void noteye_erase() { 
  P->changed = true;
  for(int y=0; y<P->s->sy; y++) for(int x=0; x<P->s->sx; x++) 
    P->s->get(x, y) = P->brush0;
  P->curx=0; P->cury=0;
  }
void noteye_move(int y, int x) {
  P->changed = true;
  P->curx=x; P->cury=y;
  if(P->curx<0) P->curx=0;
  if(P->curx>=P->s->sx) P->curx=P->s->sx-1;
  if(P->cury<0) P->cury=0;
  if(P->cury>=P->s->sy) P->cury=P->s->sy-1;
  }
void noteye_clrtoeol() { 
  P->changed = true;
  if(P->cury<P->s->sy)
  for(int x=P->curx; x<P->s->sx; x++)
    P->s->get(x, P->cury) = P->brush0;
  }

void noteye_mvaddch(int y, int x, char ch) {
  noteye_move(y, x); noteye_addch(ch);
  }

void noteye_mvaddstr(int y, int x, const char *buf) {
  noteye_move(y,x); noteye_addstr(buf);
  }

void InternalProcess::setColor(int _fore, int _back) {
  // brush.back = vgacol[ mtrans[back] ];
  back = _back;
  fore = _fore;
  brushback = addFill(back, 0xffffff);
  brush0 = addMerge(brushback, addRecolor(f->ti[32], fore, 0xffffff), false);
  }

void setTextAttr32(int fore, int back) {
  P->setColor(fore, back);
  }

void setTextAttr(int fore, int back) {
  setTextAttr32(vgacol[fore & 15], vgacol[back & 15]);
  }

int getVGAcolor(int c) {
  return vgacol[c & 15];
  }

#undef P


// ---

InternalProcess::InternalProcess(Screen *scr, Font *f, const char *cmdline) : Process(scr, f, cmdline) {

  isActive = true; 
  curx = 0; cury = 0; 
  setColor(vgacol[7], 0);
  kbs = 0; kbe = 0;
  evs = 0; eve = 0;
  for(int i=0; i<EBUFSIZE; i++) evbuf[i] = NULL;
  
  for(int x=0; x<scr->sx; x++) for(int y=0; y<scr->sy; y++)
    scr->get(x,y) = brush0;
  }

InternalProcess::~InternalProcess() {
  for(int i=0; i<EBUFSIZE; i++) if(evbuf[i]) delete evbuf[i];  
  }

void InternalProcess::sendKey(int symbol, int mod, bool down, int unicode) {

  int neve = (eve + 1) % IBUFSIZE;
  if(neve != evs) {
    SDL_Event *ev = new SDL_Event;
    ev->type = down ? SDL_KEYDOWN : SDL_KEYUP;
    SDL_KeyboardEvent& kev(ev->key);
    kev.keysym.sym = (SDLKey) symbol;
    kev.keysym.mod = (SDLMod) mod;
    kev.keysym.unicode = unicode;
    evbuf[eve] = ev;
    eve = neve;
    // todo: free them if unused
    }

  int nkbe = (kbe + 1) % IBUFSIZE;
  if(nkbe == kbs) return;
  
  int retkey;

  if(!down) return;

  int sym = symbol;
  
  char ac = unicode;
  
  // bool shift = mod & (KMOD_LSHIFT|KMOD_RSHIFT);
  // bool ctrl  = mod & (KMOD_LCTRL|KMOD_RCTRL);

  // #define Shift(k0, k1) if(shift && ac == k0) ac = k1;

  /*
  if(shift && ac >= 'a' && ac <= 'z')
    ac -= 32;
  
  if(ctrl && ac >= 'a' && ac <= 'z')
    ac &= 31;
  
  #define Shift(k0, k1) \
    if(shift && ac == k0) ac = k1;
  
  Shift(',', '<')
  Shift('.', '>')
  Shift('/', '?')
  Shift(';', ':')
  Shift('\'', '"')
  Shift('[', '{')
  Shift(']', '}')
  Shift('-', '_')
  Shift('=', '+')
  Shift('1', '!')
  Shift('2', '@')
  Shift('3', '#')
  Shift('4', '$')
  Shift('5', '%')
  Shift('6', '^')
  Shift('7', '&')
  Shift('8', '*')
  Shift('9', '(')
  Shift('0', ')')
  */
  
  #define Snd(key, x) else if(sym == SDLK_ ## key) retkey = x;
  
  if(sym == SDLK_LSHIFT) return;
  else if(sym == SDLK_RSHIFT) return;
  else if(sym == SDLK_LCTRL) return;
  else if(sym == SDLK_RCTRL) return;
  else if(sym == SDLK_LALT) return;
  else if(sym == SDLK_RALT) return;
  else if(sym == SDLK_CAPSLOCK) return;

  else if(sym == SDLK_RETURN) retkey = '\n';
  Snd(TAB, '\t')
  Snd(F1, KEY_F0+1)
  Snd(F2, KEY_F0+2)
  Snd(F3, KEY_F0+3)
  Snd(F4, KEY_F0+4)
  Snd(F5, KEY_F0+5)
  Snd(F6, KEY_F0+6)
  Snd(F7, KEY_F0+7)
  Snd(F8, KEY_F0+8)
  Snd(F9, KEY_F0+9)
  Snd(F10, KEY_F0+10)
  Snd(F11, KEY_F0+11)
  Snd(F12, KEY_F0+12)

  // linux terminal
  Snd(UP, D_UP)
  Snd(DOWN, D_DOWN)
  Snd(RIGHT, D_RIGHT)
  Snd(LEFT, D_LEFT)
  Snd(HOME, D_HOME)
  Snd(END, D_END)
  Snd(PAGEUP, D_PGUP)
  Snd(PAGEDOWN, D_PGDN)

  Snd(KP8, D_UP)
  Snd(KP2, D_DOWN)
  Snd(KP6, D_RIGHT)
  Snd(KP4, D_LEFT)
  Snd(KP7, D_HOME)
  Snd(KP1, D_END)
  Snd(KP9, D_PGUP)
  Snd(KP3, D_PGDN)
  Snd(KP5, D_CTR)

  #undef Snd
/*
  Snd(KP0, "\x1bOp")
  Snd(KP1, "\x1bOq")
  Snd(KP2, "\x1bOr")
  Snd(KP3, "\x1bOs")
  Snd(KP4, "\x1bOt")
  Snd(KP5, "\x1bOu")
  Snd(KP6, "\x1bOv")
  Snd(KP7, "\x1bOw")
  Snd(KP8, "\x1bOx")
  Snd(KP9, "\x1bOy")
*/

  else retkey = ac;

  kbuf[kbe] = retkey;
  kbe = nkbe;
  }

bool InternalProcess::checkEvent(lua_State *L) {

  if(!isActive) {
    lua_newtable(L);
    setfield(L, "type", evProcQuit);
    setfield(L, "obj", P->id);
    setfield(L, "exitcode", exitcode);
    return true;
    }

  if(changed) {
    changed = false;
    lua_newtable(L);
    setfield(L, "type", evProcScreen);
    setfield(L, "obj", id);
    return true;
    }
  
  return false;
  }

#ifdef USELUA

int lh_internal(lua_State *L) {
  checkArg(L, 3, "internal");

  P = new InternalProcess(luaO(1, Screen), luaO(2, Font), luaStr(3));
  
  return retObject(L, P);
  }

#endif

void noteye_setinternal(InternalProcess *Proc, lua_State *L, int spos) {
  P = Proc;
  internalstate = L;
  lua_pushvalue(L, spos);
  lua_setglobal(L, CALLUI);
  }

InternalProcess *noteye_getinternal() {
  return P;
  }

void noteye_finishinternal(int exitcode) {
  P->exitcode = exitcode;
  P->isActive = false;
  }

int lh_uicreate(lua_State *L) {
  // kill the old thread?
  lua_setglobal(L, "threadtemp");
  uithread = lua_newthread(L);
  lua_getglobal(uithread, "threadtemp");
  int status = lua_resume(uithread, 0);
  if (status != LUA_YIELD) {
    fprintf(errfile, "error running uicreate: %s\n", lua_tostring(uithread, -1));
    halt();
    }
  return 1;
  }

int lh_uisleep(lua_State *L) {
  return lua_yield(L,0);
  }

void noteye_uiresume() {
  if(!uithread) {
    fprintf(errfile, "no UI thread to resume\n");
    halt();
    }
  int status = lua_resume(uithread, 0);
  if(status != LUA_YIELD) {
    fprintf(errfile, "uifinish did not finish thread: %s\n", lua_tostring(uithread, -1));
    halt();
    }
  }

void noteye_uifinish() {
  if(!uithread) {
    fprintf(errfile, "no UI thread to finish\n");
    halt();
    }
  int status = lua_resume(uithread, 0);
  if(status != 0) {
    fprintf(errfile, "uiresume did not yield: %s\n", lua_tostring(uithread, -1));
    halt();
    }
  uithread = NULL;
  }
