/* macOS System/Graphics/Font API Implementations */

/********************/
/*    System API    */
/********************/

////////////////////////////////

function
system_get_path_sig(){
    String_Const_u8 result = {};
    
    switch (path_code){
        case SystemPath_CurrentDirectory:
        {
            char *working_dir = getcwd(NULL, 0);
            u64 working_dir_length = cstring_length(working_dir);
            
            // TODO(yuval): Maybe use push_string_copy instead
            u8 *out = push_array(arena, u8, working_dir_length);
            block_copy(out, working_dir, working_dir_length);
            
            free(working_dir);
            
            result = SCu8(out, working_dir_length);
        } break;
        
        case SystemPath_Binary:
        {
            local_persist b32 has_stashed_4ed_path = false;
            if (!has_stashed_4ed_path){
                local_const u32 binary_path_capacity = PATH_MAX;
                u8 *memory = (u8*)system_memory_allocate(binary_path_capacity, file_name_line_number_lit_u8);
                
                pid_t pid = getpid();
                i32 size = proc_pidpath(pid, memory, binary_path_capacity);
                Assert(size < binary_path_capacity);
                
                mac_vars.binary_path = SCu8(memory, size);
                mac_vars.binary_path = string_remove_last_folder(mac_vars.binary_path);
                mac_vars.binary_path.str[mac_vars.binary_path.size] = 0;
                
                has_stashed_4ed_path = true;
            }
            
            result = push_string_copy(arena, mac_vars.binary_path);
        } break;
    }
    
    return(result);
}

function
system_get_canonical_sig(){
    NSString *path_ns_str =
        [[NSString alloc] initWithBytes:name.data length:name.size encoding:NSUTF8StringEncoding];
    
    NSString *standardized_path_ns_str = [path_ns_str stringByStandardizingPath];
    String_Const_u8 standardized_path = SCu8((u8*)[standardized_path_ns_str UTF8String],[standardized_path_ns_str lengthOfBytesUsingEncoding:NSUTF8StringEncoding]);
    
    String_Const_u8 result = push_string_copy(arena, standardized_path);
    
    [path_ns_str release];
    
    return(result);
}

////////////////////////////////

function File_Attributes
mac_get_file_attributes(struct stat file_stat) {
    File_Attributes result;
    result.size = file_stat.st_size;
    result.last_write_time = file_stat.st_mtimespec.tv_sec;
    
    result.flags = 0;
    if (S_ISDIR(file_stat.st_mode)) {
        result.flags |= FileAttribute_IsDirectory;
    }
    
    return(result);
}

function inline File_Attributes
mac_file_attributes_from_path(char *path) {
    File_Attributes result = {};
    
    struct stat file_stat;
    if (stat(path, &file_stat) == 0){
        result = mac_get_file_attributes(file_stat);
    }
    
    return(result);
}

function inline File_Attributes
mac_file_attributes_from_fd(i32 fd) {
    File_Attributes result = {};
    
    struct stat file_stat;
    if (fstat(fd, &file_stat) == 0){
        result = mac_get_file_attributes(file_stat);
    }
    
    return(result);
}

