#include <stdio.h>      
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <netdb.h>
#include <sys/time.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <ctype.h>
#include "bgrab.h"

#define  PORT       5060
#define  BUFLEN     1024
#define  UTLEN      4096
#define  UTCAL      0.01
#define  UTREADINTV 10000
#define  UTSPEED    3

// asciigrab vars

#define WIDTH   80
#define HEIGHT  48
#define DEPTH   2

int                 lasttry, activeFlag;
int                 i;    
fd_set              rfd;
fd_set              c_rfd;
fd_set              wfd;
fd_set              c_wfd;
int                 dsize;
int                 file_ut, file_sp, file_st, file_ac, file_sc; // untertitel, spiel, spielstand filehandler
char                utBuf[UTLEN] = "";
char                oldUTBuf[UTLEN] = "";
char                ut[WIDTH+1];
float               utSum = 0;
char                spiel[WIDTH+1] = "";
char                spielstand[WIDTH] = "";
char                screen[(WIDTH*HEIGHT+HEIGHT)*2];

char locateZero[]   = "\033[H";
//char locateZero[]   = "\E[H\E[0;0H";
char cls[]          = "\E[H\E[2J";
  

static int reverse_ascii;

/* Gray2ASCII convertion table */

char *intens[16] = {
    "   ...,,,vvuuwww",
    "   ...,,,vvuuwww",
    "  ..,:,,,vvvuuww",
    " ..,:::,;;vvvuww",
    "'',::::o;;;;nnww",
    "''::::ooo;;;nnmw",
    "'''::ooooo;yynmm",
    "''''oooooOOyyymm",
    "^^YYYoooOOOOyyyy",
    "^^^YYYoOOOO%%yyy",
    "^^^^YYYOOO%%%%yy",
    "\"^^^YYYYO%%%$$$y",
    "\"\"^PPPRRR%%$$$$#",
    "\"\"\"PPPPRRR$$$$##",
    "\"\"\"\"PPPPRRR$$###",
    "\"\"\"\"\"PPPPRRR####",
   };

char *help =
"Usage: asciilive [options]\n"
"options:\n"
"-h    this help\n"
"-s    size                        default: 126x94\n"
"-n    norm: pal|ntsc              default: pal\n"
"-c    channel: tuner|composite    default: tuner\n"
"-d    device:                     default: /dev/video\n";
    
void setNonBlocking(int sock) {

   int opts;

   opts = fcntl(sock,F_GETFL);
   if (opts < 0) {
      perror("fcntl(F_GETFL)");
      exit(1);
   }
   opts = (opts | O_NONBLOCK);
   if (fcntl(sock,F_SETFL,opts) < 0) {
      perror("fcntl(F_SETFL)");
      exit(1);
   }
   return;
}

void signalHandler(int sig) {

    switch(sig){
    
        case SIGPIPE:
            printf("SIGPIPE\n");
            printf("closing handler: %d\n",lasttry);
            close(lasttry);
            FD_CLR(lasttry, &wfd);
            break; 
        case SIGINT:
            printf("closing handlers...\n");
            for (i=0; i<dsize; i++)
                if (i != 1) close(i);
            printf("done.\n");          
            exit(0);
            break;
    } 
}

float calcUTsum (char *p) {

    int j = 1;
    float mySUM = 0;

    while (p[0] != 0) {
    
        mySUM += (UTCAL * j * p[0]);
        p++;
        j++;
    }
    return mySUM;
}

void readUT() {

    char newUTBuf[UTLEN];
    float mySum;
    
    // printf("reading untertitel\n");
    
    if ((file_ut = fopen( "./untertitel.txt","r")) == NULL) { 
    
        printf("Can't open %s\n","untertitel.txt");
        strcpy(newUTBuf, "");				 
    } else {
    
        fgets(newUTBuf, UTLEN, file_ut);    
        fclose(file_ut);
    }
    
    // printf("read: %s\n",newUTBuf);
    
    mySum = calcUTsum(newUTBuf);
    
    // printf("mySum = %f\n", mySum);
    
    if (mySum != utSum) {
   
        utSum = mySum;
        strcat(utBuf, newUTBuf);
    }
    
    // printf("written: %s\n", utBuf);  
}

