#include <termios.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>

#ifdef __CYGWIN__
#include <ncurses/term.h>
#include <sys/time.h>
#else
#include <term.h>
#endif

#include "mcurses.h"
#include "mdebug.h"

static struct termios gTtySave;
static char gBuf[512][512];
static int gBufAttr[512][512];
static char gBufBfr[512][512];
static int gBufAttrBfr[512][512];
static int gX = 0, gY = 0;
static int gRealX = 0, gRealY = 0;
static int gBufAttrNow = 0;
static int gCurses = 0;

char gKeyMap[kKeyMapMax][6] = {
    {27,   '[',   'A',   0,   0,  0},   // KEY_UP
    {27,   '[',   'B',   0,   0,  0},   // KYE_DOWN
    {27,   '[',   'C',   0,   0,  0},   // KEY_RIGHT
    {27,   '[',   'D',   0,   0,  0},   // KEY_LEFT
    {27,   '[',   '2',  '~',  0,  0},   // KEY_IC
    {127,   0,     0,    0,   0,  0},   // KEY_DC
    {27,    'O',  'H',   0,   0,  0},   // KEY_HOME
    {27,    'O',  'F',   0,   0,  0},   // KEY_END
    {27,   '[',   '5',  '~',  0,  0},   // KEY_PPAGE PageUp
    {27,   '[',   '6',  '~',  0,  0},   // KEY_NPAGE PageDown
//    {8,   0,    0,     0,   0,  0},   // BackSpace
    
    {27,   '[',   '1',  '1', '~', 0},   // F1
    {27,   '[',   '1',  '2', '~', 0},   // F2
    {27,   '[',   '1',  '3', '~', 0},   // F3
    {27,   '[',   '1',  '4', '~', 0},   // F4
    {27,   '[',   '1',  '5', '~', 0},   // F5
    {27,   '[',   '1',  '7', '~', 0},   // F6
    {27,   '[',   '1',  '8', '~', 0},   // F7
    {27,   '[',   '1',  '9', '~', 0},   // F8
    {27,   '[',   '2',  '0', '~', 0},   // F9
    {27,   '[',   '2',  '1', '~', 0},   // F10
    {27,   '[',   '2',  '3', '~', 0},   // F11
    {27,   '[',   '2',  '4', '~', 0}    // F12
};

const int kKeyMapKey[kKeyMapMax] = {
    KEY_UP, KEY_DOWN, KEY_RIGHT, KEY_LEFT, KEY_IC, KEY_DC,
    KEY_HOME, KEY_END, KEY_PPAGE, KEY_NPAGE, 
    KEY_F(1), KEY_F(2), KEY_F(3), KEY_F(4), KEY_F(5),
    KEY_F(6), KEY_F(7), KEY_F(8), KEY_F(9), KEY_F(10),
    KEY_F(11), KEY_F(12)
};

int kKeyMapNum[kKeyMapMax];

void minitscr()
{
    struct termios t;
    int x,y;
    int i, j;

    gCurses = 1;

    tcgetattr(STDIN_FILENO, &gTtySave);
    
    if(setupterm(NULL, STDOUT_FILENO, (int*) 0) == ERR) {
        fprintf(stderr, "invalid TERM setting");
        exit(1);
    }
    
    tcgetattr(STDIN_FILENO, &t);

#ifdef ECHOPRT
    t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE| ECHOK | ECHOKE
                         | ECHONL | ECHOPRT);
#else
    t.c_lflag &= ~(ICANON | ISIG | ECHO | ECHOCTL | ECHOE | ECHOK | ECHOKE
                  | ECHONL);
#endif

    t.c_iflag |= IGNBRK;
    t.c_iflag &= ~(IXOFF|IXON);
    t.c_cc[VMIN] = 0;
    t.c_cc[VTIME] = 0;
    t.c_cc[VLNEXT] = 0;
    t.c_cc[VDISCARD] = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &t);

    for(x=0; x<512; x++) {
        for(y=0; y<512; y++) {
            gBufBfr[y][x] = 0;
            gBufAttrBfr[y][x] = 0;
        }
    }
    mclear();
    
    gRealX = 0;
    gRealY = 0;
    gBufAttrNow = 0;
    
    for(i=0; i<kKeyMapMax; i++) {
        for(j=0; j<6; j++) {
            if(gKeyMap[i][j] == 0) break;
        }
        
        kKeyMapNum[i] = j;
    }
}

