#include <string.h>
#include <semaphore.h>
#include <stdlib.h>
#include "model_data/viewtube_model_objects.h"
#include "model_data/viewtube_line_lookup_tables.h"


#include "viewtube_model.h"
#include "viewtube_helpers.h"





#define VIEWTUBE_RIGHT_WALL     (VIEWTUBE_BACKGROUND_GRAPHIC_WIDTH - VIEWTUBE_FPGA_WINDOW_WIDTH)
#define VIEWTUBE_BOTTOM_FLOOR   (VIEWTUBE_BACKGROUND_GRAPHIC_HEIGHT - VIEWTUBE_FPGA_WINDOW_HEIGHT)



#define VIEW_PANE_SCROLL_INTERVAL 10

void initialize_line( unsigned short line_index);

static sem_t viewtube_model_mutex;
static unsigned char mutex_needs_to_be_destroyed = 0;

unsigned char active_train_line = VIEWTUBE_ONE_LINE;


extern viewtube_map_position_t * station_locations[VIEWTUBE_NUMBER_OF_LINES];
extern unsigned char * station_lookup_type[VIEWTUBE_NUMBER_OF_LINES];

static unsigned short window_top    = 400;
static unsigned short window_left   = 0;

char * status_bar_messages[VIEWTUBE_NUMBER_OF_LINES] =
{
    "Currently showing the 1 Line",
    "Currently showing the 6 Line"
};

/**
 * @brief Get the station by line and id object; 
 * 
 * 
 * @param line_id 
 * @param station_id 
 * @return viewtube_station_t* pointer to station
 */
static viewtube_station_t * get_station_by_line_and_id(unsigned short line_id, unsigned short station_id)
{
    // note here that max stations per line 
    viewtube_station_t * return_val = NULL;
    if (line_id < VIEWTUBE_NUMBER_OF_LINES )
    {
        viewtube_line_t * cur_subway_line = get_subway_line(line_id);
        if(station_id < cur_subway_line->number_of_stations)
        {
            
            return_val = &( cur_subway_line->stations[station_id]);
        }
    }
    return return_val;
}

/**
 * @brief Get the current status bar string
 * 
 * @return char* - pointer to the string. It is null-terminated so you can strlen it. do not free
 */
char * get_current_status_bar()
{
    return status_bar_messages[active_train_line];
}

void set_active_train_line(unsigned char in_active_train_line)
{
    switch(in_active_train_line)
        {
        case '1':
            active_train_line = VIEWTUBE_ONE_LINE;
            break;
        case '6':
            active_train_line = VIEWTUBE_SIX_LINE;
            break;
        default:
            active_train_line = in_active_train_line;
            break;
        }
    
    initialize_line(active_train_line);
}

unsigned char get_active_train_line()
{
    return active_train_line;
}

void populate_line_with_trains(viewtube_train_t * trains, unsigned short num_trains, unsigned char line_id)
{
    
        if(line_id < VIEWTUBE_NUMBER_OF_LINES && num_trains < VIEWTUBE_MAX_TRAINS_PER_LINE)
        {
            viewtube_line_t * cur_subway_line = get_subway_line(line_id);
            for(unsigned short index = 0; index < num_trains; index++)
            {
                
                viewtube_train_t *cur_train  = &(cur_subway_line->trains[index]);
                
                cur_train->direction                 = trains[index].direction;
                cur_train->distance_to_next_station  = trains[index].distance_to_next_station;
                cur_train->next_station              = trains[index].next_station;
                cur_train->previous_station          = trains[index].previous_station;
                
            }
            
            cur_subway_line->number_of_trains = num_trains;
        }    
    

}

void populate_active_line_with_trains(viewtube_train_t * trains, unsigned short num_trains)
{
    populate_line_with_trains(trains,num_trains,active_train_line);
}

unsigned char get_number_of_trains_on_line(unsigned char line_id)
{
    unsigned char return_val;
    if(line_id >= VIEWTUBE_NUMBER_OF_LINES)
    {
        return_val = 0;
    }
    else
    {
        viewtube_line_t * cur_subway_line = get_subway_line(line_id);
        return_val = cur_subway_line->number_of_trains;
    }
    return return_val;
}

unsigned char get_number_of_trains_on_active_line()
{
    
        return get_number_of_trains_on_line(active_train_line);    
    
}

void scroll_window_left()
{
    
        window_left = decrease(window_left, VIEW_PANE_SCROLL_INTERVAL, 0);    
    
}

void scroll_window_right()
{
    
        window_left = increase(window_left, VIEW_PANE_SCROLL_INTERVAL, VIEWTUBE_RIGHT_WALL);    
    
}

void scroll_window_up()
{
    
        window_top = decrease(window_top, VIEW_PANE_SCROLL_INTERVAL, 0);    
    
}

