#include "etermviewer.h"
#include "esystem.h"
#include "efile.h"

#include <sys/ioctl.h>
#include <unistd.h>
#include <ncurses.h>

#ifndef MIN
#define MIN(a,b) (a)<(b)?(a):(b)
#endif
#ifndef MAX
#define MAX(a,b) (a)>(b)?(a):(b)
#endif


int etermviewer::lines=0;
int etermviewer::cols=0;
etermviewer* etermviewer::cviewer=0x00;

etermviewer::etermviewer(): cpos(0),hcpos(0),tpos(0),listMode(false),listCursor(-1),inputfd(0),outputfd(1),errorfd(2),text(0x00) 
{
}

void etermviewer::init()
{
  inputfd=0;

  if (!isatty(fileno(stdin))){
    inputfd=dup(0);
    add("Input NOT from a terminal (according to isatty)");
  } else
    add("Input from a terminal (according to isatty)");

  if (!isatty(fileno(stdout))){
    outputfd=dup(1);
    add("Output NOT to a terminal (according to isatty)");
  } else
    add("Output to a terminal (according to isatty)");

  ldieif(!termfd.open("/dev/tty","r+"),"error opening /dev/tty");

  int pfd[2];
  ldieif(pipe(pfd)==-1,"error creating pipe for termviewer");
  dup2(pfd[1],1); // set pipe for getting program output
  outputfd=pfd[0];
  outputf.open(pfd[0],"r");
  outputf.disableBuffer();
  outputf.setNonBlocking();

  errorf.open("error.log","w");
  dup2(errorf.fileno(),2);



//  pipe(pfd);
//  dup2(pfd[1],2); // set pipe for getting program error
//  errorfd=pfd[0];

//  dup2(f.fileno(),0);
//  dup2(f.fileno(),1);
//  termfd.disableBuffer();
//  termfd.setNonBlocking();

  struct sigaction sa;
  sigemptyset(&sa.sa_mask);
//  sigaddset(&sa.sa_mask,SIGWINCH);
  sa.sa_handler=&etermviewer::doWindowResize;
  sa.sa_flags=SA_SIGINFO;
  sigaction(SIGWINCH,&sa,0x00);

  atexit(etermviewer::doExit);

  if (newterm(0x00,termfd.f,termfd.f)==0x00){
    char termsz[256];
    strcpy(termsz,"xterm");
    ldieif(newterm(termsz,termfd.f,termfd.f)==0x00,"error initializing ncurses");
  }
//  initscr();
//  raw();
  cbreak();
  noecho();
  nodelay(stdscr,TRUE);
  keypad(stdscr,TRUE);
  getSystem().addReadCallback(termfd.fileno(),efunc(*this,&etermviewer::doKeyPress),evararray());

//  mousemask(BUTTON1_PRESSED,0x00);
  
  getmaxyx(stdscr,lines,cols);

  if (has_colors()){
    start_color();
    init_pair(30, COLOR_BLACK,   COLOR_BLACK);
    init_pair(31, COLOR_RED,     COLOR_BLACK);
    init_pair(32, COLOR_GREEN,   COLOR_BLACK);
    init_pair(33, COLOR_YELLOW,  COLOR_BLACK);
    init_pair(34, COLOR_BLUE,    COLOR_BLACK);
    init_pair(35, COLOR_CYAN,    COLOR_BLACK);
    init_pair(36, COLOR_MAGENTA, COLOR_BLACK);
    init_pair(37, COLOR_WHITE,   COLOR_BLACK);
  }
  lastcolor=-1;

  doDraw();
}

void etermviewer::doExit()
{
//  if (onExit.isSet())
//    onExit.call();
  endwin();
}