function
system_get_file_list_sig(){
    File_List result = {};
    
    u8 *c_directory = push_array(arena, u8, directory.size + 1);
    block_copy(c_directory, directory.str, directory.size);
    c_directory[directory.size] = 0;
    
    DIR *dir = opendir((char*)c_directory);
    if (dir){
        File_Info* first = 0;
        File_Info* last = 0;
        i32 count = 0;
        
        for (struct dirent *entry = readdir(dir);
             entry;
             entry = readdir(dir)){
            char *c_file_name = entry->d_name;
            String_Const_u8 file_name = SCu8(c_file_name);
            
            if (string_match(file_name, string_u8_litexpr(".")) || string_match(file_name, string_u8_litexpr(".."))){
                continue;
            }
            
            File_Info *info = push_array(arena, File_Info, 1);
            sll_queue_push(first, last, info);
            count += 1;
            
            info->file_name = push_string_copy(arena, file_name);
            
            // NOTE(yuval): Get file attributes
            {
                Temp_Memory temp = begin_temp(arena);
                
                b32 append_slash = false;
                u64 file_path_size = directory.size + file_name.size;
                if (string_get_character(directory, directory.size - 1) != '/'){
                    append_slash = true;
                    file_path_size += 1;
                }
                
                char *file_path = push_array(arena, char, file_path_size + 1);
                char *file_path_at = file_path;
                
                block_copy(file_path_at, directory.str, directory.size);
                file_path_at += directory.size;
                
                if (append_slash){
                    *file_path_at = '/';
                    file_path_at += 1;
                }
                
                block_copy(file_path_at, file_name.str, file_name.size);
                file_path_at += file_name.size;
                
                *file_path_at = 0;
                
                info->attributes = mac_file_attributes_from_path(file_path);
                
                end_temp(temp);
            }
        }
        
        closedir(dir);
        
        result.infos = push_array(arena, File_Info*, count);
        result.count = count;
        
        i32 index = 0;
        for (File_Info *node = first;
             node != 0;
             node = node->next){
            result.infos[index] = node;
            index += 1;
        }
    }
    
    return(result);
}

function
system_quick_file_attributes_sig(){
    Temp_Memory temp = begin_temp(scratch);
    
    char *c_file_name = push_array(scratch, char, file_name.size + 1);
    block_copy(c_file_name, file_name.str, file_name.size);
    c_file_name[file_name.size] = 0;
    
    File_Attributes result = mac_file_attributes_from_path(c_file_name);
    
    end_temp(temp);
    
    return(result);
}

function inline Plat_Handle
mac_to_plat_handle(i32 fd){
    Plat_Handle result = *(Plat_Handle*)(&fd);
    return(result);
}

function inline i32
mac_to_fd(Plat_Handle handle){
    i32 result = *(i32*)(&handle);
    return(result);
}

function
system_load_handle_sig(){
    b32 result = false;
    
    i32 fd = open(file_name, O_RDONLY);
    if ((fd != -1) && (fd != 0)) {
        *out = mac_to_plat_handle(fd);
        result = true;
    }
    
    return(result);
}

function
system_load_attributes_sig(){
    i32 fd = mac_to_fd(handle);
    File_Attributes result = mac_file_attributes_from_fd(fd);
    
    return(result);
}

function
system_load_file_sig(){
    i32 fd = mac_to_fd(handle);
    
    do{
        ssize_t bytes_read = read(fd, buffer, size);
        if (bytes_read == -1){
            if (errno != EINTR){
                // NOTE(yuval): An error occured while reading from the file descriptor
                break;
            }
        } else{
            size -= bytes_read;
            buffer += bytes_read;
        }
    } while (size > 0);
    
    b32 result = (size == 0);
    return(result);
}

function
system_load_close_sig(){
    b32 result = true;
    
    i32 fd = mac_to_fd(handle);
    if (close(fd) == -1){
        // NOTE(yuval): An error occured while close the file descriptor
        result = false;
    }
    
    return(result);
}

function
system_save_file_sig(){
    File_Attributes result = {};
    
    i32 fd = open(file_name, O_WRONLY | O_TRUNC | O_CREAT, 00640);
    if (fd != -1) {
        do{
            ssize_t bytes_written = write(fd, data.str, data.size);
            if (bytes_written == -1){
                if (errno != EINTR){
                    // NOTE(yuval): An error occured while writing to the file descriptor
                    break;
                }
            } else{
                data.size -= bytes_written;
                data.str += bytes_written;
            }
        } while (data.size > 0);
        
        if (data.size == 0) {
            result = mac_file_attributes_from_fd(fd);
        }
        
        close(fd);
    }
    
    return(result);
}

////////////////////////////////

function inline System_Library
mac_to_system_library(void *dl_handle){
    System_Library result = *(System_Library*)(&dl_handle);
    return(result);
}

function inline void*
mac_to_dl_handle(System_Library system_lib){
    void *result = *(void**)(&system_lib);
    return(result);
}