void scroll_window_down()
{
    
        window_top = increase(window_top, VIEW_PANE_SCROLL_INTERVAL, VIEWTUBE_BOTTOM_FLOOR);
    
}

void set_window_left_position(unsigned short left_pos)
{
    
        if(left_pos < VIEWTUBE_RIGHT_WALL)
        {
            window_left = left_pos;
        }
    
}

void set_window_top_position(unsigned short top_pos)
{
    
        if(top_pos < VIEWTUBE_BOTTOM_FLOOR)
        {
            window_left = top_pos;
        }
    
}

unsigned short get_window_left_position()
{

    return window_left;

}

unsigned short get_window_top_position()
{

    return window_top;    

}

viewtube_line_t * get_line_object(unsigned char line_id)
{
    viewtube_line_t *return_val = NULL;
    if(line_id < VIEWTUBE_NUMBER_OF_LINES)
    {
        return_val = get_subway_line(line_id);
    }
    return return_val;
}
viewtube_line_t * get_active_line_object()
{
    return get_line_object(active_train_line);
}


void lock_model()
{
    sem_wait(&viewtube_model_mutex);
    
}

void unlock_model()
{
    sem_post(&viewtube_model_mutex);
}
/**
 * @brief Get the x/y train position of a train by id from the current active line
 * 
 * This function is a mess
 * 
 * @param train_id - id of the train on the current line
 * @return viewtube_map_position_t x/y coordinates of the train
 */
viewtube_train_vector_t get_train_vector(unsigned short train_id)
{
    
    viewtube_train_vector_t return_val = {{0,0},0};
    unsigned short direction = 0;
    viewtube_station_t * northmost_station = NULL;
    float                lookup_distance = 0.0;
    unsigned short distance_between_stations = 0;
    viewtube_train_t * train = NULL;
    
    viewtube_line_t * cur_line = get_active_line_object();
    if(train_id < cur_line->number_of_trains)
    {
        train = &(cur_line->trains[train_id]);
        direction = train->direction;

        debug("train next station:%d, prev station:%d\n",
        train->next_station, train->previous_station);
        
        // stations lookup tables go north to south
        if(VIEWTUBE_DIRECTION_NORTH == train->direction)
        {
            northmost_station = get_station_by_line_and_id( active_train_line, train->next_station );
            debug("train->next_station:%d\n",train->next_station);
        } else 
        {
            northmost_station = get_station_by_line_and_id( active_train_line, train->previous_station );
            debug("train->next_station:%d\n",train->previous_station);
        }
        //northmost_station = get_station_by_line_and_id( active_train_line, train->next_station );
        direction = train->direction;
        return_val.direction = train->direction;

        lookup_distance = train->distance_to_next_station/100.0;
        if(lookup_distance > 1.0)
        {
            lookup_distance = 1.0;
        }
        
        debug("northmost_station->id:%d\n",northmost_station->id );
        debug("northmost_station->lookup_type:%d\n",northmost_station->lookup_type );
        //debug("northmost_station->name:%s\n",northmost_station->name);
        //debug("northmost_station->name_len:%d\n",northmost_station->name_len );
        debug("northmost_station->pos->x:%d\n",northmost_station->position.x_pos );
        debug("northmost_station->pos->y:%d\n",northmost_station->position.y_pos );
        viewtube_station_t * southmost_station = get_station_by_line_and_id(active_train_line,northmost_station->id +1);
        unsigned short lookup_index = 0;
        debug("northmost_station: %d (%d,%d), southmost_station: %d (%d,%d)\n",
        northmost_station->id,
        northmost_station->position.x_pos,
        northmost_station->position.y_pos,
        southmost_station->id,
        southmost_station->position.x_pos,
        southmost_station->position.y_pos);
        debug("lookup_distance:%f\n",lookup_distance);
        if(VIEWTUBE_VERTICAL_LOOKUP == northmost_station->lookup_type)
        {
            distance_between_stations = abs((int) northmost_station->position.y_pos
            - (int) southmost_station->position.y_pos);
            debug("distance_between_stations vertical:%d\n",distance_between_stations);
            
            lookup_index = (unsigned short) (distance_between_stations * lookup_distance);
            debug("lookup_index:%d\n",lookup_index);
            if(lookup_index == 0)
            {
                if(direction == 's')
                {
                    return_val.position.x_pos = southmost_station->position.x_pos;
                    return_val.position.y_pos = southmost_station->position.y_pos;

                } else 
                {
                    return_val.position.x_pos = northmost_station->position.x_pos;
                    return_val.position.y_pos = northmost_station->position.y_pos;
                }
            }
            else if(lookup_index == distance_between_stations)
            {
                if(direction == 's')
                {
                    return_val.position.x_pos = northmost_station->position.x_pos;
                    return_val.position.y_pos = northmost_station->position.y_pos;
                } else 
                {
                    return_val.position.x_pos = southmost_station->position.x_pos;
                    return_val.position.y_pos = southmost_station->position.y_pos;
                }

            } 
            else
            {
                if(direction == 's')
                {
                    lookup_index = distance_between_stations - lookup_index;
                }
                return_val.position.y_pos = lookup_index+northmost_station->position.y_pos;
                return_val.position.x_pos = lookup_station_position_by_line(active_train_line,northmost_station->id,northmost_station->lookup_type,lookup_index);
            }
        } else if(VIEWTUBE_HORIZONTAL_LOOKUP == northmost_station->lookup_type)
        {
            distance_between_stations = abs( (int) northmost_station->position.x_pos
            - (int) southmost_station->position.x_pos);
            debug("distance_between_stations horizontal:%d\n",distance_between_stations);
            lookup_index = (unsigned short) (distance_between_stations * lookup_distance);
            if(lookup_index == 0)
            {
                return_val.position.x_pos = northmost_station->position.x_pos;
                return_val.position.y_pos = northmost_station->position.y_pos;
            }
            else if(lookup_index == distance_between_stations)
            {
                return_val.position.x_pos = southmost_station->position.x_pos;
                return_val.position.y_pos = southmost_station->position.y_pos;
            } 
            else
            {
                return_val.position.x_pos = lookup_index+northmost_station->position.x_pos;
                return_val.position.y_pos = return_val.position.x_pos = lookup_station_position_by_line(active_train_line,northmost_station->id,northmost_station->lookup_type,lookup_index);

            }
        }
        debug("final (x,y):%d,%d\n",return_val.position.x_pos, return_val.position.y_pos);
    }
    

    return return_val;
}

