#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <assert.h>
#include <ctype.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>

int Nx, Ny;
int port;
uint8_t *buf;

void SetPixelRGB(int x, int y, uint8_t r, uint8_t g, uint8_t b){
    if (x < 0 || x >= Nx || y < 0 || y >= Ny) return;
    buf[3*(x+Nx*y)+0] = r;
    buf[3*(x+Nx*y)+1] = g;
    buf[3*(x+Nx*y)+2] = b;
}

void SetPixel(int x, int y, uint32_t c){
    SetPixelRGB(x, y, (c >> 16)&0xff, (c >> 8)&0xff, c&0xff);
}

uint32_t GetPixel(int x, int y){
    if (x < 0 || x >= Nx || y < 0 || y >= Ny) return 0;
    uint32_t r = buf[3*(x+Nx*y)+0];
    uint32_t g = buf[3*(x+Nx*y)+1];
    uint32_t b = buf[3*(x+Nx*y)+2];
    return (r << 16) | (g << 8) | b;
}


static const int PFT_CONNECTION_TIMEOUT = 1;
volatile int PFT_ClientThreadCount = 0;

int PFT_ReadIdent(char** begin, char* end, char* out, int size){
    int i = 0;
    while (*begin != end && isalpha(**begin) && i < size-1){
        out[i++] = **begin;
        (*begin)++;
    }
    out[i] = '\0';
    return i != 0;
}

int PRF_ReadInt(char** begin, char* end, int *out){
    if (*begin == end || !isdigit(**begin))
        return 0;
    *out = 0;
    while(*begin != end && isdigit(**begin)){
        *out = (*out)*10+(**begin-'0');
        (*begin)++;
    }
    return 1;
}

int PFT_IsHex(char c){
    return isdigit(c) || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F');
}

int PFT_HexCharToInt(char c){
    if (isdigit(c))
        return c-'0';
    if (c >= 'a' && c <= 'f')
        return c-'a'+10;
    if (c >= 'A' && c <= 'F')
        return c-'A'+10;
    return 0;
}

int PFT_ReadColorHex(char** begin, char* end, uint32_t *out){
    char* oldbegin = *begin;
    *out = 0;
    if (end-*begin < 6)
        return 0;
    for(int i = 0; i != 6; i++){
        if (!PFT_IsHex(**begin)){
            *begin = oldbegin;
            return 0;
        }
        *out = (*out)*16+PFT_HexCharToInt(**begin);
        (*begin)++;
    }
    if (end-*begin >= 2 && PFT_IsHex(**begin) && PFT_IsHex(begin[0][1])){
        *out = (*out)*16+PFT_HexCharToInt(**begin);
        (*begin)++;
        *out = (*out)*16+PFT_HexCharToInt(**begin);
        (*begin)++;
    }else{
        *out *= 256;
    }
    return 1;
}

void PFT_SkipSpace(char **begin, char *end){
    while(*begin != end && **begin == ' '){
        (*begin)++;
    }
}

void PFT_SkipOverNextNewline(char **begin, char *end){
    while(*begin != end && **begin != '\n'){
        (*begin)++;
    }
    if (*begin != end)
        (*begin)++;
}

