/*
 * @author  xamidev <xamidev@riseup.net>
 * @brief   Framebuffer-based terminal driver
 * @license GPL-3.0-only
 */

// Terminal output
/*
There are a couple of bugs here and there but for now I don't care too much
because this shitty implementation will be replaced one day by Flanterm
(once memory management is okay: paging & kernel malloc)
*/

#include <stddef.h>
#include <kernel.h>
#include <io/term/term.h>
#include <config.h>
#include <io/term/flanterm.h>
#include <io/term/flanterm_backends/fb.h>
#include <mem/kheap.h>
#include <limine.h>
#include <stdarg.h>
#include <sched/spinlock.h>
#include <io/serial/serial.h>

#define NANOPRINTF_IMPLEMENTATION
#include <io/term/nanoprintf.h>

extern struct flanterm_context* ft_ctx;
extern struct init_status init;

struct spinlock term_lock = {0};
struct spinlock printf_lock = {0};

extern int panic_count;

/*
 * internal_putc - Internal putchar function
 * @c: char to print
 * @_: (unused, for nanoprintf)
 *
 * Prints a character to the terminal if it's ready,
 * and also to the serial interface if it's ready.
 */
void internal_putc(int c, void *_)
{
    (void)_;
    char ch = (char)c;

    if (init.terminal) {
        if (panic_count == 0) {
            spinlock_acquire(&term_lock);
            if (ch == '\n') {
                char cr = '\r';
                flanterm_write(ft_ctx, &cr, 1);
            }
            flanterm_write(ft_ctx, &ch, 1);
            spinlock_release(&term_lock);
        } else {
            if (ch == '\n') {
                char cr = '\r';
                flanterm_write(ft_ctx, &cr, 1);
            }
            flanterm_write(ft_ctx, &ch, 1);
        }
    }

    if (init.serial) {
        if (ch == '\n') {
            skputc('\r');
        }
        skputc(ch);
    }
}

/*
 * debug_putc - Internal DEBUG putchar function
 * @c: char to print
 * @_: (unused, for nanoprintf)
 *
 * Prints a character to the terminal if it's ready and if 
 * the kernel is still initializing, and also always to the
 * serial interface if it's ready.
 */
void debug_putc(int c, void *_)
{
    (void)_;
    char ch = (char)c;

    if (init.terminal && (!init.all || panic_count > 0)) {
        if (panic_count == 0) {
            spinlock_acquire(&term_lock);
            if (ch == '\n') {
                char cr = '\r';
                flanterm_write(ft_ctx, &cr, 1);
            }
            flanterm_write(ft_ctx, &ch, 1);
            spinlock_release(&term_lock);
        } else {
            if (ch == '\n') {
                char cr = '\r';
                flanterm_write(ft_ctx, &cr, 1);
            }
            flanterm_write(ft_ctx, &ch, 1);
        }
    }

    if (init.serial) {
        if (ch == '\n') {
            skputc('\r');
        }
        skputc(ch);
    }
}

/*
 * printf - Fromatted printing
 * @fmt: format string
 * @...: variadic arguments
 *
 * Wrapper for nanoprintf
 *
 * Return:
 * <ret> - number of characters sent to the callback
 * %-1   - error
 */
int printf(const char* fmt, ...)
{
    if (panic_count == 0) {
        spinlock_acquire(&printf_lock);
        va_list args;
        va_start(args, fmt);
        int ret = npf_vpprintf(internal_putc, NULL, fmt, args);
        va_end(args);
        spinlock_release(&printf_lock);
        return ret;
    } else {
        va_list args;
        va_start(args, fmt);
        int ret = npf_vpprintf(internal_putc, NULL, fmt, args);
        va_end(args);
        return ret;
    }
    return -1;
}

/*
 * kprintf - Fromatted DEBUG printing 
 * @fmt: format string
 * @...: variadic arguments
 *
 * Wrapper for nanoprintf; to be used only for
 * kernel/debug messages.
 *
 * Return:
 * <ret> - number of characters sent to the callback
 * %-1   - error
 */
int kprintf(const char* fmt, ...)
{
    if (panic_count == 0) {
        spinlock_acquire(&printf_lock);
        va_list args;
        va_start(args, fmt);
        int ret = npf_vpprintf(debug_putc, NULL, fmt, args);
        va_end(args);
        spinlock_release(&printf_lock);
        return ret;
    } else {
        va_list args;
        va_start(args, fmt);
        int ret = npf_vpprintf(debug_putc, NULL, fmt, args);
        va_end(args);
        return ret;
    }
    return -1;
}

/*
 * kputs - Kernel puts
 * @str: String to write
 *
 * Writes a non-formatted string to terminal
 */
void kputs(const char* str)
{
	size_t i=0;
	while (str[i] != 0) {
		internal_putc(str[i], NULL);
		i++;
	}
}

extern struct flanterm_context* ft_ctx;
extern struct boot_context boot_ctx;

/*
 * term_init - Video output/terminal initialization
 *
 * Uses Flanterm and the framebuffer given by Limine.
 */
void term_init()
{
	uint32_t bgColor = 0x252525;
	ft_ctx = flanterm_fb_init(
        NULL,
        NULL,
        boot_ctx.fb->address, boot_ctx.fb->width, boot_ctx.fb->height, boot_ctx.fb->pitch,
        boot_ctx.fb->red_mask_size, boot_ctx.fb->red_mask_shift,
        boot_ctx.fb->green_mask_size, boot_ctx.fb->green_mask_shift,
        boot_ctx.fb->blue_mask_size, boot_ctx.fb->blue_mask_shift,
        NULL,
        NULL, NULL,
        &bgColor, NULL,
        NULL, NULL,
        NULL, 0, 0, 1,
        0, 0,
        0,
        0
    );
	init.terminal = true;
}