/*
 * Copyright 2020 Hans-Kristian Arntzen for Valve Corporation
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#ifdef VKD3D_ENABLE_PROFILING

#define VKD3D_DBG_CHANNEL VKD3D_DBG_CHANNEL_API

#include "vkd3d_profiling.h"
#include "vkd3d_platform.h"
#include "vkd3d_threads.h"
#include "vkd3d_debug.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

#ifdef _WIN32
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#else
#include <sys/types.h>
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
#endif

static pthread_once_t profiling_block_once = PTHREAD_ONCE_INIT;
static unsigned int profiling_region_count;
static spinlock_t profiling_lock;

struct vkd3d_profiling_block
{
    uint64_t ticks_total;
    uint64_t iteration_total;
    char name[64 - 2 * sizeof(uint64_t)];
};

static struct vkd3d_profiling_block *mapped_blocks;

#define VKD3D_MAX_PROFILING_REGIONS 256
static spinlock_t region_locks[VKD3D_MAX_PROFILING_REGIONS];

#ifdef _WIN32
static void vkd3d_init_profiling_path(const char *path)
{
    HANDLE profiling_fd;
    HANDLE file_view;
    char path_pid[_MAX_PATH];

    snprintf(path_pid, sizeof(path_pid), "%s.%u", path, GetCurrentProcessId());
    profiling_fd = CreateFileA(path_pid, GENERIC_READ | GENERIC_WRITE,
            FILE_SHARE_READ, NULL, CREATE_ALWAYS,
            FILE_ATTRIBUTE_NORMAL, INVALID_HANDLE_VALUE);

    if (profiling_fd == INVALID_HANDLE_VALUE)
    {
        ERR("Failed to open profiling FD.\n");
        return;
    }

    file_view = CreateFileMappingA(profiling_fd, NULL, PAGE_READWRITE, 0,
            VKD3D_MAX_PROFILING_REGIONS * sizeof(*mapped_blocks), NULL);
    if (file_view == INVALID_HANDLE_VALUE)
    {
        ERR("Failed to create profiling file view.\n");
        CloseHandle(profiling_fd);
        return;
    }

    mapped_blocks = MapViewOfFile(file_view, FILE_MAP_ALL_ACCESS, 0, 0,
            VKD3D_MAX_PROFILING_REGIONS * sizeof(*mapped_blocks));
    if (!mapped_blocks)
        ERR("Failed to map view of file.\n");
    CloseHandle(file_view);
    CloseHandle(profiling_fd);
}
#else
static void vkd3d_init_profiling_path(const char *path)
{
    int profiling_fd;
    char path_pid[PATH_MAX];

    snprintf(path_pid, sizeof(path_pid), "%s.%u", path, getpid());
    profiling_fd = open(path_pid, O_RDWR | O_CREAT, 0644);

    if (profiling_fd >= 0)
    {
        if (ftruncate(profiling_fd, VKD3D_MAX_PROFILING_REGIONS * sizeof(*mapped_blocks)) < 0)
        {
            ERR("Failed to resize profiling FD.\n");
            close(profiling_fd);
            return;
        }
        mapped_blocks = mmap(NULL, VKD3D_MAX_PROFILING_REGIONS * sizeof(*mapped_blocks),
                PROT_READ | PROT_WRITE, MAP_SHARED, profiling_fd, 0);
        if (!mapped_blocks)
        {
            ERR("Failed to map block.\n");
            close(profiling_fd);
            return;
        }
        memset(mapped_blocks, 0, VKD3D_MAX_PROFILING_REGIONS * sizeof(*mapped_blocks));
        close(profiling_fd);
    }
    else
    {
        ERR("Failed to open profiling FD.\n");
    }
}
#endif

static void vkd3d_init_profiling_once(void)
{
    char path[VKD3D_PATH_MAX];

    vkd3d_get_env_var("VKD3D_PROFILE_PATH", path, sizeof(path));
    if (strlen(path) > 0)
        vkd3d_init_profiling_path(path);
}

void vkd3d_init_profiling(void)
{
    pthread_once(&profiling_block_once, vkd3d_init_profiling_once);
}

bool vkd3d_uses_profiling(void)
{
    return mapped_blocks != NULL;
}

unsigned int vkd3d_profiling_register_region(const char *name, spinlock_t *lock, uint32_t *latch)
{
    unsigned int index;
    if (!mapped_blocks)
        return 0;

    spinlock_acquire(lock);

    if (*latch == 0)
    {
        spinlock_acquire(&profiling_lock);
        /* Begin at 1, 0 is reserved as a sentinel. */
        index = ++profiling_region_count;
        if (index <= VKD3D_MAX_PROFILING_REGIONS)
        {
            strncpy(mapped_blocks[index - 1].name, name, sizeof(mapped_blocks[index - 1].name) - 1);
            /* Important to store with release semantics after we've initialized the block. */
            vkd3d_atomic_uint32_store_explicit(latch, index, vkd3d_memory_order_release);
        }
        else
        {
            ERR("Too many profiling regions!\n");
            index = 0;
        }
        spinlock_release(&profiling_lock);
    }
    else
        index = *latch;

    spinlock_release(lock);
    return index;
}

void vkd3d_profiling_notify_work(unsigned int index,
        uint64_t start_ticks, uint64_t end_ticks,
        unsigned int iteration_count)
{
    struct vkd3d_profiling_block *block;
    spinlock_t *lock;

    if (index == 0 || index > VKD3D_MAX_PROFILING_REGIONS || !mapped_blocks)
        return;
    index--;

    lock = &region_locks[index];
    block = &mapped_blocks[index];

    spinlock_acquire(lock);
    block->iteration_total += iteration_count;
    block->ticks_total += end_ticks - start_ticks;
    spinlock_release(lock);
}

#endif /* VKD3D_ENABLE_PROFILING */