int PFT_HandlePixelflutMessage(uint8_t *buffer, int size, int sock){
    char *begin = (char*)buffer;
    char *end = begin + size;
    char cmd_buffer[32];
    char response_buffer[100];
    int x, y;
    uint32_t c;
    //packet_size = MIN(offset + (pixellength * MAX_PIXELS), packet_size);
    while ( begin != end ) {
        if (!PFT_ReadIdent(&begin, end, cmd_buffer, 32)){
            PFT_SkipOverNextNewline(&begin, end);
            continue;
        }
        if (strcmp(cmd_buffer, "PX") == 0){
            PFT_SkipSpace(&begin, end);
            if (!PRF_ReadInt(&begin, end, &x)){
                PFT_SkipOverNextNewline(&begin, end);
                continue;
            }
            PFT_SkipSpace(&begin, end);
            if (!PRF_ReadInt(&begin, end, &y)){
                PFT_SkipOverNextNewline(&begin, end);
                continue;
            }
            PFT_SkipSpace(&begin, end);
            if (PFT_ReadColorHex(&begin, end, &c)){
                SetPixel(x, y, c>>8);
            }else{
                c = GetPixel(x, y);
                int len = sprintf(response_buffer, "PX %i %i %06x\n", x, y, c);
                send(sock, response_buffer, len, 0);
            }
        }
        else if (strcmp(cmd_buffer, "SIZE") == 0){
            int len = sprintf(response_buffer, "SIZE %i %i", Nx, Ny);
            send(sock, response_buffer, len, 0);
    
        }
        else if (strcmp(cmd_buffer, "HELP") == 0){
            int len = sprintf(response_buffer, "see https://github.com/defnull/pixelflut\n");
            send(sock, response_buffer, len, 0);
        }
        PFT_SkipOverNextNewline(&begin, end);
    }
    return begin-(char*)buffer;
}

void* PFT_HandleClient(void* sock_){
    int sock = (int)(size_t)sock_;
    PFT_ClientThreadCount++;
    uint8_t buf[1024];
    uint8_t offset = 0;
    int read_size = 0;
    while( (read_size = recv(sock , buf + offset, sizeof(buf) - offset , 0)) > 0){
        offset = PFT_HandlePixelflutMessage(buf, read_size, sock);
        memcpy(buf, buf+read_size-offset, offset);  	
    }
    close(sock);
    PFT_ClientThreadCount--; 
    return 0;
}


void* PFT_RunServer(void* _){
    int client_sock;
    socklen_t addr_len;
    struct sockaddr_in addr;
    addr_len = sizeof(addr);
    struct timeval tv;

    fprintf(stderr, "PF: Starting Pixelflut Server...\n");

    int server_sock = socket(PF_INET, SOCK_STREAM, 0);

    tv.tv_sec = PFT_CONNECTION_TIMEOUT;
    tv.tv_usec = 0;

    addr.sin_addr.s_addr = INADDR_ANY;
    addr.sin_port = htons(port);
    addr.sin_family = AF_INET;

    if (server_sock == -1){
        fprintf(stderr, "PF: socket() failed\n");
        return 0;
    }
    int one = 1;
    if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) < 0)
        fprintf(stderr, "PF: setsockopt(SO_REUSEADDR) failed\n");
    if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(int)) < 0)
        fprintf(stderr, "PF: setsockopt(SO_REUSEPORT) failed\n");

    int retries;
    for (retries = 0; bind(server_sock, (struct sockaddr*)&addr, sizeof(addr)) == -1 && retries < 10; retries++){
        fprintf(stderr, "PF: bind() failed ...retry in 5s\n");
        usleep(5000000);
    }
    if (retries == 10)
        return 0;

    if (listen(server_sock, 4) == -1){
        fprintf(stderr, "PF: listen() failed\n");
        return 0;
    }
    fprintf(stderr, "PF: Pixelflut Listening...\n");

    setsockopt(server_sock, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv,sizeof(struct timeval));
    setsockopt(server_sock, SOL_SOCKET, SO_RCVTIMEO, (char *)&tv,sizeof(struct timeval));

    while(1){
        client_sock = accept(server_sock, (struct sockaddr*)&addr, &addr_len);
        if(client_sock > 0){
            pthread_t thread;
            pthread_create(&thread, NULL, PFT_HandleClient, (void*)(size_t)client_sock);
            pthread_detach(thread);
        }
    }
    close(server_sock);
    return 0;
}

int main(int argc, char **argv) {
    Nx = atoi(argv[1]);
    Ny = atoi(argv[2]);
    buf = (uint8_t*)malloc(Nx*Ny*3);
    port = atoi(argv[3]);
    if (port == 0) port = 4444;
    pthread_t thread;
    pthread_create(&thread, NULL, PFT_RunServer, NULL);

    

    while(1) {
        write(1, buf, Nx*Ny*3);
        usleep(33*1000);

    }
}