etermviewer::~etermviewer()
{
  endwin();
}
/*
void etermviewer::doDraw()
{
  bool linewrap=true;
  clear();
  if (text!=0x00){
    for (int i=tpos; i<tpos+lines-2 && i<text->size(); ++i){
      int j2;
      int j1=0;
      int maxlen;
      int pos=0;
      estr& line((*text)[i]);
      while (-1!=(j2=line.find("\033[",j1))){
        maxlen=j2-j1;
        if (pos+maxlen >= cols-1) maxlen=cols-1-pos;
        if (maxlen>0)
          mvaddnstr(i-tpos,pos,&line._str[j1],maxlen);
        pos+=j2-j1;
        if (-1==(j1=line.find("m",j2))) { j1=line.len(); break; }
        if (has_colors()){
          estrarray vals(line.substr(j2+2,j1-j2-2).explode(";"));
          if (vals.size()==1 && vals[0]=="0" && lastcolor!=-1){
            attroff(COLOR_PAIR(lastcolor));
            lastcolor=-1;
          }else if (vals.size()==2) {
            int cind=vals[1].i();
            if (cind>=30 && cind <=37) {
              lastcolor=cind;
              attron(COLOR_PAIR(lastcolor));
            }
          }
        }
        ++j1;
      }
      if (j1 < line.len() && pos < cols){
        maxlen=line.len()-j1;
        if (pos+maxlen >= cols-1) maxlen=cols-1-pos;
        if (maxlen>0)
          mvaddnstr(i-tpos,pos,&line._str[j1],maxlen);
      }
    }
  
    if (listMode && listCursor!=-1){
      attron(A_REVERSE);
      mvaddnstr(listCursor-tpos,0,(*text)[listCursor]._str,cols-1);
      attroff(A_REVERSE);
    }
  }

  attron(A_REVERSE);
  mvaddstr(lines-2,0,status._str);
  mvhline(lines-2,status.len(),' ',cols-1);
  if (text!=0x00){
    estr textsize(text->size());
    mvaddstr(lines-2,cols-5-textsize.len(),textsize._str);
    mvhline(lines-2,cols-5,' ',cols-1);
  }
  attroff(A_REVERSE);
  mvaddnstr(lines-1,0,line._str,cols-1);

  if (cpos>line.len()) cpos=line.len();
  move(lines-1,cpos);
  refresh();
}
*/

void etermviewer::doDraw()
{
  cviewer=this;
  bool linewrap=true;
  clear();
  if (text!=0x00){
    earray<estr> wraplines;
    for (int i=tpos; i<tpos+lines-2 && i<text->size(); ++i){
      int hpos=0;
      estr& line((*text)[i]);
      do{
        wraplines.add(line.substr(hpos,MIN(cols,line.len()-hpos)));
        hpos+=MIN(cols,line.len()-hpos);
      } while (hpos<line.len());
    }

    for (int i=wraplines.size()-1,ypos=MIN(lines-3,wraplines.size()-1); i>=0 && ypos>=0 && wraplines.size(); --i,--ypos){
      int j2;
      int j1=0;
      int maxlen;
      int pos=0;
      estr& line(wraplines[i]);
      while (-1!=(j2=line.find("\033[",j1))){
        maxlen=j2-j1;
        if (pos+maxlen >= cols-1) maxlen=cols-1-pos;
        if (maxlen>0)
          mvaddnstr(ypos,pos,&line._str[j1],maxlen);
        pos+=j2-j1;
        if (-1==(j1=line.find("m",j2))) { j1=line.len(); break; }
        if (has_colors()){
          estrarray vals(line.substr(j2+2,j1-j2-2).explode(";"));
          if (vals.size()==1 && vals[0]=="0" && lastcolor!=-1){
            attroff(COLOR_PAIR(lastcolor));
            lastcolor=-1;
          }else if (vals.size()==2) {
            int cind=vals[1].i();
            if (cind>=30 && cind <=37) {
              lastcolor=cind;
              attron(COLOR_PAIR(lastcolor));
            }
          }
        }
        ++j1;
      }
      if (j1 < line.len() && pos < cols){
        maxlen=line.len()-j1;
        if (pos+maxlen >= cols-1) maxlen=cols-1-pos;
        if (maxlen>0)
          mvaddnstr(ypos,pos,&line._str[j1],maxlen);
      }
    }
  
    if (listMode && listCursor!=-1){
      attron(A_REVERSE);
      mvaddnstr(listCursor-tpos,0,(*text)[listCursor]._str,cols-1);
      attroff(A_REVERSE);
    }
  }

  attron(A_REVERSE);
  mvaddstr(lines-2,0,status._str);
  mvhline(lines-2,status.len(),' ',cols-1);
  if (text!=0x00){
    estr textsize(text->size());
    mvaddstr(lines-2,cols-5-textsize.len(),textsize._str);
    mvhline(lines-2,cols-5,' ',cols-1);
  }
  attroff(A_REVERSE);
  if (cpos>line.len()) cpos=line.len();
  if (hcpos>line.len()-cols+1) hcpos=MAX(line.len()-cols+1,0);
  else if (cpos-hcpos>cols-1) hcpos=MAX(cpos-cols+1,0);
  else if (cpos<hcpos) hcpos=cpos;
    
  mvaddnstr(lines-1,0,&line._str[hcpos],cols-1);
  move(lines-1,cpos-hcpos);

  refresh();
}

