#include "stm32f4xx.h"

#include <stdarg.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>

typedef struct
{
    char *buffer;
    size_t capacity;
    size_t length;
} OutputBuffer;

static void output_char(OutputBuffer *output, char value)
{
    if (output->capacity != 0U && output->length + 1U < output->capacity)
    {
        output->buffer[output->length] = value;
    }
    output->length++;
}

static void output_unsigned(OutputBuffer *output, uint32_t value, unsigned int base,
                            unsigned int width, char pad, int uppercase)
{
    char digits[16];
    unsigned int count = 0U;
    const char *alphabet = uppercase ? "0123456789ABCDEF" : "0123456789abcdef";

    do
    {
        digits[count++] = alphabet[value % base];
        value /= base;
    } while (value != 0U);

    while (count < width)
    {
        output_char(output, pad);
        width--;
    }
    while (count != 0U)
    {
        output_char(output, digits[--count]);
    }
}

int vsnprintf(char *buffer, size_t size, const char *format, va_list args)
{
    OutputBuffer output = {buffer, size, 0U};

    while (*format != '\0')
    {
        unsigned int width = 0U;
        char pad = ' ';
        int is_long = 0;
        char specifier;

        if (*format != '%')
        {
            output_char(&output, *format++);
            continue;
        }
        format++;
        if (*format == '%')
        {
            output_char(&output, *format++);
            continue;
        }
        if (*format == '0')
        {
            pad = '0';
            format++;
        }
        while (*format >= '0' && *format <= '9')
        {
            width = width * 10U + (unsigned int)(*format++ - '0');
        }
        if (*format == 'l')
        {
            is_long = 1;
            format++;
        }
        specifier = *format++;
        if (specifier == 's')
        {
            const char *text = va_arg(args, const char *);
            if (text == NULL)
            {
                text = "(null)";
            }
            while (*text != '\0')
            {
                output_char(&output, *text++);
            }
        }
        else if (specifier == 'c')
        {
            output_char(&output, (char)va_arg(args, int));
        }
        else if (specifier == 'd' || specifier == 'i')
        {
            int32_t value = is_long ? (int32_t)va_arg(args, long) : (int32_t)va_arg(args, int);
            uint32_t magnitude;
            if (value < 0)
            {
                output_char(&output, '-');
                magnitude = 0U - (uint32_t)value;
                if (width != 0U)
                {
                    width--;
                }
            }
            else
            {
                magnitude = (uint32_t)value;
            }
            output_unsigned(&output, magnitude, 10U, width, pad, 0);
        }
        else if (specifier == 'u' || specifier == 'x' || specifier == 'X')
        {
            uint32_t value = is_long ? (uint32_t)va_arg(args, unsigned long)
                                     : (uint32_t)va_arg(args, unsigned int);
            unsigned int base = specifier == 'u' ? 10U : 16U;
            output_unsigned(&output, value, base, width, pad, specifier == 'X');
        }
        else if (specifier == 'p')
        {
            uintptr_t value = (uintptr_t)va_arg(args, void *);
            output_char(&output, '0');
            output_char(&output, 'x');
            output_unsigned(&output, (uint32_t)value, 16U, 8U, '0', 0);
        }
        else
        {
            output_char(&output, '%');
            output_char(&output, specifier);
        }
    }

    if (output.capacity != 0U)
    {
        size_t terminator = output.length < output.capacity ? output.length : output.capacity - 1U;
        output.buffer[terminator] = '\0';
    }
    return (int)output.length;
}

int snprintf(char *buffer, size_t size, const char *format, ...)
{
    int result;
    va_list args;
    va_start(args, format);
    result = vsnprintf(buffer, size, format, args);
    va_end(args);
    return result;
}

int sprintf(char *buffer, const char *format, ...)
{
    int result;
    va_list args;
    va_start(args, format);
    result = vsnprintf(buffer, (size_t)-1, format, args);
    va_end(args);
    return result;
}