void readSpielUndStand() {

    
    if ((file_sp = fopen( "./spiel.txt","r")) == NULL) { 
    
        printf("Can't open %s\n","spiel.txt");
        strcpy(spiel, "");				 
    } else {
    
        fgets(spiel, WIDTH, file_sp);    
        fclose(file_sp);
    }

    if ((file_st = fopen( "./spielstand.txt","r")) == NULL) { 
    
        printf("Can't open %s\n","spielstand.txt");
        strcpy(spielstand, "");				 
    } else {
    
        fgets(spielstand, WIDTH, file_st);    
        fclose(file_st);
    }
    
    while (strlen(spiel) < (WIDTH - strlen(spielstand))) {
    
        strcat(spiel, " ");
    }
    strcat(spiel, spielstand);
    strcat(spiel,"\n");

    if ((file_ac = fopen( "./active","r")) == NULL) { 
    
        // printf("Can't open %s\n","active.txt");
        activeFlag = 0;			 
    } else {
    
        activeFlag = 1;
        fclose(file_ac);
    }
}

void makeUT() {

    sprintf(ut,"%s","\0");
    strncpy(ut, utBuf, WIDTH);
    
    // advance string
    char *p = utBuf;
    p++;
    if (p[0] != 0) {
        strcpy(utBuf, p);
        // printf("utBuf: %s\n",utBuf);
    } else {
    
       sprintf(utBuf,"%s", "\0"); 
    }
    while (strlen(utBuf) < WIDTH) { 
    
        strcat(utBuf, " ");
    }
    
}

   
int main(int argc, char *argv[])
{
    int                 rc;      
    int                 s;
    int                 cs;
    char                buf[BUFLEN+1];
    struct sockaddr_in  sa;
    struct sockaddr_in  csa;
    int                 size_csa;
    int                 utcount = UTREADINTV;
    int                 utspeed = UTSPEED;

    struct fgdevice     fg;
    unsigned short      *curpos, *curpos2;
    int                 r,g,b,x,y;
    char                aline[1024], *curchar;
    char                emptyLine[WIDTH+1] = "";
    char                standLine[WIDTH+1] = "";
    
    // init emptyLine
    
    while(strlen(emptyLine) < WIDTH) { strcat(emptyLine, " "); }
    
    // int in_loop=1;
    /*int contrast, brightness;*/
    int framecount = 0;

    int opt = 0;
    char *short_options = "hw:n:s:c:d:";

    // default values
    int width = WIDTH;
    int height = HEIGHT;
    int norm = VIDEOMODE_PAL;
    int channel = CHANNEL_TUNER;
    char *device = "/dev/video";

    activeFlag = 1;

    // reserve screen buffer
    int screenbuffersize = (width * height + height) * 2;
    printf("screenbuffersize = %d\n", screenbuffersize);
    char *screenbuffer = (char *)malloc((screenbuffersize)*sizeof(*screenbuffer));

    // get + prozess options
    
    do {
        opt = getopt(argc, argv, short_options);

        switch (opt)
        {

        case 'c':
            if (strcmp (optarg, "tuner") == 0)
                channel = CHANNEL_TUNER;
            else if (strcmp (optarg, "composite") == 0)
                channel = CHANNEL_COMPOSITE;
            else
                {
                fprintf (stderr, "invalid video norm selected!\n");
                exit(1);
            }
            break;

        case 'n':
            if (strcmp (optarg, "pal") == 0)
                norm = VIDEOMODE_PAL;
            else if (strcmp (optarg, "ntsc") == 0)
                norm = VIDEOMODE_NTSC;
            else
                {
                fprintf (stderr, "invalid video norm selected!\n");
                exit(1);
            }
            break;

        case 'd':
            device = optarg;
            break;

        case 's':
            {
            char *t;
            char *tt;
            t = optarg;
            while (isdigit (*t))
                t++;
            *t = 0;
            width = atoi (optarg);
            tt = ++t;
            while (isdigit (*tt))
                tt++;
            *tt = 0;
            height = atoi (t);
            }
            break;

        case 'h':
            fprintf (stderr, "%s", help);
            exit (1);
            break;
        }
    } while (opt > 0);
    
    // end options

    // we do not want anybody to disturb us

    signal(SIGPIPE,signalHandler);
    //signal(SIGINT,signalHandler);

    // get + form an address

    memset(&sa, 0, sizeof(sa));
    sa.sin_family = AF_INET;
    sa.sin_port = htons(PORT);
    sa.sin_addr.s_addr = INADDR_ANY;

    // socket

    s = socket(AF_INET, SOCK_STREAM, 0);
    if (s < 0) {
        perror("socket: allocation failed");
    }

    // no blocking - if we want it to
    setNonBlocking(s);

    // bind socket
    rc = bind(s, (struct sockaddr *)&sa, sizeof(sa));

    if (rc) {
        perror("bind");
    }

    // listen - how obvious
    rc = listen(s, 5);

    if (rc) {
       perror("listen");
    }

    size_csa = sizeof(csa);
    dsize = getdtablesize();

    // close all filehandlers except stout - so no one can block us
    for (i=0; i<dsize; i++)
        if ((i != s) && (i != 1))
          close(i);

    // zero out filehandler tables

    FD_ZERO(&rfd);
    FD_ZERO(&wfd);
    FD_SET(s, &rfd);

    // setting device + channel

    if (fg_open_device (&fg, device)!=0) exit(-1);
    if (fg_set_channel (&fg,channel,norm)!=0) exit(-1);

    // and start to grab
    if (fg_start_grab_image(&fg, width, height, FORMAT_RGB565)!=0) exit(-1);

    // do it
    printf("starting loop\n");

    while (1) {
    
        // get untertitel
        
        if (utcount >= UTREADINTV) {
        
            readUT();
            readSpielUndStand();
        } else {
        
            utcount++;
        }
        // grab it
            
       if (fg_get_next_image(&fg)!=NULL) {
       
            // Count the frames
            // framecount++;
            
            // reset buffer
            sprintf(screenbuffer, "%s", "\0");

            // Determine 4bit luminance from 565bit RGB "in-place"
            curpos=(unsigned short *)fg.current_image;
            if (reverse_ascii) {
                for (i=0; i<fg.image_pixels; i++) {
                    r=(*curpos >> 11);
                    g=((*curpos >> 5) & 63);
                    b=(*curpos & 31);
                    *curpos=15-(r*2450+g*2404+b*934)/16384;
                    curpos++;
                }
            } else {
                for (i=0; i<fg.image_pixels; i++) {
                    r=(*curpos >> 11);
                    g=((*curpos >> 5) & 63);
                    b=(*curpos & 31);
                    *curpos=(r*2450+g*2404+b*934)/16384;
                    curpos++;
                }
            }

            // Output ASCII matrix from Y4 line pairs
            curpos=(unsigned short *)fg.current_image;
            curpos2=curpos;
            curpos2+=fg.width;
            aline[fg.width]=0;


            if (activeFlag == 1) {
            
                // render spiel + spielstand

               //strcat(screenbuffer,emptyLine);
               //strcat(screenbuffer,"\n");
               strcat(screenbuffer,spiel);
               strcat(screenbuffer,emptyLine);
               strcat(screenbuffer,"\n");
                
                for (y=0; y<(fg.height/2 - 2); y++) {
                    curchar=aline;
                    for (x=0; x<fg.width; x++) {
                        *curchar=intens[*curpos][*curpos2];
                        curchar++;
                        curpos++;
                        curpos2++;
                    }
                    curpos += fg.width;
                    curpos2 += fg.width;
                    if (y > 1) {
                        strcat(screenbuffer, aline);
                        strcat(screenbuffer, "\n");
                    }
                }
                strcat(screenbuffer, emptyLine);
                strcat(screenbuffer, "\n");
                if (utspeed >= UTSPEED) { 
                    makeUT();
                    utspeed = 0;
                } else { utspeed++; }
                strcat(screenbuffer, ut);
            } else {

                if ((file_sc = fopen( "./screen.txt","r")) == NULL) { 
    
                    printf("Can't open %s\n","screen.txt");
                    strcpy(screen, "");				 
                } else {
    
                    while(fgets(screen, WIDTH+1, file_sc)) {
                    
                        strcat(screenbuffer, screen);
                    }    
                    fclose(file_sc);
                }
        
                strcat(screenbuffer, screen);        
            }
        } 
        c_rfd = rfd;
        c_wfd = wfd;
        rc = select(dsize, &c_rfd, &c_wfd, NULL, (struct timeval *)NULL);
        // printf("selected\n");

        if (FD_ISSET(s, &c_rfd)) {
            cs = accept(s, (struct sockaddr *)&csa, &size_csa);
            // printf("accepted\n");

            if (cs < 0)
                continue;

            FD_SET(cs, &wfd);
            setNonBlocking(cs);
            continue;
        }

        for (i=0; i<dsize; i++)
            if (i != s && FD_ISSET(i, &wfd)) {
/*                char cls[] = "\E[H\E[2J";
                lasttry = i;
                rc = write(i, cls, sizeof(cls)); */
                lasttry = i;
                rc = write(i, locateZero, strlen(locateZero));
                lasttry = i;
                rc = write(i, screenbuffer, strlen(screenbuffer));
                // printf(locateZero);
                // printf(screenbuffer);
                if (rc <= 0) {
                    close(i);
                    FD_CLR(i, &wfd);
                }
            }

        // sleep(0.5);
    }
    
    return 0;    
}