function
system_load_library_sig(){
    b32 result = false;
    
    void *lib = 0;
    
    // NOTE(yuval): Open library handle
    {
        Temp_Memory temp = begin_temp(scratch);
        
        char *c_file_name = push_array(scratch, char, file_name.size + 1);
        block_copy(c_file_name, file_name.str, file_name.size);
        c_file_name[file_name.size] = 0;
        
        lib = dlopen(c_file_name, RTLD_LAZY | RTLD_GLOBAL);
        
        end_temp(temp);
    }
    
    if (lib){
        *out = mac_to_system_library(lib);
        result = true;
    }
    
    return(result);
}

function
system_release_library_sig(){
    void *lib = mac_to_dl_handle(handle);
    i32 rc = dlclose(lib);
    
    b32 result = (rc == 0);
    return(result);
}

function
system_get_proc_sig(){
    void *lib = mac_to_dl_handle(handle);
    Void_Func *result = (Void_Func*)dlsym(lib, proc_name);
    
    return(result);
}

////////////////////////////////

function
system_now_time_sig(){
    u64 now = mach_absolute_time();
    
    // NOTE(yuval): Now time nanoseconds conversion
    f64 now_nano = (f64)((f64)now *
                         ((f64)mac_vars.timebase_info.numer /
                          (f64)mac_vars.timebase_info.denom));
    
    // NOTE(yuval): Conversion to useconds
    u64 result = (u64)(now_nano * 1.0E-3);
    return(result);
}

function
system_wake_up_timer_create_sig(){
    Mac_Object *object = mac_alloc_object(MacObjectKind_Timer);
    dll_insert(&mac_vars.timer_objects, &object->node);
    
    object->timer = nil;
    
    Plat_Handle result = mac_to_plat_handle(object);
    return(result);
}

function
system_wake_up_timer_release_sig(){
    Mac_Object *object = mac_to_object(handle);
    if (object->kind == MacObjectKind_Timer){
        if ((object->timer != nil) && [object->timer isValid]) {
            [object->timer invalidate];
            mac_free_object(object);
        }
    }
}

function
system_wake_up_timer_set_sig(){
    Mac_Object *object = mac_to_object(handle);
    if (object->kind == MacObjectKind_Timer){
        f64 time_seconds = ((f64)time_milliseconds / 1000.0);
        object->timer = [NSTimer scheduledTimerWithTimeInterval:time_seconds
                target:mac_vars.view
                selector:@selector(request_display)
                userInfo:nil repeats:NO];
    }
}

function
system_signal_step_sig(){
    if (!mac_vars.step_requested){
        [NSTimer scheduledTimerWithTimeInterval:0.0
                target:mac_vars.view
                selector:@selector(request_display)
                userInfo:nil repeats:NO];
        
        mac_vars.step_requested = true;
    }
}

function
system_sleep_sig(){
    u64 nanoseconds = (microseconds * Thousand(1));
    u64 abs_sleep_time = (u64)((f64)nanoseconds *
                               ((f64)mac_vars.timebase_info.denom /
                                (f64)mac_vars.timebase_info.numer));
    
    u64 now = mach_absolute_time();
    mach_wait_until(now + abs_sleep_time);
}

////////////////////////////////

function
system_post_clipboard_sig(){
    Arena *arena = &mac_vars.clip_post_arena;
    if (arena->base_allocator == 0){
        *arena = make_arena_system();
    } else{
        linalloc_clear(arena);
    }
    
    mac_vars.clip_post.str = push_array(arena, u8, str.size + 1);
    if (mac_vars.clip_post.str != 0){
        block_copy(mac_vars.clip_post.str, str.str, str.size);
        mac_vars.clip_post.str[str.size] = 0;
        mac_vars.clip_post.size = str.size;
    } else{
        // NOTE(yuval): Failed to allocate buffer for clipboard post
    }
}

////////////////////////////////