int printf(const char *format, ...)
{
    char buffer[192];
    int result;
    size_t count;
    va_list args;

    va_start(args, format);
    result = vsnprintf(buffer, sizeof(buffer), format, args);
    va_end(args);
    count = result < 0 ? 0U : (size_t)result;
    if (count >= sizeof(buffer))
    {
        count = sizeof(buffer) - 1U;
    }
    for (size_t index = 0U; index < count; index++)
    {
        while ((USART2->SR & USART_SR_TC) == 0U)
        {
        }
        USART2->DR = (uint8_t)buffer[index];
    }
    return result;
}

void *memcpy(void *destination, const void *source, size_t count)
{
    uint8_t *to = (uint8_t *)destination;
    const uint8_t *from = (const uint8_t *)source;
    while (count-- != 0U)
    {
        *to++ = *from++;
    }
    return destination;
}

void *memset(void *destination, int value, size_t count)
{
    uint8_t *to = (uint8_t *)destination;
    while (count-- != 0U)
    {
        *to++ = (uint8_t)value;
    }
    return destination;
}

int memcmp(const void *left, const void *right, size_t count)
{
    const uint8_t *lhs = (const uint8_t *)left;
    const uint8_t *rhs = (const uint8_t *)right;
    while (count-- != 0U)
    {
        if (*lhs != *rhs)
        {
            return (int)*lhs - (int)*rhs;
        }
        lhs++;
        rhs++;
    }
    return 0;
}

size_t strlen(const char *string)
{
    const char *end = string;
    while (*end != '\0')
    {
        end++;
    }
    return (size_t)(end - string);
}

char *strcpy(char *destination, const char *source)
{
    char *result = destination;
    do
    {
        *destination++ = *source;
    } while (*source++ != '\0');
    return result;
}

int abs(int value)
{
    return value < 0 ? -value : value;
}

double sqrt(double value)
{
    double estimate;
    if (value <= 0.0)
    {
        return value == 0.0 ? 0.0 : (0.0 / 0.0);
    }
    estimate = value > 1.0 ? value : 1.0;
    for (unsigned int iteration = 0U; iteration < 12U; iteration++)
    {
        estimate = 0.5 * (estimate + value / estimate);
    }
    return estimate;
}

double pow(double base, double exponent)
{
    int power = (int)exponent;
    unsigned int magnitude;
    double result = 1.0;
    if ((double)power != exponent)
    {
        return 0.0 / 0.0;
    }
    magnitude = power < 0 ? (unsigned int)(-power) : (unsigned int)power;
    while (magnitude != 0U)
    {
        if ((magnitude & 1U) != 0U)
        {
            result *= base;
        }
        base *= base;
        magnitude >>= 1U;
    }
    return power < 0 ? 1.0 / result : result;
}

double atan(double value)
{
    const double half_pi = 1.5707963267948966;
    const double quarter_pi = 0.7853981633974483;
    double sign = 1.0;
    double result;
    if (value < 0.0)
    {
        value = -value;
        sign = -1.0;
    }
    if (value > 1.0)
    {
        double reciprocal = 1.0 / value;
        result = half_pi - reciprocal * (quarter_pi + 0.273 * (1.0 - reciprocal));
    }
    else
    {
        result = value * (quarter_pi + 0.273 * (1.0 - value));
    }
    return sign * result;
}

double tan(double value)
{
    const double pi = 3.1415926535897932;
    double squared;
    double sine;
    double cosine;
    while (value > 1.5707963267948966)
    {
        value -= pi;
    }
    while (value < -1.5707963267948966)
    {
        value += pi;
    }
    squared = value * value;
    sine = value * (1.0 + squared * (-1.0 / 6.0 + squared * (1.0 / 120.0 +
           squared * (-1.0 / 5040.0 + squared * (1.0 / 362880.0)))));
    cosine = 1.0 + squared * (-1.0 / 2.0 + squared * (1.0 / 24.0 +
             squared * (-1.0 / 720.0 + squared * (1.0 / 40320.0))));
    return sine / cosine;
}
