#include "lprintf.h"
#include "lprintf_config.h"
#include <stdarg.h>
#include <string.h>

#ifndef LPRINTF_NO_FLOAT
    #define LPRINTF_NO_FLOAT 0
#endif

static void f_putstr_ch(char c) {
    static char prev_cr = 0;

    if ((c == '\n') && !prev_cr)
        lprintf_putchar('\r');

    prev_cr = c == '\r';
    lprintf_putchar(c);
}

static const char* f_atoi(const char* str, int *val) {
    int t_val = 0;
    const char *p = str;
    for (; (*str >= '0') && (*str <= '9'); ++str) {
        t_val *= 10;
        t_val += *str - '0';
    }

    if (p!=str)
        *val = t_val;
    return str;
}

#define LPRINT_FLAG_FILL_NULL    0x01 /* заполнение расширения ширины числа нулями, а не пробелами */
#define LPRINT_FLAG_LJ           0x02 /* выравнивание по левому краю */
#define LPRINT_FLAG_HEX_PREFIX   0x04 /* вывод 0x/0X для hex-числе */
#define LPRINT_FLAG_UPPER        0x08 /* вывод hex в верхнем регистре */
#define LPRINT_FLAG_UNSIGNED     0x10 /* признак, что число должно интерпретироваться как unsigned */
#define LPRINT_FLAG_FORCE_SIGN   0x20 /* признак обязательного вывода знака даже для положительных чисел */
#define LPRINT_FLAG_FORCE_NEG    0x40 /* принудительно указать отрицательный знак */

#define LPRINT_LEN_STD          0
#define LPRINT_LEN_LONG         1
#define LPRINT_LEN_LONGLONG     2
#define LPRINT_LEN_SHORT        3
#define LPRINT_LEN_SHORTSHORT   4
#define LPRINT_LEN_SIZET        5


/**************************************************
 *  если с - флаг - обновляем flags и возвращаем 1
 *  иначе - возвращаем 0
 *************************************************/
static int f_check_flags(const char* c, unsigned *flags, unsigned *len) {
    int res = 0;
    switch (*c) {
    case '0':
        *flags |= LPRINT_FLAG_FILL_NULL;
        res = 1;
        break;
    case '-':
        *flags |= LPRINT_FLAG_LJ;
        res = 1;
        break;
    case '+':
        *flags |= LPRINT_FLAG_FORCE_SIGN;
        res = 1;
        break;
    case '#':
        *flags |= LPRINT_FLAG_HEX_PREFIX;
        res = 1;
        break;
    case 'l': /* fallthrough */
    case 'L':
        *len = (*len == LPRINT_LEN_LONG) ? LPRINT_LEN_LONGLONG : LPRINT_LEN_LONG;
        res = 1;
        break;
    case 'h':
        *len = (*len == LPRINT_LEN_SHORT) ? LPRINT_LEN_SHORTSHORT : LPRINT_LEN_SHORT;
        res = 1;
        break;
    case 'z':
        *len = LPRINT_LEN_SIZET;
        res = 1;
        break;
    }
    return res;
}


#define PRINT_HEX_PREFIX() if (flags&LPRINT_FLAG_HEX_PREFIX) \
    { lprintf_putchar('0');    lprintf_putchar(flags & LPRINT_FLAG_UPPER ? 'X' : 'x'); }

/* печать в форматах %x и %X */
static void f_print_hex(unsigned long val, unsigned flags, int width) {
    int i, j, start_print=0;

    for (i = 2 * sizeof(val) - 1; i >= 0; i--) {
        char c = (val >> (i<<2)) & 0xF;
        if ((c || (i == 0)) && !start_print) {
            start_print = i+1;
            /* выводим нужное кол-во нулей или пробелов перед */
            if (!(flags & LPRINT_FLAG_LJ)) {
                if (flags& LPRINT_FLAG_FILL_NULL) {
                    PRINT_HEX_PREFIX();
                }
                for (j = i + 1; j < width; ++j)
                    lprintf_putchar((flags & LPRINT_FLAG_FILL_NULL) ? '0' : ' ');
                if (!(flags & LPRINT_FLAG_FILL_NULL)) {
                    PRINT_HEX_PREFIX();
                }
            } else {
                PRINT_HEX_PREFIX();
            }
        }

        /* печать символа из преобразования */
        if (c || start_print) {
            if (c <= 9) {
                lprintf_putchar('0' + c);
            } else if (flags & LPRINT_FLAG_UPPER) {
                lprintf_putchar('A' + c - 10);
            } else {
                lprintf_putchar('a' + c - 10);
            }
        }
    }

    if (flags & LPRINT_FLAG_LJ) {
        for (j = start_print; j < width; ++j)
            lprintf_putchar(' ');
    }
}

