/*
 * 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:
 *     Stream.cpp - base class for character-based streams
 * ----------------------------------------------------------------------------------
 * Copyright (C) 2008 David A. Mellis. All right reserved.
 *
 * 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
 *
 * Parsing functions based on TextFinder library by Michael Margolis
 * ----------------------------------------------------------------------------------
 */


#include "CoreSys.h"
#include "CoreLib.h"
#include "Stream.h"


namespace eCxx {

namespace NodeMCU {


bool Stream::find(const char* target)
{ return findUntil( target, "" ); }


bool Stream::find(const char* target, size_t length)
{ return findUntil(target, length, nullptr, 0); }


bool Stream::findUntil(const char* target, const char* terminator)
{ return findUntil( target, strlen(target), terminator, strlen(terminator) ); }


bool Stream::findUntil(const char *target, size_t targetLen, const char *terminator, size_t termLen)
{
    // return true if target is a null string
    if(*target == 0) return true;

    size_t index     = 0;
    size_t termIndex = 0;

    int32_t c;
    while( ( c = timedRead() ) > 0 ) {
        // Reset index if any char does not match
        if(c != target[index]) index = 0;

        if(c == target[index]) {
            // Return true if all chars in the target match
            if(++index >= targetLen) return true;
        }

        if( termLen > 0 && c == terminator[termIndex] ) {
            // Return false if terminate string found before target string
            if(++termIndex >= termLen) return false;
        }
        else {
            termIndex = 0;
        }
    }

    return false;
}


size_t Stream::readBytes(char* buffer, size_t length)
{
    size_t count = 0;

    while(count < length) {
        const int32_t c = timedRead();
        if(c < 0) break;
        *buffer++ = static_cast<char>(c);
        count++;
    }

    return count;
}


size_t Stream::readBytesUntil(char terminator, char* buffer, size_t length)
{
    if(length < 1) return 0;

    size_t index = 0;

    while(index < length) {
        const int32_t c = timedRead();
        if(c < 0 || c == terminator) break;
        *buffer++ = static_cast<char>(c);
        index++;
    }

    return index;
}


const WString Stream::readString()
{
    WString ret;

    int32_t c = timedRead();

    while(c >= 0) {
        ret += static_cast<char>(c);
        c = timedRead();
    }

    return ret;
}


const WString Stream::readStringUntil(char terminator)
{
    WString ret;

    int32_t c = timedRead();

    while(c >= 0 && c != terminator) {
        ret += static_cast<char>(c);
        c = timedRead();
    }

    return ret;
}


long Stream::parseInt(char skipChar)
{
    bool    isNegative = false;
    long    value      = 0;
    int32_t c;

    c = peekNextDigit();

    // Ignore non numeric leading characters
    if(c < 0) return 0;

    do {
        if(c == skipChar) {
            // Ignore this charactor
        }
        else if(c == '-') {
            isNegative = true;
        }
        else if(c >= '0' && c <= '9') {
            value = value * 10 + c - '0';
        }
        // Consume the character that has just been peeked
        read();
        c = timedPeek();
    } while( (c >= '0' && c <= '9') || c == skipChar );

    if(isNegative) value = -value;

    return value;
}


float Stream::parseFloat(char skipChar)
{
    bool    isNegative = false;
    bool    isFraction = false;
    long    value      = 0;
    int32_t c;

    float   fraction   = 1.0f;

    c = peekNextDigit();

    // Ignore non numeric leading characters
    if(c < 0) return 0;

    do {
        if(c == skipChar) {
            // Ignore this charactor
        }
        else if(c == '-') {
            isNegative = true;
        }
        else if(c == '.') {
            isFraction = true;
        }
        else if(c >= '0' && c <= '9') {
            value = value * 10 + c - '0';
            if(isFraction) fraction *= 0.1f;
        }
        // Consume the character that has just been peeked
        read();
        c = timedPeek();
    } while( (c >= '0' && c <= '9') || c == '.' || c == skipChar );

    if(isNegative) value = -value;

    if(isFraction) return value * fraction;
    return value;
}


int32_t Stream::timedRead()
{
    _startMillis = millis();

    do {
        const int32_t c = read();
        if(c >= 0) return c;
        if(_timeout == 0) return -1;
        yield();
    } while( millis() - _startMillis < _timeout );

    return -1; // -1 indicates timeout
}


int32_t Stream::timedPeek()
{
    _startMillis = millis();

    do {
        const int32_t c = peek();
        if(c >= 0) return c;
        if(_timeout == 0) return -1;
        yield();
    } while( millis() - _startMillis < _timeout );

    return -1; // -1 indicates timeout
}


int32_t Stream::peekNextDigit(){
    // Returns peek of the next digit in the stream or -1 if timeout;
    // discards non-numeric characters

    while(1) {
        const int32_t c = timedPeek();
        if(c < 0) return c; // Timeout
        if(c == '-') return c;
        if(c >= '0' && c <= '9') return c;
        read(); // Discard non-numeric
    }
}


} // namespace NodeMCU

} // namespace eCxx
