/*
 * Copyright (C) 2020-2021 Aloysius Indrayanto
 *
 * This file is part of the eCxx library, see LICENSE file for the license details.
 */

/*
 * ----------------------------------------------------------------------------------
 * The code in this file is copied (with a slight modifications) from:
 *     core_esp8266_noniso.cpp - nonstandard (but useful) conversion functions
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2014 Ivan Grokhotkov. All rights reserved.
 *
 * This file is part of the esp8266 core for Arduino environment.
 *
 * This library is free software; you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any later
 * version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along
 * with this library; if not, write to the Free Software Foundation, Inc., 51
 * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
 * ----------------------------------------------------------------------------------
 */


#include <ieeefp.h>
#include <stdlib.h>

#ifdef ECXX_NODEMCU_USE_NEWLIB
    #include <math.h>
#endif

#include "CoreUtil.h"


extern "C" {


extern char* ltoa(long value, char* result, int base)
{ return itoa( static_cast<int>(value), result, base ); }


extern char* ultoa(unsigned long value, char* result, int base)
{ return utoa( static_cast<unsigned int>(value), result, base ); }


extern char* dtostrf(double number, signed char width, unsigned char prec, char* s)
{
    // Check for NaN and Inf
    if( isnan(number) ) {
        strcpy(s, "nan");
        return s;
    }

    if( isinf(number) ) {
        strcpy(s, "inf");
        return s;
    }

    // How many cells to fill for the integer part
    eCxx::int32_t fillme = width;
    if(prec > 0) fillme -= (prec + 1);

    // Handle negative numbers
    bool negative = false;

    if(number < 0.0) {
        negative = true;
        number   = -number;
        --fillme;
    }

    // Round correctly so that print(1.999, 2) prints as "2.00"
    double rounding = 2.0;
    for(eCxx::uint8_t i = 0; i < prec; ++i) rounding *= 10.0;
    rounding = 1.0 / rounding;

    number += rounding;

    // Figure out how big the number really is
    eCxx::int32_t digitcount = 1;
    double        tenpow     = 1.0;
    double        nextpow;
    while( number >= ( nextpow = ( 10.0 * tenpow ) ) ) {
        tenpow = nextpow;
        ++digitcount;
    }

    // Minimal compensation for possible lack of precision
    number *= 1 + eCxx::NumericLimits<decltype(number)>::epsilon();

    number /= tenpow;
    fillme -= digitcount;

    // Pad unused cells with spaces
    char* out = s;

    while(fillme-- > 0) *out++ = ' ';

    // Handle negative sign
    if(negative) *out++ = '-';

    // Print the digits and the decimal point
    digitcount += prec;

    while(digitcount-- > 0) {
        eCxx::uint8_t digit = static_cast<eCxx::uint8_t>(number);
        if(digit > 9) digit = 9;

        *out++ = static_cast<char>('0' | digit);
        if( (digitcount == prec) && (prec > 0) ) *out++ = '.';

        number -= digit;
        number *= 10.0;
    }

    // Make sure the returned string is null terminated
    *out = 0;

    return s;
}


//
// Backwards search for p_pcPattern in p_pcString
// Based on https://stackoverflow.com/a/1634398/2778898
//
const char* strrstr(const char* __restrict p_pcString, const char* __restrict p_pcPattern)
{
    const char* pcResult        = 0;

    size_t      stStringLength  = ( p_pcString  ? strlen(p_pcString ) : 0 );
    size_t      stPatternLength = ( p_pcPattern ? strlen(p_pcPattern) : 0 );

    if(stStringLength && stPatternLength && stPatternLength <= stStringLength) {
        // Pattern is shorter or has the same length than the string
        for(const char* s = (p_pcString + stStringLength - stPatternLength); s >= p_pcString; --s) {
            if(strncmp(s, p_pcPattern, stPatternLength) == 0) {
                pcResult = s;
                break;
            }
        }
    }

    return pcResult;
}


} // extern "C"