void mendwin()
{
/*
    mmove_immediately(0, 0);
    mclear_immediately();
    mrefresh();
*/    

    gCurses = 0;
    tcsetattr(STDIN_FILENO, TCSANOW, &gTtySave);
}

void mclear_immediately()
{
    int x, y;

    putp(tigetstr("clear"));
    
    for(x=0; x<512; x++) {
        for(y=0; y<512; y++) {
            gBufBfr[y][x] = 0;
            gBufAttrBfr[y][x] = 0;
        }
    }

    gX = 0;
    gY = 0;
}

void mmove_immediately(int y, int x)
{
    putp(tparm(tigetstr("cup"), y, x));
    
    gRealX = x;
    gRealY = y;
}

void mclear()
{
   char space[1024];
   char space2[1024];
   int x, y;

   const int maxx = mgetmaxx();
   const int maxy = mgetmaxy();
   
   for(x=0; x<maxx; x++) {
      space[x] = ' ';
   }
   space[x] = 0;
 
#ifdef __CYGWIN__
   for(x=0; x<maxx-1; x++) {
      space2[x] = ' ';
   }
   space2[x] = 0;
    
   mattron(0);
   for(y=0; y<maxy-1; y++) {
      mmvprintw(y, 0, space);
   }
   mmvprintw(maxy-1, 0, space2);
#else   
   mattron(0);
   for(y=0; y<maxy; y++) {
      mmvprintw(y, 0, space);
   }
#endif

    gX = 0;
    gY = 0;
}

void mclear_online(int y)
{
    char space[1024];
    int x;

    const int maxx = mgetmaxx();

#ifdef __CYGWIN__
    const int maxy = mgetmaxy();
    
    if(y == maxy-1) {
        for(x=0; x<maxx-1; x++) {
            space[x] = ' ';
        }
    }
    else {   
        for(x=0; x<maxx; x++) {
            space[x] = ' ';
        }
    }
#else
    for(x=0; x<maxx; x++) {
        space[x] = ' ';
    }
#endif    
    space[x] = 0;
    
    mattron(0);
    mmvprintw(y, 0, space);
}

void mmove(int y, int x)
{
    gX = x;
    gY = y;    
}

int mmvprintw(int y, int x, char* str, ...)
{
    char buf[BUFSIZ];
    int i;
    char* p;

    const int maxx = mgetmaxx();
    const int maxy = mgetmaxy();
    
    va_list args;
    va_start(args, str);
    i = vsprintf(buf, str, args);
    va_end(args);
    
    mmove(y, x);

    p = buf;
    while(*p) {
        gBuf[gY][gX] = *p;
        gBufAttr[gY][gX] = gBufAttrNow;
        gX++;
        if(gX >= maxx) {
            gX = 0;
            gY++;
        }
        p++;
    }
    
    return i;
}

int mprintw(char* str, ...)
{
    char buf[BUFSIZ];
    int i;
    char* p;

    const int maxx = mgetmaxx();
    const int maxy = mgetmaxy();
    
    va_list args;
    va_start(args, str);
    i = vsprintf(buf, str, args);
    va_end(args);

    p = buf;
    while(*p) {
        gBuf[gY][gX] = *p;
        gBufAttr[gY][gX] = gBufAttrNow;
        gX++;
        if(gX >= maxx) {
            gX = 0;
            gY++;
        }
        p++;
    }
    
    return i;
}

int mhas_color()
{
    return tigetstr("setaf") == NULL;
}

void mattron(int attrs)
{
    gBufAttrNow = attrs;
}

void mattroff()
{
    gBufAttrNow = 0;
}

int mgetmaxx()
{
    struct winsize ws;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);

    return ws.ws_col;
}

int mgetmaxy()
{
    struct winsize ws;
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);

#ifdef __CYGWIN__
    return ws.ws_row-1;
#else
    return ws.ws_row;
#endif
}

void mbox(int y, int x, int width, int height)
{
   char hbar[256];
   int i;
   
   hbar[0] = '+';
   for(i=1; i<width-1; i++) {
      hbar[i] = '-';
   }
   hbar[i] = '+';
   hbar[i+1] = 0;

   mmvprintw(y, x, hbar);
   for(i=0; i<height-2; i++) {
      mmvprintw(y + 1 + i, x, "|");
      mmvprintw(y + 1 + i, x + width-1, "|");
   }
   mmvprintw(y + height-1, x, hbar);
}