static void f_print_dec(long int val, unsigned flags, int width) {
    int digts = 1, i, neg = 0;
    unsigned long div, uval, max_div;
    int sign_chars = 0;

    if (flags & LPRINT_FLAG_UNSIGNED) {
        uval = (unsigned long)val;
    } else if (val < 0) {
        neg = 1;
        uval = -val;
    } else {
        uval = val;
        if (flags & LPRINT_FLAG_FORCE_NEG)
            neg = 1;
    }

   if (neg || (flags & LPRINT_FLAG_FORCE_SIGN)) {
       ++sign_chars;
   }

    max_div = (1UL << (sizeof(int)*8-1))/10;

    /* определяем кол-во цифр */
    for (digts = 1, div = 10; (uval >= div) && (div < max_div); ++digts, div *= 10) {
        continue;
    }

    if (uval >= div) {
        ++digts;
    } else {
        div/=10;
    }

    /* если дополняем число нулями, то знак выводится
     * до нулей, иначе - после пробелов перед числом */
    if (sign_chars && (flags & LPRINT_FLAG_FILL_NULL))
        lprintf_putchar(neg ? '-' : '+');

    /* выводим начальные пробелы или нули */
    if (!(flags & LPRINT_FLAG_LJ)) {
        for (i = digts + sign_chars; i < width; ++i)
            lprintf_putchar(flags & LPRINT_FLAG_FILL_NULL ? '0' : ' ');
    }

    if (sign_chars && !(flags & LPRINT_FLAG_FILL_NULL))
        lprintf_putchar(neg ? '-' : '+');

    while (div!=0) {
        char c;
        c = uval/div;
        uval %= div;
        lprintf_putchar('0' + c);
        div/=10;
    }

    /* если left just - выводим пробелы в конце, если нужны */
    if (flags & LPRINT_FLAG_LJ) {
        for (i = digts; i < width; ++i)
            lprintf_putchar(' ');
    }
}

#if !LPRINTF_NO_FLOAT
static void f_print_double(double val, int flags, int width, int fract_width) {
    int int_part = (int)val;
    int fract_mul = 10;
    long long fract_part;

    if (fract_width <= 0)
        fract_width = 6;

    for (int i = 0; i < fract_width - 1; ++i) {
        fract_mul *= 10;
    }



    fract_part = ((long long)(val * fract_mul)) % fract_mul;
    if (fract_part < 0)
        fract_part =-fract_part;
    /* для отрицательного числа меньше 1 целая часть будет 0 и знак не будет выведен по
     * умолчанию. для исправления явно указываем для отрицательного числа знак */
    f_print_dec(int_part, flags | (val < 0 ? LPRINT_FLAG_FORCE_NEG : 0), width - fract_width - 1);
    f_putstr_ch('.');
    f_print_dec(fract_part, (flags | LPRINT_FLAG_FILL_NULL) & ~LPRINT_FLAG_FORCE_SIGN, fract_width);
}
#endif

static void f_print_str(const char *str, int flags, int width, int maxlen) {
    int len = 0;
    if (!(flags & LPRINT_FLAG_LJ) && (width > 0)) {
        const char *ptr;

        for (ptr = str; (*ptr != '\0') && ((maxlen <= 0) || (len < maxlen)); ++ptr)
            ++len;

        /* при обычном выравнивании выводим пробелы вначале, если длина
          строки меньше заданной ширины */
        for ( ; len < width; ++len) {
            f_putstr_ch(' ');
        }
    }

    for (len=0; (*str != '\0') && ((maxlen <= 0) || (len < maxlen)); ++str) {
        f_putstr_ch(*str);
        ++len;
    }

    if (flags & LPRINT_FLAG_LJ) {
        for ( ; len < width; ++len) {
            f_putstr_ch(' ');
        }
    }
}



