/*
 * Copyright (C) 2018-2020 Western Digital Corporation or its affiliates
 * Copyright (C) 2017-2018 Wearable, Inc.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at https://mozilla.org/MPL/2.0/.
 * 
 * This Source Code Form is “Incompatible With Secondary Licenses”,
 * as defined by the Mozilla Public License, v. 2.0.
 */

#include "state.h"
#include "types.h"
#include "platform_hooks.h"

static TIMESTAMP start_time;
static TIMESTAMP sys_time;

TIMESTAMP atomic_time_get(TIMESTAMP* val)
{
    return __atomic_load_n(val, __ATOMIC_SEQ_CST);
}

void exit_hook(nvm_state_t* pState) {}

void idle_hook(nvm_state_t* pState)
{
    platform_wait_events(pState->wake_time - sys_time);
}

#ifdef __APPLE_CC__
#include <mach/clock.h>
#include <mach/mach.h>
#include <pthread.h>

#define MACH_CALL(call) \
    do {                                    \
        kern_return_t status = (call);      \
        if (status != KERN_SUCCESS) {       \
            mach_error(#call ": ", status); \
            abort();                        \
        }                                   \
    } while (0)

static mach_port_t clock_port;
static clock_serv_t clock_service;

void get_nanotime(TIMESTAMP* sec, TIMESTAMP* nsec)
{
    mach_timespec_t clock_time;
    MACH_CALL(clock_get_time(clock_service, &clock_time));
    *sec = (TIMESTAMP) clock_time.tv_sec;
    *nsec = (TIMESTAMP) clock_time.tv_nsec;
}

static TIMESTAMP timespec_to_timestamp(mach_timespec_t ts)
{
    return ((TIMESTAMP) ts.tv_sec) * 1000 + ((TIMESTAMP) ts.tv_nsec) / 1000000;
}

static void* time_server(void* opaque)
{
#define TIMER_INTERVAL    10
#define TIMER_INTERVAL_NS (1000*1000*TIMER_INTERVAL)

    mach_timespec_t clock_time;

    struct {
        mach_msg_header_t header;
        uint8_t data[512];
    } clock_msg;
    memset(&clock_msg, 0, sizeof(clock_msg));

    while (1) {
        MACH_CALL(clock_get_time(clock_service, &clock_time));
        __atomic_store_n(&sys_time, timespec_to_timestamp(clock_time) - start_time, __ATOMIC_SEQ_CST);
        clock_time.tv_nsec += TIMER_INTERVAL_NS;
        if (clock_time.tv_nsec == 1000*1000*1000) {
            clock_time.tv_sec++;
            clock_time.tv_nsec = 0;
        }
        MACH_CALL(clock_alarm(clock_service, TIME_ABSOLUTE, clock_time, clock_port));
    }
}

void init_time(nvm_state_t* pState)
{
    mach_timespec_t clock_time;
    MACH_CALL(mach_port_allocate(mach_task_self(), MACH_PORT_RIGHT_RECEIVE, &clock_port));
    MACH_CALL(host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &clock_service));
    MACH_CALL(clock_get_time(clock_service, &clock_time));
    start_time = timespec_to_timestamp(clock_time);
    clock_time.tv_nsec -= clock_time.tv_nsec % TIMER_INTERVAL_NS;
    MACH_CALL(clock_alarm(clock_service, TIME_ABSOLUTE, clock_time, clock_port));

    pState->sys_time = &sys_time;
    static pthread_t thread;
    if (pthread_create(&thread, NULL, time_server, pState) != 0) {
        abort();
    }
}
#else
#include <signal.h>
#include <time.h>

static TIMESTAMP timespec_to_timestamp(struct timespec ts)
{
    return ((TIMESTAMP) ts.tv_sec) * 1000 + ((TIMESTAMP) ts.tv_nsec) / 1000000;
}

static void timer_handler(int signo, siginfo_t *si, void *uc)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    __atomic_store_n(&sys_time, timespec_to_timestamp(ts) - start_time, __ATOMIC_SEQ_CST);
}

void get_nanotime(TIMESTAMP* sec, TIMESTAMP* nsec)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    *sec = (TIMESTAMP) ts.tv_sec;
    *nsec = (TIMESTAMP) ts.tv_nsec;
}

void init_time(nvm_state_t* pState)
{
    struct timespec ts;
    clock_gettime(CLOCK_MONOTONIC, &ts);
    start_time = timespec_to_timestamp(ts);

    struct sigaction sa = { .sa_sigaction = timer_handler, .sa_flags = SA_RESTART };
    sigemptyset(&sa.sa_mask);
    if (sigaction(SIGALRM, &sa, NULL) == -1) {
        perror("sigaction");
        exit(1);
    }

    timer_t t;
    struct sigevent ev = { .sigev_notify = SIGEV_SIGNAL, .sigev_signo = SIGALRM };
    if (timer_create(CLOCK_MONOTONIC, &ev, &t) == -1) {
        perror("timer_create");
        exit(1);
    }

    struct itimerspec it = { .it_interval = (struct timespec) { .tv_nsec = 10000000 },
                             .it_value    = (struct timespec) { .tv_nsec = 10000000 }};
    if (timer_settime(t, 0, &it, NULL) == -1) {
        perror("timer_settime");
        exit(1);
    }

    pState->sys_time = &sys_time;
}
#endif


