#include "viewtube_view_fpga.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include "viewtube_helpers.h"


#define NUM_COLOR_SPRITE_ENTRIES 64    //entries in sprite table
#define NUM_LETTER_SPRITE_ENTRIES 64   //entires in sprite table
#define SPRITE_TABLE_SIZE (NUM_LETTER_SPRITE_ENTRIES + NUM_COLOR_SPRITE_ENTRIES)

#define STATUS_BAR_Y_POS (WINDOW_HEIGHT + 30)

#define STATUS_BAR_X_START 20

#define WINDOW_LEFT_MARGIN 20
#define WINDOW_TOP_MARGIN  20

#define VIEWTUBE_SPRITE_NO_JUMP 0
#define VIEWTUBE_SPRITE_JUMP 0
#define JUMP_THRESHOLD      200


#define REFRESH_INTERVAL 330000 // 0.33 seconds

#define SPRITE_TABLE_STATUS_BAR_SPRITES_OFFSET 32
// this value should be greater than the maximum
// number of trains we see at once
// we use this offset so if the train number changes,
// we don't have to redraw the other sprites

#define LOOKUP_TABLE_COLOR_SPRITES_TRAIN_OFFSET 0

static const char fpga_viewtube_filename[] = "/dev/viewtube";

int viewtube_fd;

static viewtube_sprite_table_entry_t sprite_table[SPRITE_TABLE_SIZE];


static viewtube_sprite_table_entry_t blank_sprite = {
    .type = SPRITE_TYPE_COLOR,
    .sprite_num = 200,
    .cur_x = 0,
    .cur_y = 0,
    .active = 0,
    .prev_active = 0,
    .prev_sprite_num = 0,
    .prev_cur_x = 0,
    .prev_cur_y = 0,
    .dirty = 0,
    .sprite = {.color= {
        .width = 0,
        .height = 0
    }}
};
// from view_data/viewtube_fpga_view_lookup_tables.c
// the offsets in these tables will match memory in FPGA
// the width and height match the actual values
extern viewtube_view_color_sprite_t lookup_table_4bit_color_sprite[NUMBER_OF_4BIT_SPRITES];

// put in your ascii char in this array
// and you will get your offset to use
// in the lookup table for the same offset
// if your byte isn't supported, it will
// default to a blank space 
extern char letter_to_sprite_offset[256];

static unsigned short fpga_view_running = 0;

static pthread_t fpga_view_thread;


unsigned char sprite_offset_from_line_number(unsigned short line_number)
{
    unsigned char offset;
    switch(line_number)
    {
        case 0:
            offset = 0;
            break;

        case 1:
            offset = 5;
            break;

        default:
            offset = 0;
            break;
    }
    return offset;
}

/**
 * @brief this will update the fpga with the sprite. 
 * 
 * @param in_sprite - pointer to sprite; on success, dirty bit is set to 0.
 *
 */
void update_sprite_to_fpga(viewtube_sprite_table_entry_t *in_sprite, unsigned short sprite_index,char jump)
{
    viewtube_sprite_t update_sprite =
    {
        in_sprite->type,
        jump, // no to jump
        sprite_index,
        in_sprite->cur_x,
        in_sprite->cur_y,
        {
            .color = {0xff,0xff,0xff} // update these in if/then
        }
    };
    if(in_sprite->type == SPRITE_TYPE_MONO) // letter
    {
        update_sprite.obj.mono.blue = 0xff;
        update_sprite.obj.mono.red = 0xff;
        update_sprite.obj.mono.blue = 0xff;
        update_sprite.obj.mono.sprite_num = in_sprite->sprite_num;
    }
    else // color 
    {
        update_sprite.obj.color.height = in_sprite->sprite.color.height;
        update_sprite.obj.color.width = in_sprite->sprite.color.width;
        update_sprite.obj.color.sprite_num = in_sprite->sprite_num;
    }
    /*printf("converted sprite for drawing:"
    "\t type: %d\n"
    "\t jump: %d\n"
    "\t table_index: %d\n"
    "\t cur_x: %d\n"
    "\t cur_y: %d\n",
    update_sprite.type,
    update_sprite.jump,
    update_sprite.table_index,
    update_sprite.new_x,
    update_sprite.new_y            
    );
    if(in_sprite->type == SPRITE_TYPE_COLOR)
    {
        printf(
            "\t sprite_num: %d\n"
            "\t width: %d\n"
            "\t height: %d\n",
            update_sprite.obj.color.sprite_num,
            update_sprite.obj.color.height,
            update_sprite.obj.color.width
        );
    }*/
    set_viewtube_sprite_data(&update_sprite);
    set_viewtube_sprite_pos(&update_sprite);
    in_sprite->dirty = 0;
}

