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

unsigned short web_view_server_running = 0;

pthread_t web_view_server_thread;

pthread_t client_threads[CLIENT_THREAD_COUNT];

unsigned char client_thread_available[CLIENT_THREAD_COUNT];

static unsigned char get_next_client_thread(pthread_t ** client_thread)
{
    unsigned char client_thread_index = 0;
    for(unsigned char index = 0; index< CLIENT_THREAD_COUNT; index++)
    {
        if (client_thread_available[index])
        {
            *client_thread = &client_threads[index];
            client_thread_available[index] = 0;
            client_thread_index = index;
            
            break;
        }
    }
    return client_thread_index;
}


void * handle_client(void * in_thread_info)
{
    viewtube_socket_thread_info_t * thread_info = (viewtube_socket_thread_info_t *) in_thread_info;
    viewtube_socket_thread_info_t client_info;
    if(thread_info == NULL)
    {
        return thread_info;
    }
    client_info.socket_fd = thread_info->socket_fd;
    client_info.thread_id = thread_info->thread_id;
    free(thread_info);
    
    write_board_to_fd(client_info.socket_fd);
    usleep(100000);
    

    close(client_info.socket_fd);

    client_thread_available[client_info.thread_id] = 1;
    return NULL;
}


void * web_view_server_thread_func( void * nothing)
{
    // socket web_view_server refresher: https://www.geeksforgeeks.org/socket-programming-cc/
    // threading refresher: https://www.cs.cmu.edu/afs/cs/academic/class/15492-f07/www/pthreads.html
    int fd_web_view_server;
    int fd_client_conn; 
    int sock_opt = 1;

    int client_socket;
    
    struct sockaddr_in web_view_server_address;
    int addrlen = sizeof(web_view_server_address);

    if ( 0 == (fd_web_view_server = socket(AF_INET, SOCK_STREAM, 0)) ) {
        perror("socket failed");
        exit(EXIT_FAILURE);
    }

    if (setsockopt(fd_web_view_server, SOL_SOCKET,
                   SO_REUSEADDR | SO_REUSEPORT, &sock_opt,
                   sizeof(sock_opt))) {
        perror("setsockopt");
        exit(EXIT_FAILURE);
    }

    web_view_server_address.sin_family = AF_INET;
    web_view_server_address.sin_addr.s_addr = INADDR_ANY;
    web_view_server_address.sin_port = htons(SERVER_LISTEN_PORT);

    if ( 0 > bind(fd_web_view_server, ( struct sockaddr * ) &web_view_server_address,
             sizeof(web_view_server_address))) {
        perror("bind failed");
        exit(EXIT_FAILURE);
    }

    if ( 0 > listen(fd_web_view_server, 8) ) {
        perror("listen");
        exit(EXIT_FAILURE);
    }
    while(web_view_server_running)
    {
        if (0 > (client_socket
            = accept(fd_web_view_server, (struct sockaddr*) &web_view_server_address,
                    (socklen_t*) &addrlen))) {
            perror("accept");
            exit(EXIT_FAILURE);
        }
        pthread_t * cur_client_thread;
        viewtube_socket_thread_info_t * thread_info = malloc(sizeof(viewtube_socket_thread_info_t));
        if(thread_info != NULL)
        {
            thread_info->socket_fd = client_socket;
            thread_info->thread_id = get_next_client_thread(&cur_client_thread);
            pthread_create(cur_client_thread, 0, handle_client, (void *) thread_info);
        }
    }
    close(fd_web_view_server);
}


void start_web_view_server()
{
    for(unsigned short index = 0; index < CLIENT_THREAD_COUNT; index++)
    {
        client_thread_available[index] = 1;
    }
    web_view_server_running = 1;
    pthread_create(&web_view_server_thread, 0, web_view_server_thread_func, NULL);
}


void stop_web_view_server()
{
    web_view_server_running = 0;
    for (unsigned char thread_index = 0; thread_index < CLIENT_THREAD_COUNT; thread_index++)
    {
        if (client_thread_available[thread_index] == 0)
        {
            pthread_join(client_threads[thread_index], NULL);
        }
    }
    pthread_join(web_view_server_thread, NULL);
}

void write_board_to_fd(int write_fd)
{
    char board_buff[1024*1024];
    unsigned int write_count = 0;
    unsigned int write_max = 1024*1024;
    memset(board_buff,0,write_max);

    write_count += snprintf(board_buff+write_count,write_max - write_count,"viewtube_window_width =%d;\n",VIEWTUBE_FPGA_WINDOW_WIDTH);
    write_count += snprintf(board_buff+write_count,write_max - write_count,"viewtube_window_height =%d;\n",VIEWTUBE_FPGA_WINDOW_HEIGHT);
    lock_model();
    write_count += snprintf(board_buff+write_count,write_max - write_count,"viewtube_active_train_line =%02x;\n",get_active_train_line());
    //TODO: add lookup to convert to actual chars
    write_count += snprintf(board_buff+write_count,write_max - write_count,"viewtube_window_top_position=  %d;\nviewtube_window_left_position= %d;\n", get_window_top_position(), get_window_left_position() );

    unsigned char train_count = get_number_of_trains_on_active_line();

    write_count += snprintf(board_buff+write_count,write_max - write_count,"viewtube_trains = Array(%d);",train_count); 

    for(unsigned char train_index = 0; train_index<train_count; train_index++)
    {
        viewtube_train_vector_t train_vector = get_train_vector(train_index);
        write_count += snprintf(board_buff+write_count,write_max - write_count,"viewtube_trains[%d] = {x: %d, y: %d, dir: %d};\n", 
        train_index, 
        train_vector.position.x_pos, 
        train_vector.position.y_pos, 
        train_vector.direction);
    }
    write_count += snprintf(board_buff+write_count,write_max - write_count,"viewtube_status_bar = \"%s\";\n",get_current_status_bar());
    unlock_model();
    board_buff[write_count] = 0;
    char read_buffer[1024];
    char * send_buffer = NULL;
    char * message = "HTTP/1.0 200 OK\r\n"
    "Content-Type: text/javascript\r\n"
    "Access-Control-Allow-Origin: *\r\n"
    "Content-Length: %d\r\n\r\n"
    "%s";
    send_buffer = malloc(write_count + 2048);
    if(send_buffer != NULL)
    {
        snprintf(send_buffer,write_count+2048,message,write_count,board_buff);
        unsigned int message_len = strlen(send_buffer);
        write(write_fd, send_buffer, message_len);
    }
    free(send_buffer);
    
}