void etermviewer::setText(earray<estr>* _text)
{
  text=_text;
  if (text->size() < lines-2)
    tpos=0;
  else 
    tpos=text->size()-(lines-2)/2;
//  if (tpos<0) tpos=0;
}

void etermviewer::update()
{
  if (text==0x00) return;

  if ((text->size()-tpos)/(lines-2)>=1){
    tpos=text->size()-text->size()%((lines-2)/2)-(lines-2)/2;
//    tpos+=(lines-2)/2;
  }
  doDraw();
}

void etermviewer::add(const estr& line)
{
  if (text==0x00) return;

  text->add(line);
  if ((text->size()-tpos)/(lines-2)>=1 && (text->size()-tpos)%(lines-2)==1){
    tpos+=(lines-2)/2;
    doDraw();
  }else{
    mvaddstr(text->size()-tpos-1,0,line._str);
    refresh();
  }
}
void etermviewer::add(const earray<estr>& addlines)
{
  if (text==0x00) return;

  for (int i=0; i<addlines.size(); ++i)
    text->add(addlines[i]);
  if ((text->size()-tpos)/(lines-2)>=1 && (text->size()-tpos)%(lines-2)==1){
    tpos+=(lines-2)/2;
    doDraw();
  }else{
    mvaddstr(text->size()-tpos-1,0,line._str);
    refresh();
  }
}

void etermviewer::textPageUp()
{
  if (text==0x00) return;
  if (tpos==0) return;

  tpos-=(lines-2)/2;
  if (tpos<0) tpos=0;

  cerr << tpos << endl;

  doDraw();
}

void etermviewer::textPageDown()
{
  if (text==0x00) return;
  if (tpos==text->size()-text->size()%((lines-2)/2)-(lines-2)/2) return;

  tpos+=(lines-2)/2;
  if (tpos>=text->size()) tpos=text->size()-text->size()%((lines-2)/2)-(lines-2)/2;
  if (tpos<0) tpos=0;

  cerr << tpos << endl;

  doDraw();
}


void etermviewer::listChoice(int choice)
{

}

