// Copyright 2018-2019 Espressif Systems (Shanghai) PTE LTD
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "sdkconfig.h"

#include "esp_attr.h"

#include "esp8266/eagle_soc.h"
#include "esp8266/uart_register.h"
#include "esp8266/rom_functions.h"

#ifdef CONFIG_ETS_PRINTF_EXIT_WHEN_FLASH_RW
#define ETS_PRINTF_ATTR IRAM_ATTR
#else
#define ETS_PRINTF_ATTR
#endif

#ifndef CONFIG_ESP_CONSOLE_UART_NONE
static void uart_putc(int c)
{
    while (1) {
        uint32_t fifo_cnt = READ_PERI_REG(UART_STATUS(CONFIG_ESP_CONSOLE_UART_NUM)) & (UART_TXFIFO_CNT << UART_TXFIFO_CNT_S);

        if ((fifo_cnt >> UART_TXFIFO_CNT_S & UART_TXFIFO_CNT) < 126)
            break;
    }

    WRITE_PERI_REG(UART_FIFO(CONFIG_ESP_CONSOLE_UART_NUM) , c);
}
#else
#define uart_putc(_c) { }
#endif

int __attribute__ ((weak)) ets_putc(int c)
{
#ifdef CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF
    if (c == '\n')
        uart_putc('\r');
#elif defined(CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR)
    if (c == '\n')
        c = '\r';
#endif

    uart_putc(c);

    return c;
}

#if defined(CONFIG_USING_NEW_ETS_VPRINTF) && !defined(BOOTLOADER_BUILD)

#define FILL_0      0x01
#define FILL_LEFT   0x02
#define POINTOR     0x04
#define ALTERNATE   0x08
#define OUPUT_INT   0x10
#define START       0x20
#define END         0x40

#define VINT_STR_MAX 10

typedef union _val_cache {
    uint8_t         val8;
    int32_t         val32;
    uint32_t        val32u;
    const char      *valcp;
} val_cache_t;

typedef struct _val_attr {
    uint8_t         type;
    uint8_t         state;
    uint8_t         fillbytes;
    uint8_t         precision;

    val_cache_t     value;
} val_attr_t;

#define isdigit(_c)                 ((_c <= '9') && (_c >= '0'))
#define fill_num(_attr)             ((attr)->fillbytes)
#define isfill_0(_attr)             (fill_num(_attr) && ((_attr)->state & FILL_0))
#define isfill_left(_attr)          (fill_num(_attr) && ((_attr)->state & FILL_LEFT))
#define isstart(_attr)              ((_attr)->state & START)

static inline void ets_printf_ch_mutlti(uint32_t c, uint32_t len)
{
    while (len--)
        ets_putc(c);
}

static inline void ets_printf_buf(const char *s, uint32_t len)
{
    while (len--)
        ets_putc(*s++);
}

static int ets_printf_str(const val_attr_t * const attr)
{
    const char *s = attr->value.valcp;
    s = s == NULL ? "<null>" : s;

    if (fill_num(attr)) {
        unsigned char left;
        unsigned char len;

        while (*s != '\0')
            s++;
        len = s - attr->value.valcp;
        left = fill_num(attr) > len ? fill_num(attr) - len : 0;

        if (!isfill_left(attr)) {
            ets_printf_ch_mutlti(' ', left);
        }

        ets_printf_buf(attr->value.valcp, len);

        if (isfill_left(attr)) {
            ets_printf_ch_mutlti(' ', left);
        }
    } else {
        while (*s != '\0')
            ets_putc(*s++);
    }

    return 0;
}

static int ets_printf_int(val_attr_t * const attr, uint8_t hex)
{
    char buf[VINT_STR_MAX];
    unsigned char offset = VINT_STR_MAX;

    if (attr->value.val32u != 0) {
        for (; attr->value.val32u > 0; attr->value.val32u /= hex) {
            unsigned char c = attr->value.val32u % hex;
            if (c < 10)
                buf[--offset] = c + '0';
            else
                buf[--offset] = c + 'a' - 10;
        }
    } else
        buf[--offset] = '0';

    if (fill_num(attr)) {
        char fill_data = isfill_0(attr) ? '0' : ' ';
        unsigned char len = fill_num(attr) - (VINT_STR_MAX - offset);
        unsigned char left = fill_num(attr) > (VINT_STR_MAX - offset) ? len : 0;

        if (!isfill_left(attr)) {
            ets_printf_ch_mutlti(fill_data, left);
        }

        ets_printf_buf(&buf[offset], VINT_STR_MAX - offset);

        if (isfill_left(attr)) {
            fill_data = ' ';
            ets_printf_ch_mutlti(fill_data, left);
        }
    } else {
        ets_printf_buf(&buf[offset], VINT_STR_MAX - offset);
    }

    return 0;
}