void mrefresh()
{
    int x, y;
    const int maxx = mgetmaxx();
    const int maxy = mgetmaxy();
    char* p;
    
    for(y=0; y<maxy; y++) {
        for(x=0; x<maxx; x++) {
            if(gBuf[y][x] != gBufBfr[y][x] 
                || gBufAttr[y][x] != gBufAttrBfr[y][x]
                
                || (is_kanji(gBuf[y][x])
                    && (gBuf[y][x] != gBufBfr[y][x]
                        || gBufAttr[y][x] != gBufAttrBfr[y][x]
                        || gBuf[y][x+1] != gBufBfr[y][x+1]
                        || gBufAttr[y][x+1] != gBufAttrBfr[y][x+1]
                        )))
            {
                putp(tparm(tigetstr("cup"), y, x));
    
                if(gBufAttr[y][x] & kCAReverse) putp(tigetstr("rev"));
                if(gBufAttr[y][x] & kCABold) putp(tigetstr("bold"));
                if(gBufAttr[y][x] & kCAUnderline) putp(tigetstr("rmul"));
                if(gBufAttr[y][x] & kCABlack) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 0, 0));
                }
                if(gBufAttr[y][x] & kCARed) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 1, 0));
                }        
                if(gBufAttr[y][x] & kCAGreen) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 2, 0));
                }
                if(gBufAttr[y][x] & kCAYellow) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 3, 0));
                }        
                if(gBufAttr[y][x] & kCABlue) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 4, 0));
                }        
                if(gBufAttr[y][x] & kCAMagenta) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 5, 0));
                }        
                if(gBufAttr[y][x] & kCACyan) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 6, 0));
                }        
                if(gBufAttr[y][x] & kCAWhite) {
                    p = tigetstr("setaf");
                    if(p) putp(tparm(p, 7, 0));
                }        
                if(gBufAttr[y][x] & kCABackBlack) {
                    p = tigetstr("setab");
                    if(p) putp(tparm(p, 0, 0));
                }        
                if(gBufAttr[y][x] & kCABackRed) {
                    p = tigetstr("setab");
                    if(p) putp(tparm(p, 1, 0));
                }        
                if(gBufAttr[y][x] & kCABackGreen) {
                    p = tigetstr("setab");
                    if(p) putp(tparm(p, 2, 0));
                }        
                if(gBufAttr[y][x] & kCABackYellow) {
                    p = tigetstr("setab");
                    if(p) putp(tparm(p, 3, 0));
                }        
                if(gBufAttr[y][x] & kCABackBlue) {
                    p = tigetstr("setab");
                        if(p) putp(tparm(p, 4, 0));
                }        
                if(gBufAttr[y][x] & kCABackMagenta) {
                    p = tigetstr("setab");
                    if(p) putp(tparm(p, 5, 0));
                }
                if(gBufAttr[y][x] & kCABackCyan) {
                    p = tigetstr("setab");
                    if(p) putp(tparm(p, 6, 0));
                }
                if(gBufAttr[y][x] & kCABackWhite) {
                    p = tigetstr("setab");
                    if(p) putp(tparm(p, 7, 0));
                }        

                if(is_kanji(gBuf[y][x])) {
                    putchar(gBuf[y][x]);
                    putchar(gBuf[y][x+1]);
                }
                else {       
                    putchar(gBuf[y][x]);
                }
            
                if(gBufAttr[y][x]) putp(tigetstr("sgr0"));
            }
            
            if(is_kanji(gBuf[y][x])) {
                x++;
            }
        }
    }
    
    putp(tparm(tigetstr("cup"), gRealY, gRealX));
    
    fflush(stdout);

    for(y=0; y<maxy; y++) {
        for(x=0; x<maxx; x++) {
            gBufBfr[y][x] = gBuf[y][x];
            gBufAttrBfr[y][x] = gBufAttr[y][x];
        }
    }    
}

static int gKeyBuf[1024];
static int gKeyBufNum = 0;