function
system_cli_call_sig(){
    b32 result = false;
    
    int pipe_fds[2];
    if (pipe(pipe_fds) == -1){
        perror("system_cli_call: pipe");
        return(false);
    }
    
    pid_t child_pid = fork();
    if (child_pid == -1){
        perror("system_cli_call: fork");
        return(false);
    }
    
    enum { PIPE_FD_READ, PIPE_FD_WRITE };
    
    if (child_pid == 0){
        // NOTE(yuval): Child Process
        close(pipe_fds[PIPE_FD_READ]);
        dup2(pipe_fds[PIPE_FD_WRITE], STDOUT_FILENO);
        dup2(pipe_fds[PIPE_FD_WRITE], STDERR_FILENO);
        
        if (chdir(path) == -1){
            perror("system_cli_call: chdir");
            exit(1);
        }
        
        char* argv[] = {"sh", "-c", script, 0};
        
        if (execv("/bin/sh", argv) == -1){
            perror("system_cli_call: execv");
        }
        
        exit(1);
    } else{
        // NOTE(yuval): Parent Process
        close(pipe_fds[PIPE_FD_WRITE]);
        
        *(pid_t*)&cli_out->proc = child_pid;
        *(int*)&cli_out->out_read = pipe_fds[PIPE_FD_READ];
        *(int*)&cli_out->out_write = pipe_fds[PIPE_FD_WRITE];
        
        mac_vars.running_cli += 1;
    }
    
    return(true);
}

function
system_cli_begin_update_sig(){
    // NOTE(yuval): Nothing to do here.
}

function
system_cli_update_step_sig(){
    int pipe_read_fd = *(int*)&cli->out_read;
    
    fd_set fds;
    FD_ZERO(&fds);
    FD_SET(pipe_read_fd, &fds);
    
    struct timeval tv = {};
    
    size_t space_left = max;
    char* ptr = dest;
    
    while (space_left > 0 && (select(pipe_read_fd + 1, &fds, NULL, NULL, &tv) == 1)){
        ssize_t num = read(pipe_read_fd, ptr, space_left);
        if (num == -1){
            perror("system_cli_update_step: read");
        } else if (num == 0){
            // NOTE(inso): EOF
            break;
        } else{
            ptr += num;
            space_left -= num;
        }
    }
    
    *amount = (ptr - dest);
    
    b32 result = ((ptr - dest) > 0);
    return(result);
}

function
system_cli_end_update_sig(){
    b32 close_me = false;
    
    pid_t pid = *(pid_t*)&cli->proc;
    
    int status;
    if (pid && (waitpid(pid, &status, WNOHANG) > 0)){
        cli->exit = WEXITSTATUS(status);
        
        close(*(int*)&cli->out_read);
        close(*(int*)&cli->out_write);
        
        mac_vars.running_cli -= 1;
        
        close_me = true;
    }
    
    return(close_me);
}

////////////////////////////////

function
system_open_color_picker_sig(){
    NotImplemented;
}

function
system_get_screen_scale_factor_sig(){
    f32 result = mac_vars.screen_scale_factor;
    return(result);
}

////////////////////////////////

function void*
mac_thread_wrapper(void *ptr){
    Mac_Object *object = (Mac_Object*)ptr;
    Thread_Function *proc = object->thread.proc;
    void *object_ptr = object->thread.ptr;
    
    pthread_mutex_lock(&mac_vars.thread_launch_mutex);
    {
        mac_vars.waiting_for_launch = false;
        pthread_cond_signal(&mac_vars.thread_launch_cv);
    }
    pthread_mutex_unlock(&mac_vars.thread_launch_mutex);
    
    proc(object_ptr);
    
    return(0);
}

function
system_thread_launch_sig(){
    Mac_Object *object = mac_alloc_object(MacObjectKind_Thread);
    object->thread.proc = proc;
    object->thread.ptr = ptr;
    
    pthread_mutex_lock(&mac_vars.thread_launch_mutex);
    {
        mac_vars.waiting_for_launch = true;
        pthread_create(&object->thread.thread, 0, mac_thread_wrapper, object);
        
        while (mac_vars.waiting_for_launch){
            pthread_cond_wait(&mac_vars.thread_launch_cv, &mac_vars.thread_launch_mutex);
        }
    }
    pthread_mutex_unlock(&mac_vars.thread_launch_mutex);
    
    System_Thread result = mac_to_plat_handle(object);
    return(result);
}

function
system_thread_join_sig(){
    Mac_Object *object = mac_to_object(thread);
    if (object->kind == MacObjectKind_Thread){
        pthread_join(object->thread.thread, 0);
    }
}