void etermviewer::doKeyPress()
{
  int c;
//  text.add("doKeyPress");
//  doDraw();
  do {
    c=getch();
    if (c==ERR) break;

    if (cpos>line.len()) cpos=line.len();
//    text.add("doKeyPress: "+estr(c));
//    doDraw();
    if (onKeyPress.isSet()){
      evar res(onKeyPress.call(evararray(this,c)));
      if (res.getTypeid()==typeid(bool) && !res.get<bool>()) continue; // do not process char if return is false
    }
    switch (c){
      case KEY_MOUSE:{
        MEVENT mevent;
        if (getmouse(&mevent)==OK && (mevent.bstate&BUTTON1_PRESSED))
          mvprintw(0,0,"mouse click at: %i,%i",mevent.x,mevent.y);
        refresh();
      }; break;
/*      case ERR:{
        getmaxyx(stdscr,lines,cols);
        clear();
        attron(A_REVERSE);
        mvhline(lines-2,0,' ',cols-1);
        mvprintw(lines-2,0," terminal resized: %i,%i",lines,cols);
        attroff(A_REVERSE);
        mvaddstr(lines-1,0,line._str);
        move(lines-1,cpos);
        refresh();
      }; break;
      case KEY_RESIZE:{
        getmaxyx(stdscr,lines,cols);
        clear();
        attron(A_REVERSE);
        mvhline(lines-2,0,' ',cols-1);
        mvprintw(lines-2,0," terminal resized: %i,%i",lines,cols);
//        mvaddstr(lines-2,0," terminal resized");
        attroff(A_REVERSE);
        mvaddstr(lines-1,0,line._str);
        move(lines-1,cpos);
        refresh();
      }; break;
*/
      case 0x09:{ // tab
        
/*
        text.clear();
        tpos=0;
        int i;
        for (i=getLogger().dbpos; i!=getLogger().depos; i=(i+1)%getLogger().debuglines.size()){
          text.add(getLogger().debuglines[i].text());
        }
        doDraw();
*/
      }; break;
      case 0x01:{ // Ctrl+A

      } break;
      case 0x04:{ // Ctrl+D
        if (line.len()==0) { endwin(); exit(0); }
      }; break;
      case 0x0A:{ // Enter
        if (onEnter.isSet())
          onEnter.call(evararray(this,line));
/*
        textAddLine(line);
        mvhline(lines-1,0,' ',line.len());
        line.clear();
        cpos=0;
        move(lines-1,0);
        refresh();
*/
      }; break;
      case KEY_BACKSPACE:
      case 0x007f:{
        if (cpos==0) break;
        mvhline(lines-1,0,' ',line.len());
        line.del(cpos-1,1);
        --cpos;
        mvaddstr(lines-1,0,line._str);
        move(lines-1,cpos);
        refresh();
      }; break;
      case KEY_DC:{
        if (cpos==line.len()) break;
        mvhline(lines-1,0,' ',line.len());
        line.del(cpos,1);
        mvaddstr(lines-1,0,line._str);
        move(lines-1,cpos);
        refresh();
      }; break;
      case KEY_NPAGE:{
        textPageDown();
      }; break;
      case KEY_PPAGE:{
        textPageUp();
      }; break;
      case KEY_DOWN:{
/*
        if (listMode){
          if (listCursor+1==text.size()) break;

          if (listCursor!=-1)
            mvaddstr(listCursor-tpos,0,text[listCursor]._str);
          ++listCursor;
          if (listCursor>tpos+lines-2)
            textPageDown();
          attron(A_REVERSE);
          mvaddstr(listCursor-tpos,0,text[listCursor]._str);
          attroff(A_REVERSE);
          refresh();
        }else
          textPageDown();
*/
      }; break;
      case KEY_UP:{
/*
        if (listMode){
          if (listCursor==0) break;

          if (listCursor!=-1)
            mvaddstr(listCursor-tpos,0,text[listCursor]._str);
          --listCursor;
          if (listCursor<tpos)
            textPageUp();
          attron(A_REVERSE);
          mvaddstr(listCursor-tpos,0,text[listCursor]._str);
          attroff(A_REVERSE);
          refresh();
        }else{
          textPageUp();
        }
*/
      }; break;
      case KEY_LEFT:{
        if (cpos==0) break;
        --cpos;
        move(lines-1,cpos);
        refresh();
      }; break;
      case KEY_RIGHT:{
        if (cpos==line.len()) break;
        ++cpos;
        move(lines-1,cpos);
        refresh();
      }; break;
      case ERR:{
//         lerror("ncurses error on getc");
        break;
      }
      default:
//        attron(A_REVERSE);
//        mvprintw(lines-2,0,"  0x%.4x",c);
//        attroff(A_REVERSE);
  
        if (!(c&KEY_CODE_YES)){
          if (cpos==line.len())
            line+=(char)c;
          else
            line=line.substr(0,cpos)+estr((char)c)+line.substr(cpos);
          ++cpos;
          mvaddstr(lines-1,0,line._str);
          move(lines-1,cpos);
          refresh();
          break;
        }
        move(lines-1,cpos);
    }
  } while (c!=ERR);

  doDraw();
}

void etermviewer::doWindowResize(int)
{
  endwin();
  refresh();
  getmaxyx(stdscr,lines,cols);
  if (cviewer)
    cviewer->doDraw();
/*
//  printf("RESIZED WINDOW");
  struct winsize ws;
  ioctl(1, TIOCGWINSZ, &ws);

  lines=ws.ws_row;
  cols=ws.ws_col;
  cerr << "lines: " << lines << " cols: " << cols << endl;
//  resizeterm(lines,cols);
//  doDraw();
*/
}