int mkbuf_exist() 
{
    if(gKeyBufNum > 0) 
        return 1;
    else
        return 0;
}

int mgetch(int* meta)
{
    int i, j;
    int specialkey_check;
    int result;
    char buf[128];
    char tmp[128];
    int n;
    int n2;
    fd_set mask;
    struct timeval tv;
    int on_the_way;
    int match;
    
static int y = 0;
    
    *meta = 0;
    
    if(gKeyBufNum > 0) {
        while(1) {
            on_the_way = 0;

            /// meta special key ///
            if(gKeyBuf[0] == 27) {
                for(i=0; i<kKeyMapMax; i++) {
                    match = 1;
                        
                    for(j=1; j<gKeyBufNum; j++) {
                        if(gKeyBuf[j] != gKeyMap[i][j-1]) {
                            match = 0;
                            break;
                        }
                    }
                        
                    if(gKeyBufNum-1 == kKeyMapNum[i]) {
                        match = 0;
                    }
    
                    if(match) {
                        on_the_way = 1;
                        break;
                    }
                }
            }
                
            /// special key ///
            for(i=0; i<kKeyMapMax; i++) {
                match = 1;
    
                for(j=0; j<gKeyBufNum; j++) {
                    if(gKeyBuf[j] != gKeyMap[i][j]) {
                        match = 0;
                        break;
                    }
                }
                    
                if(gKeyBufNum == kKeyMapNum[i]) {
                    match = 0;
                }
    
                if(match) {
                    on_the_way = 1;
                    break;
                }
            }
                
            if(on_the_way) {
                usleep(100000);
    
                if((n2 = read(0, tmp, 10)) < 0) {
                    perror("read");
                    exit(1);
                }
                
                if(n2 != 0)
                    for(i=0; i<n2; i++) gKeyBuf[gKeyBufNum++] = tmp[i];
                else
                    break;
            }
            else
                break;
        }

/*
mmvprintw(y, 10, "gKeyBufNum %d", gKeyBufNum);
for(i=0; i<gKeyBufNum; i++) {
mmvprintw(y,i*5+25, "(%d)", gKeyBuf[i]);
}
y++;
*/

        /// special key - meta ///
        if(gKeyBuf[0] == 27) {
            for(i=0; i<kKeyMapMax; i++) {
                specialkey_check = 1;
                    
                if(gKeyBufNum-1 < kKeyMapNum[i]) {
                    specialkey_check = 0;
                }
                else {
                    for(j=0; j<kKeyMapNum[i]; j++) {
                        if(gKeyMap[i][j] != gKeyBuf[j+1]) {
                            specialkey_check = 0;
                            break;
                        }
                    }
                }
                          
                if(specialkey_check) {
                    memmove(gKeyBuf, gKeyBuf + kKeyMapNum[i]+1,
                            sizeof(int)*(gKeyBufNum-kKeyMapNum [i]-1));
                    gKeyBufNum-=(kKeyMapNum[i]+1);
                    *meta = 1;
                    return kKeyMapKey[i];
                }
            }
        }
        
        /// special key ///
        for(i=0; i<kKeyMapMax; i++) {
            specialkey_check = 1;
                
            if(gKeyBufNum < kKeyMapNum[i]) {
                specialkey_check = 0;
            }
            else {
                for(j=0; j<kKeyMapNum[i]; j++) {
                    if(gKeyMap[i][j] != gKeyBuf[j]) {
                        specialkey_check = 0;
                        break;
                    }
                }
            }
                      
            if(specialkey_check) {
                memmove(gKeyBuf, gKeyBuf + kKeyMapNum[i],
                             sizeof(int)*(gKeyBufNum-kKeyMapNum[i]));
                gKeyBufNum-=kKeyMapNum[i];
                return kKeyMapKey[i];
            }
        }
    
        if(gKeyBuf[0] == 27 && gKeyBufNum > 1) {
            result = gKeyBuf[1];
            memmove(gKeyBuf, gKeyBuf + 2, sizeof(int)*(gKeyBufNum-2));
            gKeyBufNum-=2;
        
            *meta = 1;
            return result;
        }
        else {
            if(gKeyBufNum == 1) {
                gKeyBufNum--;
                return gKeyBuf[0];
            }
            else {
                result = gKeyBuf[0];
                memmove(gKeyBuf, gKeyBuf + 1, sizeof(int)*(gKeyBufNum-1));
                gKeyBufNum--;
                return result;
            }
        }
    }
    else {
        while(1) {
            n = 0;
            while(n == 0) {
                if((n = read(0, buf, 10)) < 0) {
                    perror("read");
                    exit(1);
                }
            }

            while(1) {
                on_the_way = 0;

                /// meta special key ///
                if(buf[0] == 27) {
                    for(i=0; i<kKeyMapMax; i++) {
                        match = 1;
                    
                        for(j=1; j<n; j++) {
                            if(buf[j] != gKeyMap[i][j-1]) {
                                match = 0;
                                break;
                            }
                        }
                        
                        if(n-1 == kKeyMapNum[i]) {
                            match = 0;
                        }
    
                        if(match) {
                            on_the_way = 1;
                            break;
                        }
                    }
                }
            

                /// special key ///
                for(i=0; i<kKeyMapMax; i++) {
                    match = 1;
    
                    for(j=0; j<n; j++) {
                        if(buf[j] != gKeyMap[i][j]) {
                            match = 0;
                            break;
                        }
                    }
                    
                    if(n == kKeyMapNum[i]) {
                        match = 0;
                    }
    
                    if(match) {
                        on_the_way = 1;
                        break;
                    }
                }
                
                if(on_the_way) {
                    usleep(100000);
    
                    if((n2 = read(0, tmp, 10)) < 0) {
                        perror("read");
                        exit(1);
                    }

                    if(n2 != 0)
                        for(i=0; i<n2; i++) buf[n++] = tmp[i];
                    else
                        break;
                }
                else
                    break;
            }

/*
mmvprintw(y, 10, "read1 n %d", n);            
for(i=0; i<n; i++) {
mmvprintw(y, i*5 + 25, "(%d)", buf[i]);
}
mrefresh();
y++;
*/

            /// meta special key ///
            if(buf[0] == 27) {
                for(i=0; i<kKeyMapMax; i++) {
                    specialkey_check = 1;
                    
                    if(n-1 < kKeyMapNum[i]) {
                         specialkey_check = 0;
                    }
                    else {
                        for(j=0; j<kKeyMapNum[i]; j++) {
                            if(gKeyMap[i][j] != buf[j+1]) {
                                specialkey_check = 0;
                                break;
                            }
                        }
                    }
                          
                    if(specialkey_check) {
                        if(n-1 > kKeyMapNum[i]) {
                            for(j=kKeyMapNum[i]+1; j<n; j++) {
                                gKeyBuf[gKeyBufNum++] = buf[j];
                            }
                        }

                        *meta = 1;
                        return kKeyMapKey[i];
                    }
                }
            }
            

            /// special key ///
            for(i=0; i<kKeyMapMax; i++) {
                specialkey_check = 1;
                
                if(n < kKeyMapNum[i]) {
                    specialkey_check = 0;
                }
                else {
                    for(j=0; j<kKeyMapNum[i]; j++) {
                        if(gKeyMap[i][j] != buf[j]) {
                            specialkey_check = 0;
                            break;
                        }
                    }
                }
                      
                if(specialkey_check) {
                    if(n > kKeyMapNum[i]) {
                        for(j=kKeyMapNum[i]; j<n; j++) {
                            gKeyBuf[gKeyBufNum++] = buf[j];
                        }
                    }
                    
                    return kKeyMapKey[i];
                }
            }

            //// other key ///
            if(buf[0] == 27 && n>1) {
                for(i=2; i<n; i++) {
                    gKeyBuf[gKeyBufNum++] = buf[i];
                }
                    
                *meta = 1;
                return buf[1];
            }
            else {
                for(i=1; i<n; i++) {
                    gKeyBuf[gKeyBufNum++] = buf[i];
                }
                    
                return buf[0];
            }
        }
    }
}

enum eKanjiCode gKanjiCode = kEuc;

int is_kanji(unsigned char c)
{
    if(gKanjiCode == kEuc) {
        return c >= 0xA1 && c <= 0xFE;
//        return c >= 161 && c <= 254;
    }
    else if(gKanjiCode == kSjis) {
        return c >= 0x81 && c <= 0x9f || c >= 0xE0 && c <= 0xff;
    }
}

int mis_curses()
{
    return gCurses;
}