int ets_vprintf(const char *fmt, va_list va)
{
    for (; ;) {
        const char *ps = fmt;
        val_attr_t attr;

        while (*ps != '\0' && *ps != '%')
            ets_putc(*ps++);

        if (*ps == '\0')
            break;

        fmt = ps;

        memset(&attr, 0, sizeof(val_attr_t));

        for (; ;) {
            switch (*++ps) {
                case '#':
                    attr.state |= ALTERNATE;
                    ps++;
                    break;
                case '0'...'9':
                    if ((!isstart(&attr) || *(ps - 1) == '-') && *ps == '0') {
                        attr.state |= FILL_0;
                    } else {
                        if (attr.state & POINTOR)
                            attr.precision = attr.precision * 10 + *ps - '0';
                        else
                            attr.fillbytes = attr.fillbytes * 10 + *ps - '0';
                    }
                    break;
                case '.':
                    attr.state |= POINTOR;
                    break;
                case '-':
                    attr.state |= FILL_LEFT;
                    break;
                default:
                    attr.type = *ps++;
                    attr.state |= END;
                    break;
            }

            if (attr.state & END)
                break;
            
            attr.state |= START;
        }

        switch (attr.type) {
            case 'c':
                attr.value.val8 = (char)va_arg(va, int);
                ets_putc(attr.value.val8);
                break;
            case 's':
                attr.value.valcp = va_arg(va, const char *);
                ets_printf_str(&attr);
                break;
            case 'i':
            case 'd':
                attr.value.val32 = va_arg(va, int);
                if (attr.value.val32 < 0) {
                    ets_putc('-');
                    attr.value.val32 = -attr.value.val32;
                }
                ets_printf_int(&attr, 10);
                break;
            case 'u':
                attr.value.val32u = va_arg(va, unsigned int);
                ets_printf_int(&attr, 10);
                break;
            case 'x':
                attr.value.val32u = va_arg(va, unsigned int);
                ets_printf_int(&attr, 16);
                break;
            case 'p':
                ets_printf_buf("0x", 2);
                attr.value.valcp = va_arg(va, const char *);
                ets_printf_int(&attr, 16);
                break;
            case '%':
                ets_putc('%');
                break;
            default:
                ets_putc('%');
                ps = fmt + 1;
                break;
        }

        fmt = ps;
    }

    return 0;
}

#else /* defined(CONFIG_USING_NEW_ETS_VPRINTF) && !defined(BOOTLOADER_BUILD) */

int ets_vprintf(const char *fmt, va_list ap)
{
    return ets_io_vprintf(ets_putc, fmt, ap);
}

#endif /* defined(CONFIG_USING_NEW_ETS_VPRINTF) && !defined(BOOTLOADER_BUILD) */

/**
 * Re-write ets_printf in SDK side, since ets_printf in ROM will use a global
 * variable which address is in heap region of SDK side. If use ets_printf in ROM,
 * this variable maybe re-write when heap alloc and modification.
 * 
 * Using new "ets_vprintf" costs stack without alignment and accuracy:
 *                      just "fmt": 136 Bytes
 *                            "%s": 172 Bytes
 *      "%p", "%d, "%i, "%u", "%x": 215 Bytes
 */
int ETS_PRINTF_ATTR ets_printf(const char *fmt, ...)
{
    va_list ap;
    int ret;

#ifdef CONFIG_ETS_PRINTF_EXIT_WHEN_FLASH_RW
    extern uint8_t FlashIsOnGoing;

    if (FlashIsOnGoing) {
        return -1;
    }
#endif

    va_start(ap, fmt);
    ret = ets_vprintf(fmt, ap);
    va_end(ap);

    return ret;
}