function
system_thread_free_sig(){
    Mac_Object* object = mac_to_object(thread);
    if (object->kind == MacObjectKind_Thread){
        mac_free_object(object);
    }
}

function
system_thread_get_id_sig(){
    pthread_t id = pthread_self();
    i32 result = *(i32*)(&id);
    return(result);
}

function
system_mutex_make_sig(){
    Mac_Object *object = mac_alloc_object(MacObjectKind_Mutex);
    mac_init_recursive_mutex(&object->mutex);
    
    System_Mutex result = mac_to_plat_handle(object);
    return(result);
}

function
system_mutex_acquire_sig(){
    Mac_Object *object = mac_to_object(mutex);
    if (object->kind == MacObjectKind_Mutex){
        pthread_mutex_lock(&object->mutex);
    }
}

function
system_mutex_release_sig(){
    Mac_Object *object = mac_to_object(mutex);
    if (object->kind == MacObjectKind_Mutex){
        pthread_mutex_unlock(&object->mutex);
    }
}

function
system_mutex_free_sig(){
    Mac_Object *object = mac_to_object(mutex);
    if (object->kind == MacObjectKind_Mutex){
        pthread_mutex_destroy(&object->mutex);
        mac_free_object(object);
    }
}

function
system_acquire_global_frame_mutex_sig(){
    if (tctx->kind == ThreadKind_AsyncTasks){
        system_mutex_acquire(mac_vars.global_frame_mutex);
    }
}

function
system_release_global_frame_mutex_sig(){
    if (tctx->kind == ThreadKind_AsyncTasks){
        system_mutex_release(mac_vars.global_frame_mutex);
    }
}

function
system_condition_variable_make_sig(){
    Mac_Object *object = mac_alloc_object(MacObjectKind_CV);
    pthread_cond_init(&object->cv, 0);
    
    System_Condition_Variable result = mac_to_plat_handle(object);
    return(result);
}

function
system_condition_variable_wait_sig(){
    Mac_Object *object_cv = mac_to_object(cv);
    Mac_Object *object_mutex = mac_to_object(mutex);
    if ((object_cv->kind == MacObjectKind_CV) && (object_mutex->kind == MacObjectKind_Mutex)){
        pthread_cond_wait(&object_cv->cv, &object_mutex->mutex);
    }
}

function
system_condition_variable_signal_sig(){
    Mac_Object *object = mac_to_object(cv);
    if (object->kind == MacObjectKind_CV){
        pthread_cond_signal(&object->cv);
    }
}

function
system_condition_variable_free_sig(){
    Mac_Object *object = mac_to_object(cv);
    if (object->kind == MacObjectKind_CV){
        pthread_cond_destroy(&object->cv);
        mac_free_object(object);
    }
}

////////////////////////////////

struct Memory_Annotation_Tracker_Node{
    Memory_Annotation_Tracker_Node *next;
    Memory_Annotation_Tracker_Node *prev;
    String_Const_u8 location;
    u64 size;
};

struct Memory_Annotation_Tracker{
    Memory_Annotation_Tracker_Node *first;
    Memory_Annotation_Tracker_Node *last;
    i32 count;
};

global Memory_Annotation_Tracker memory_tracker = {};
global pthread_mutex_t memory_tracker_mutex;

global_const u64 ALLOCATION_SIZE_ADJUSTMENT = 64;