int lprintf(const char* fmt, ...) {
    const char* first = fmt;
    va_list vl;
    va_start(vl,fmt);

    for (; *fmt != 0; ++fmt)  {
        if (*fmt != '%') {
            f_putstr_ch(*fmt);
        } else {
            const char *start_fmt = fmt;
            ++fmt;

            if (*fmt == '%') {
                lprintf_putchar('%');
            } else {
                int width = 0;
                int fract_width = 0;
                unsigned flags = 0;
                unsigned len = LPRINT_LEN_STD;

                /* читаем флаги */
                while (f_check_flags(fmt, &flags, &len))
                    ++fmt;

                /* пробуем прочитать длину (если * - из аргумента, иначе как число в формате) */
                if (*fmt == '*') {
                    ++fmt;
                    width = va_arg(vl, int);
                } else {
                    fmt = f_atoi(fmt, &width);
                }

                /* если идет ., то после нее - длина дробной части (или макс длина строки) */
                if (*fmt == '.') {
                    ++fmt;
                    if (*fmt == '*') {
                        ++fmt;
                        fract_width = va_arg(vl, int);
                    } else {
                        fmt = f_atoi(fmt, &fract_width);
                    }
                }

                /* чтение флагов, которые идут за размером поля */
                while (f_check_flags(fmt, &flags, &len))
                    ++fmt;

                switch(*fmt) {
                    case 'X': /* fallthrough */
                    case 'x': /* fallthrough */
                    case 'u': {
                            unsigned long val;

                            if (len == LPRINT_LEN_LONG) {
                                val = va_arg(vl, unsigned);
                            } else if (len == LPRINT_LEN_LONGLONG) {
                                val = (unsigned long)va_arg(vl, unsigned long long);
                            } else if (len == LPRINT_LEN_SIZET) {
                                val = va_arg(vl, size_t);
                            } else if (len == LPRINT_LEN_SHORT) {
                                val = va_arg(vl, unsigned) & 0xFFFF;
                            } else if (len == LPRINT_LEN_SHORTSHORT) {
                                val = va_arg(vl, unsigned) & 0xFF;
                            } else {
                                val = va_arg(vl, unsigned);
                            }

                            if (*fmt == 'X') {
                                flags |= LPRINT_FLAG_UPPER;
                                f_print_hex(val, flags, width);
                            } else if (*fmt == 'x') {
                                f_print_hex(val, flags, width);
                            } else {
                                flags |= LPRINT_FLAG_UNSIGNED;
                                f_print_dec((long int)val, flags, width);
                            }
                            break;
                        }
                    case 'd': /* fallthrough */
                    case 'i': {
                            long int val;
                            if (len == LPRINT_LEN_LONG) {
                                val = va_arg(vl, long int);
                            } else if (len == LPRINT_LEN_LONGLONG) {
                                val = (long int)va_arg(vl, long long int);
                            } else if (len == LPRINT_LEN_SIZET) {
                                val = va_arg(vl, size_t);
                            } else if (len == LPRINT_LEN_SHORT) {
                                val = va_arg(vl, int) & 0xFFFF;
                            } else if (len == LPRINT_LEN_SHORTSHORT) {
                                val = va_arg(vl, int) & 0xFF;
                            } else {
                                val = va_arg(vl, int);
                            }
                            f_print_dec((long int)val, flags, width);
                        }
                        break;
                    case 's': {
                            const char *str = va_arg(vl, char*);
                            f_print_str(str, flags, width, fract_width);
                        }
                        break;
                    case 'c': {
                            int ch = va_arg(vl, int);
                            f_putstr_ch((char)ch);
                        }
                        break;
#if !LPRINTF_NO_FLOAT
                    case 'f': {
                            double val = va_arg(vl, double);
                            f_print_double(val, flags, width, fract_width);
                        }
                        break;
#endif
                    default:
                        fmt = start_fmt;
                        break;
                }
            }
        }
    }

    va_end(vl);
    return (fmt-first);
}