/**
 * @brief Get the station id from name object
 *  * 
 * @param name - name to search for
 * @param name_len - length of string name
 * @param line_id - id of line to search
 * @return short - returns -1 if not found
 */
short get_station_id_from_name(char * name, unsigned short name_len, unsigned char line_id)
{
    short return_val = -1;
    viewtube_line_t * cur_subway_line = get_subway_line(line_id);
    for(unsigned short index = 0; index < cur_subway_line->number_of_stations; index++)
    {   
        if(cur_subway_line->stations->name_len != name_len)
        {
            continue;
        }
        if(strncmp(cur_subway_line->stations->name, name, name_len) == 0)
        {
            return_val = index;
            break;
        }
    }
    return return_val;
}


void initialize_line( unsigned short line_index)
{
    viewtube_line_t *cur_line = get_subway_line(line_index);
    for(unsigned short station_index = 0; station_index < cur_line->number_of_stations; station_index++)
    {
        viewtube_station_t *cur_station = &(cur_line->stations[station_index]);

        cur_station->id = station_index;
        //cur_station->name = station_names[line_index][station_index];
        //cur_station->name_len = (unsigned short) strlen(cur_station->name);
        cur_station->name = NULL;
        cur_station->name_len = 0;
        viewtube_map_position_t * cur_line_station_locations = get_station_locations_by_line(active_train_line);
        cur_station->position.x_pos = cur_line_station_locations[station_index].x_pos;
        cur_station->position.y_pos = cur_line_station_locations[station_index].y_pos;
        unsigned char * station_lookup_type = get_station_lookup_type_by_line(active_train_line);
        cur_station->lookup_type = station_lookup_type[station_index];

    }
    
}


/**
 * @brief this function should be called once to initialize anything in the model that
 * needs to be initialized. Calling it more than once may cause leaks.
 * 
 */
void initialize_viewtube_model()
{
    initialize_one_line_station_lookup_table();
    initialize_six_line_station_lookup_table();
    for(unsigned short line_index = 0; line_index < VIEWTUBE_NUMBER_OF_LINES; line_index++)
    {
        initialize_line(line_index);
    }
    if(!mutex_needs_to_be_destroyed)
    {
         sem_init(&viewtube_model_mutex, 0, 1);
    }
    mutex_needs_to_be_destroyed = 1;

}


void cleanup_viewtube_model()
{
    if(mutex_needs_to_be_destroyed)
    {
        sem_destroy(&viewtube_model_mutex);
    }
    mutex_needs_to_be_destroyed = 0;
}

/**
 * @brief frees the line object
 * 
 * @param line pass a pointer to your pointer so the function can 
 * set it to NULL for you.
 */
void free_viewtube_line_t(viewtube_line_t ** line)
{
    
    free((*line)->stations);
    free((*line)->trains);
    free((*line));
    line = NULL;
}

viewtube_line_t * alloc_viewtube_line_t()
{
    viewtube_line_t * return_ptr = NULL;

    if(
        (return_ptr = (viewtube_line_t * ) malloc(sizeof(viewtube_line_t))) != NULL
    )
    {
        return_ptr->number_of_stations = 0;
        return_ptr->number_of_trains = 0;
        return_ptr->stations = NULL;
        return_ptr->trains = NULL;
    }

    return return_ptr;

}