/*
 * Copyright (c) 2020 Raspberry Pi (Trading) Ltd.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <machine/time.h>
#include <time.h>
#include <sys/time.h>
#include <sys/times.h>

#include "pico.h"
#if LIB_PICO_STDIO
#include "pico/stdio.h"
#endif

#if PICO_ENTER_USB_BOOT_ON_EXIT
#include "pico/bootrom.h"
#endif

#include "pico/time.h"
#include "pico/runtime_init.h"

#if LIB_PICO_PRINTF_PICO
#include "pico/printf.h"
#else
#define weak_raw_printf printf
#define weak_raw_vprintf vprintf
#endif

static int picolibc_putc(char c, __unused FILE *file) {
#if LIB_PICO_STDIO
    stdio_putchar(c);
#endif
    return c;
}

static int picolibc_getc(__unused FILE *file) {
#if LIB_PICO_STDIO
    return stdio_getchar();
#else
    return -1;
#endif
}

static int picolibc_flush(__unused FILE *file) {
#if LIB_PICO_STDIO
    stdio_flush();
#endif
    return 0;
}

static FILE __stdio = FDEV_SETUP_STREAM(picolibc_putc,
                                        picolibc_getc,
                                        picolibc_flush,
                                        _FDEV_SETUP_RW);

#ifdef __GNUCLIKE_PRAGMA_DIAGNOSTIC
#pragma GCC diagnostic ignored "-Wpragmas"
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
#pragma GCC diagnostic ignored "-Wredundant-decls"
#endif

FILE *const stdin = &__stdio; __strong_reference(stdin, stdout); __strong_reference(stdin, stderr);

void __weak __assert_func(const char *file, int line, const char *func, const char *failedexpr) {
    weak_raw_printf("assertion \"%s\" failed: file \"%s\", line %d%s%s\n",
                    failedexpr, file, line, func ? ", function: " : "",
                    func ? func : "");

    _exit(1);
}


void __attribute__((noreturn)) __weak _exit(__unused int status) {
#if PICO_ENTER_USB_BOOT_ON_EXIT
    reset_usb_boot(0,0);
#else
    while (1) {
        __breakpoint();
    }
#endif
}

int __weak getpid(void) {
    return 0;
}

int __weak kill(__unused int pid, __unused int sig) {
    __breakpoint();
    return -1;
}

static int64_t epoch_time_us_since_boot;

__weak int gettimeofday (struct timeval *__restrict tv, __unused void *__restrict tz) {
    if (tv) {
        int64_t us_since_epoch = ((int64_t)to_us_since_boot(get_absolute_time())) - epoch_time_us_since_boot;
        tv->tv_sec = (time_t)(us_since_epoch / 1000000);
        tv->tv_usec = (suseconds_t)(us_since_epoch % 1000000);
    }
    return 0;
}

__weak int settimeofday(__unused const struct timeval *tv, __unused const struct timezone *tz) {
    if (tv) {
        int64_t us_since_epoch = tv->tv_sec * 1000000 + tv->tv_usec;
        epoch_time_us_since_boot = (int64_t)to_us_since_boot(get_absolute_time()) - us_since_epoch;
    }
    return 0;
}

__weak clock_t times(struct tms *tms) {
#if CLOCKS_PER_SEC >= 1000000
    tms->tms_utime = (clock_t)(to_us_since_boot(get_absolute_time()) * (CLOCKS_PER_SEC / 1000000));
#else
    tms->tms_utime = (clock_t)(to_us_since_boot(get_absolute_time()) / (1000000 / CLOCKS_PER_SEC));
#endif
    tms->tms_stime = 0;
    tms->tms_cutime = 0;
    tms->tms_cstime = 0;
    return 0;
}

void runtime_init(void) {
#ifndef NDEBUG
    if (__get_current_exception()) {
        // crap; started in exception handler
        __breakpoint();
    }
#endif

#if !PICO_RUNTIME_SKIP_INIT_PER_CORE_INSTALL_STACK_GUARD
    // install core0 stack guard
    extern char __StackBottom;
    runtime_init_per_core_install_stack_guard(&__StackBottom);
#endif

    // piolibc __libc_init_array does __preint_array and __init_array
    extern void __libc_init_array(void);
    __libc_init_array();
}
