/*
 * Copyright (C) 2016 The Android Open Source Project
 *
 * 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 <stdio.h>
#include <printf.h>
#include <cpu/inc/cpuMath.h>


static uint32_t StrPrvPrintfEx_number(printf_write_c putc_, void* userData, uint64_t number, bool base10, bool zeroExtend, bool isSigned, uint32_t padToLength, bool caps, bool* bail)
{
    char buf[64];
    uint32_t idx = sizeof(buf) - 1;
    uint32_t chr, i;
    bool neg = false;
    uint32_t numPrinted = 0;

    *bail = false;

    if (padToLength > sizeof(buf) - 1)
        padToLength = sizeof(buf) - 1;

    buf[idx--] = 0;    //terminate

    if (isSigned) {

        if (((int64_t)number) < 0) {

            neg = true;
            number = -number;
        }
    }

    do{
        if (base10) {
            uint64_t t = U64_DIV_BY_CONST_U16(number, 10);
            chr = (number - t * 10) + '0';
            number = t;
        }
        else {
            chr = number & 0x0F;
            number >>= 4;
            chr = (chr >= 10) ? (chr + (caps ? 'A' : 'a') - 10) : (chr + '0');
        }

        buf[idx--] = chr;

        numPrinted++;

    } while (number);

    if (neg) {

        buf[idx--] = '-';
        numPrinted++;
    }

    if (padToLength > numPrinted) {

        padToLength -= numPrinted;
    }
    else {

        padToLength = 0;
    }

    while (padToLength--) {

        buf[idx--] = zeroExtend ? '0' : ' ';
        numPrinted++;
    }

    idx++;


    for(i = 0; i < numPrinted; i++) {

        if (!putc_(userData,(buf + idx)[i])) {

            *bail = true;
            break;
        }
    }


    return i;
}

static uint32_t StrVPrintf_StrLen_withMax(const char* s, uint32_t max)
{
    uint32_t len = 0;

    while ((*s++) && (len < max)) len++;

    return len;
}

static uint32_t StrVPrintf_StrLen(const char* s)
{
    uint32_t len = 0;

    while (*s++) len++;

    return len;
}

static inline char prvGetChar(const char** fmtP)
{

    return *(*fmtP)++;
}

uint32_t cvprintf(printf_write_c putc_f, void* userData, const char* fmtStr, va_list vl)
{

    char c, t;
    uint32_t numPrinted = 0;
    uint64_t val64;

#define putc_(_ud,_c)                \
        do {                 \
            if (!putc_f(_ud,_c))    \
                goto out;    \
        } while(0)

    while ((c = prvGetChar(&fmtStr)) != 0) {

        if (c == '\n') {

            putc_(userData,c);
            numPrinted++;
        }
        else if (c == '%') {

            bool zeroExtend = false, useLong = false, useLongLong = false, useSizeT = false, bail = false, caps = false;
            uint32_t padToLength = 0, len, i;
            const char* str;

more_fmt:

            c = prvGetChar(&fmtStr);

            switch(c) {

                case '%':

                    putc_(userData,c);
                    numPrinted++;
                    break;

                case 'c':

                    t = va_arg(vl,unsigned int);
                    putc_(userData,t);
                    numPrinted++;
                    break;

                case 's':

                    str = va_arg(vl,char*);
                    if (!str) str = "(null)";
                    if (padToLength)
                        len = StrVPrintf_StrLen_withMax(str,padToLength);
                    else
                        padToLength = len = StrVPrintf_StrLen(str);

                    if (len > padToLength)
                        len = padToLength;
                    else {
                        for(i = len; i < padToLength; i++)
                            putc_(userData, ' ');
                    }
                    numPrinted += padToLength;
                    for(i = 0; i < len; i++)
                        putc_(userData,*str++);

                    numPrinted += len;
                    break;

                case '0':
                case '.':

                    if (!zeroExtend && !padToLength) {

                        zeroExtend = true;
                        goto more_fmt;
                    }

                case '1':
                case '2':
                case '3':
                case '4':
                case '5':
                case '6':
                case '7':
                case '8':
                case '9':

                    padToLength = (padToLength * 10) + c - '0';
                    goto more_fmt;

#define GET_UVAL64() \
        useSizeT ? va_arg(vl, size_t) :                 \
        useLongLong ? va_arg(vl, unsigned long long) :  \
        useLong ? va_arg(vl, unsigned long) :           \
        va_arg(vl, unsigned int)

#define GET_SVAL64() \
        useSizeT ? va_arg(vl, size_t) :                 \
        useLongLong ? va_arg(vl, signed long long) :    \
        useLong ? va_arg(vl, signed long) :             \
        va_arg(vl, signed int)

                case 'u':

                    val64 = GET_UVAL64();
                    numPrinted += StrPrvPrintfEx_number(putc_f, userData, val64, true, zeroExtend,0,padToLength,0,&bail);
                    if (bail)
                        goto out;
                    break;

                case 'd':
                case 'i':

                    val64 = GET_SVAL64();
                    numPrinted += StrPrvPrintfEx_number(putc_f, userData, val64, true, zeroExtend, true, padToLength, false, &bail);
                    if (bail)
                        goto out;
                    break;

                case 'X':
                    caps = true;

                case 'x':

                    val64 = GET_UVAL64();
                    numPrinted += StrPrvPrintfEx_number(putc_f, userData, val64, false, zeroExtend, false, padToLength, caps, &bail);
                    if (bail)
                        goto out;
                    break;

                case 'p':
                    putc_(userData,'0');
                    putc_(userData,'x');
                    numPrinted += 2;
                    val64 = (uintptr_t)va_arg(vl, const void*);
                    numPrinted += StrPrvPrintfEx_number(putc_f, userData, val64, false, zeroExtend, false, padToLength, caps, &bail);
                    if (bail)
                        goto out;
                    break;

#undef GET_UVAL64
#undef GET_SVAL64

                case 'h':
                    // Technically, we're supposed to cast down to short/char, so that
                    // { int x = 256; printf("%hhd", x); } would yield "0" (assuming 32-bit int
                    // and 8-bit char). But the more common usage would be to expect the caller
                    // to do printf("%hhd", (char) x) to get this output, which we're assuming
                    // here to make our implementation simpler.
                    goto more_fmt;

                case 'L':
                case 'l':
                    if (useLong)
                        useLongLong = true;
                    useLong = true;
                    goto more_fmt;

                case 'z':
                    useSizeT = true;
                    goto more_fmt;

                default:

                    putc_(userData,c);
                    numPrinted++;
                    break;

            }
        }
        else {

            putc_(userData,c);
            numPrinted++;
        }
    }

out:

    return numPrinted;
}