static unsigned short get_train_sprite_offset_from_parameters(unsigned short active_line, unsigned short direction)
{
    // trains in memory are ordered in the same order as active lines, but with two
    // entries per line. the first entry is for facing north, the second entry is south.
    unsigned char sprite_direction = 0;
    if (direction == 's')
    {
        sprite_direction = 1;
    }
    return LOOKUP_TABLE_COLOR_SPRITES_TRAIN_OFFSET + sprite_offset_from_line_number(active_line)*2 + sprite_direction;
}

void update_fpga_window_position(viewtube_map_position_t * map_position)
{
    set_viewtube_background_position(map_position->x_pos, map_position->y_pos, VIEWTUBE_SPRITE_NO_JUMP); 
}

void clear_board_of_trains()
{
    unsigned short sprite_index = 0;
    for(sprite_index = 0; sprite_index < SPRITE_TABLE_SIZE; sprite_index++)
    {
        sprite_table[sprite_index].active = 0;
        sprite_table[sprite_index].cur_x = 0;
        sprite_table[sprite_index].cur_y = 0;
        sprite_table[sprite_index].type = VIEWTUBE_SPRITE_TYPE_MONO;
        
        sprite_table[sprite_index].sprite_num = LETTER_SPACE_OFFSET; 
        sprite_table[sprite_index].sprite.color.width = 0;
        sprite_table[sprite_index].sprite.color.height = 0;
        update_sprite_to_fpga(&sprite_table[sprite_index], 
            sprite_index,VIEWTUBE_SPRITE_JUMP);
    }
}