function void*
mac_memory_allocate_extended(void *base, u64 size, String_Const_u8 location){
    u64 adjusted_size = size + ALLOCATION_SIZE_ADJUSTMENT;
    void *memory = mmap(base, adjusted_size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
    Assert(memory != MAP_FAILED);
    
    Memory_Annotation_Tracker_Node *node = (Memory_Annotation_Tracker_Node*)memory;
    
    pthread_mutex_lock(&memory_tracker_mutex);
    {
        zdll_push_back(memory_tracker.first, memory_tracker.last, node);
        memory_tracker.count += 1;
    }
    pthread_mutex_unlock(&memory_tracker_mutex);
    
    node->location = location;
    node->size = size;
    
    void* result = (node + 1);
    return(result);
}

function void
mac_memory_free_extended(void *ptr){
    Memory_Annotation_Tracker_Node *node = (Memory_Annotation_Tracker_Node*)ptr;
    node -= 1;
    
    pthread_mutex_lock(&memory_tracker_mutex);
    {
        zdll_remove(memory_tracker.first, memory_tracker.last, node);
        memory_tracker.count -= 1;
    }
    pthread_mutex_unlock(&memory_tracker_mutex);
    
    munmap(node, node->size + ALLOCATION_SIZE_ADJUSTMENT);
}

function
system_memory_allocate_sig(){
    void* result = mac_memory_allocate_extended(0, size, location);
    return(result);
}

function
system_memory_set_protection_sig(){
    b32 result = true;
    
    int protect = 0;
    switch (flags & 0x7){
        case 0:
        {
            protect = PROT_NONE;
        } break;
        
        case MemProtect_Read:
        {
            protect = PROT_READ;
        } break;
        
        case MemProtect_Write:
        case MemProtect_Read | MemProtect_Write:
        {
            protect = PROT_READ | PROT_WRITE;
        } break;
        
        case MemProtect_Execute:
        {
            protect = PROT_EXEC;
        } break;
        
        case MemProtect_Execute | MemProtect_Read:
        {
            protect = PROT_READ | PROT_EXEC;
        } break;
        
        // NOTE(inso): some W^X protection things might be unhappy about this one
        case MemProtect_Execute | MemProtect_Write:
        case MemProtect_Execute | MemProtect_Write | MemProtect_Read:
        {
            protect = PROT_READ | PROT_WRITE | PROT_EXEC;
        } break;
    }
    
    Memory_Annotation_Tracker_Node *node = (Memory_Annotation_Tracker_Node*)ptr;
    node -= 1;
    
    if(mprotect(node, size, protect) == -1){
        result = false;
    }
    
    return(result);
}

function
system_memory_free_sig(){
    mac_memory_free_extended(ptr);
}

function
system_memory_annotation_sig(){
    Memory_Annotation result = {};
    
    pthread_mutex_lock(&memory_tracker_mutex);
    {
        for (Memory_Annotation_Tracker_Node *node = memory_tracker.first;
             node != 0;
             node = node->next){
            Memory_Annotation_Node *r_node = push_array(arena, Memory_Annotation_Node, 1);
            sll_queue_push(result.first, result.last, r_node);
            result.count += 1;
            
            r_node->location = node->location;
            r_node->address = node + 1;
            r_node->size = node->size;
        }
        
    }
    pthread_mutex_unlock(&memory_tracker_mutex);
    
    return(result);
}

////////////////////////////////

function
system_show_mouse_cursor_sig(){
    mac_vars.cursor_show = show;
}

function
system_set_fullscreen_sig(){
    // NOTE(yuval): Read comment in system_set_fullscreen_sig in win32_4ed.cpp
    mac_vars.do_toggle = (mac_vars.full_screen != full_screen);
    
    b32 success = true;
    return(success);
}

function
system_is_fullscreen_sig(){
    // NOTE(yuval): Read comment in system_is_fullscreen_sig in win32_4ed.cpp
    b32 result = (mac_vars.full_screen != mac_vars.do_toggle);
    return(result);
}

function
system_get_keyboard_modifiers_sig(){
    Input_Modifier_Set result = copy_modifier_set(arena, &mac_vars.input_chunk.pers.modifiers);
    return(result);
}

////////////////////////////////

/**********************/
/*    Graphics API    */
/**********************/

////////////////////////////////

function
graphics_get_texture_sig(){
    u32 result = renderer->get_texture(renderer, dim, texture_kind);
    return(result);
}

function
graphics_fill_texture_sig(){
    b32 result = renderer->fill_texture(renderer, texture_kind, texture, p, dim, data);
    return(result);
}

////////////////////////////////

/******************/
/*    Font API    */
/******************/

////////////////////////////////

function
font_make_face_sig(){
    Face* result = ft__font_make_face(arena, description, scale_factor);
    return(result);
}

////////////////////////////////