void * fpga_thread_func( void * nothing)
{
    viewtube_map_position_t map_position = {0,0};
    unsigned short sprite_index = 0;
    unsigned short train_count;
    unsigned short sprites_drawn_index = 0;
    unsigned short active_line = 0;
    char * status_bar_message = NULL;
    unsigned short status_bar_message_len = 0;
    while(fpga_view_running)
    {
        sprites_drawn_index = 0;
        for(sprite_index = 0; sprite_index < SPRITE_TABLE_SIZE; sprite_index++)
        {
            sprite_table[sprite_index].prev_active = sprite_table[sprite_index].active;
            sprite_table[sprite_index].prev_cur_x = sprite_table[sprite_index].cur_x;
            sprite_table[sprite_index].prev_cur_y = sprite_table[sprite_index].cur_y;
            sprite_table[sprite_index].prev_sprite_num = sprite_table[sprite_index].sprite_num;
            sprite_table[sprite_index].prev_type = sprite_table[sprite_index].type;
            sprite_table[sprite_index].active = 0;
        }
        viewtube_background_window_position_t current_window_position;
        poll_background_position(&current_window_position);
        lock_model();
            map_position.x_pos =  get_window_left_position();
            map_position.y_pos = get_window_top_position();
            active_line = get_active_train_line();
            train_count = get_number_of_trains_on_active_line();
            status_bar_message = get_current_status_bar();

            //go through all the trains and only draw the trains in view of window
            for(unsigned char train_index = 0; train_index<train_count; train_index++)
            {
                viewtube_train_vector_t train_vector = get_train_vector(train_index);  
                // if train in range
                if (
                       (train_vector.position.x_pos < current_window_position.coordinate_x + WINDOW_WIDTH )
                    && (train_vector.position.x_pos > current_window_position.coordinate_x)
                    && (train_vector.position.y_pos > current_window_position.coordinate_y)
                    && (train_vector.position.y_pos < current_window_position.coordinate_y + WINDOW_HEIGHT)
                )
                {
                    debug("train_vectory pre(x,y): %d,%d\n",train_vector.position.x_pos,train_vector.position.y_pos);
                    sprites_drawn_index = train_index;
                    sprite_table[sprites_drawn_index].sprite.color.width = 
                        lookup_table_4bit_color_sprite[sprite_table[sprites_drawn_index].sprite_num].width;

                    sprite_table[sprites_drawn_index].sprite.color.height = 
                        lookup_table_4bit_color_sprite[sprite_table[sprites_drawn_index].sprite_num].height;

                    sprite_table[sprites_drawn_index].cur_x = 
                        (train_vector.position.x_pos - map_position.x_pos) 
                        + WINDOW_LEFT_MARGIN - 
                        (sprite_table[sprites_drawn_index].sprite.color.width/2);
                    sprite_table[sprites_drawn_index].cur_y = 
                        (train_vector.position.y_pos - map_position.y_pos) 
                        + WINDOW_TOP_MARGIN -
                        (sprite_table[sprites_drawn_index].sprite.color.height/2);
                    sprite_table[sprites_drawn_index].type = SPRITE_TYPE_COLOR;
                     debug("sprite(x,y): %d,%d\n",sprite_table[sprites_drawn_index].cur_x,sprite_table[sprites_drawn_index].cur_y);
                    sprite_table[sprites_drawn_index].sprite_num = get_train_sprite_offset_from_parameters(active_line, train_vector.direction);



                    sprite_table[sprites_drawn_index].active = 1;
                    //sprites_drawn_index++;
                }
            }
        unlock_model();

        status_bar_message_len = strlen(status_bar_message);
        sprites_drawn_index = SPRITE_TABLE_STATUS_BAR_SPRITES_OFFSET;
        for (   unsigned short message_index = 0;
                message_index < status_bar_message_len; 
                message_index++
            )
        {
            sprite_table[sprites_drawn_index].sprite_num = 
                letter_to_sprite_offset[status_bar_message[message_index]];
            sprite_table[sprites_drawn_index].type = SPRITE_TYPE_MONO;
            sprite_table[sprites_drawn_index].cur_y = STATUS_BAR_Y_POS;
            sprite_table[sprites_drawn_index].cur_x = 
                STATUS_BAR_X_START + (message_index*SPRITE_LETTER_WIDTH);
            sprite_table[sprites_drawn_index].sprite.letter.red   = 0xff;
            sprite_table[sprites_drawn_index].sprite.letter.green = 0xff;
            sprite_table[sprites_drawn_index].sprite.letter.blue  = 0xff;
            sprite_table[sprites_drawn_index].active = 1;
            sprites_drawn_index++;
        }     

        for(sprite_index = 0; sprite_index < SPRITE_TABLE_SIZE; sprite_index++)
        {
            unsigned char jump = VIEWTUBE_SPRITE_NO_JUMP;
            if ( // if a sprite was changed, set dirty bit
                 // dirty bit means the index in fpga is stale
                 // and it needs to be changed.
                 // i probably could have just not set the bit at all
                 // but it seemed to make sense in my head initially
                (
                    sprite_table[sprite_index].prev_cur_x != sprite_table[sprite_index].cur_x
                    || sprite_table[sprite_index].prev_cur_y != sprite_table[sprite_index].cur_y
                    || sprite_table[sprite_index].prev_sprite_num != sprite_table[sprite_index].sprite_num
                    || sprite_table[sprite_index].prev_type != sprite_table[sprite_index].type
                    || sprite_table[sprite_index].prev_active != sprite_table[sprite_index].active
                )
            )
            {
                sprite_table[sprite_index].dirty = 1;
                jump = (sprite_table[sprite_index].prev_active != sprite_table[sprite_index].active)||
                        (abs((int) sprite_table[sprite_index].prev_cur_x - (int)sprite_table[sprite_index].cur_x) > JUMP_THRESHOLD)||
                        (abs((int) sprite_table[sprite_index].prev_cur_y - (int) sprite_table[sprite_index].cur_y) > JUMP_THRESHOLD);
            } else {
                sprite_table[sprite_index].dirty = 0;
            }


            if(sprite_table[sprite_index].dirty)
            {
                if (! sprite_table[sprite_index].active)
                {
                    update_sprite_to_fpga(&blank_sprite, sprite_index, VIEWTUBE_SPRITE_JUMP);
                }
                else 
                {
                    update_sprite_to_fpga(&sprite_table[sprite_index], sprite_index, jump);
                }
                
                /*printf("drawing sprite:"
                "\t type: %d\n"
                "\t sprite_num: %d\n"
                "\t cur_x: %d\n"
                "\t cur_y: %d\n"
                "\t active: %d\n",
                sprite_table[sprite_index].type,
                sprite_table[sprite_index].sprite_num,
                sprite_table[sprite_index].cur_x,
                sprite_table[sprite_index].cur_y,
                sprite_table[sprite_index].active                
                );*/
            }
        }
        update_fpga_window_position(&map_position);
        usleep(REFRESH_INTERVAL);
    }
    return NULL;
}


void start_fpga_view()
{
    if (! fpga_view_running)
    {
        if ( (viewtube_fd = open(fpga_viewtube_filename, O_RDWR)) == -1) {
            fprintf(stderr, "[!] - could not open %s; skipping FPGA view thread\n", 
            fpga_viewtube_filename);
            fprintf(stderr, "[+] Press enter to acknowledge and continue\n");
            getchar();
            return;
        }
        clear_board_of_trains();
        initialize_fpga_view_lookup_tables();
        fpga_view_running = 1;
        pthread_create(&fpga_view_thread, 0, fpga_thread_func, NULL);
    }

}


void stop_fpga_view()
{
    if(fpga_view_running)
    {
        fpga_view_running = 0;
        pthread_join(fpga_view_thread, NULL);
        close(viewtube_fd);
    }

}
