/*
BASED ON Stream.h B(describe below) UT MODIFIED BY S. SImmons TO BE
COMPATIBLE WITH UnoArduSim Parser


 Stream.cpp - adds parsing methods to Stream class
 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

 Created July 2011
 parsing functions based on TextFinder library by Michael Margolis

 findMulti/findUntil routines written by Jim Leonard/Xuth
 */

#include "Stream.h"

#define PARSE_TIMEOUT 1000  // default number of milli-seconds to wait

// protected method to read stream with timeout
int Stream::timedRead()
{
	int c;
	_startMillis = millis();
	do 
	{
		c = read();
		if (c >= 0) 
			return c;
	} while(millis() - _startMillis < _timeout);
	return -1;     // -1 indicates timeout
}

// protected method to peek stream with timeout
int Stream::timedPeek()
{
	int c;
	_startMillis = millis();
	do 
	{
		c = peek();
		if (c >= 0) 
			return c;
	} while(millis() - _startMillis < _timeout);
	return -1;     // -1 indicates timeout
}

// returns peek of the next digit in the stream or -1 if timeout
// discards non-numeric characters
int Stream::peekNextDigit(LookaheadMode lookahead, bool detectDecimal)
{
	int c;
	while (1) 
	{
		c = timedPeek();
		
		if( c < 0 || c == '-' || (c >= '0' && c <= '9') ||	(detectDecimal && c == '.')) 
			return c;
		
		switch( lookahead )
		{
		case SKIP_NONE: 
			return -1; // Fail code.
		case SKIP_WHITESPACE:
			switch( c )
			{
			case ' ':
			case '\t':
			case '\r':
			case '\n': 
				break;
			default: 
				return -1; // Fail code.
		}
	case SKIP_ALL:
		break;
}
read();  // discard non-numeric
}
return -1;//NEVER-REACHED BUT UnoArduSim INSISTS function have a retrun value HERE
}

// Public Methods
/////////////////////////////////////////////////////////////

void Stream::setTimeout(unsigned long timeout)  // sets the maximum number of milliseconds to wait
{
_timeout = timeout;
}

// find returns true if the target string is found
bool  Stream::find(char *target)
{
return findUntil(target, strlen(target), NULL, 0);
}

// reads data from the stream until the target string of given length is found
// returns true if target string is found, false if timed out
bool Stream::find(char *target, size_t length)
{
return findUntil(target, length, NULL, 0);
}

// as find but search ends if the terminator string is found
bool  Stream::findUntil(char *target, char *terminator)
{
return findUntil(target, strlen(target), terminator, strlen(terminator));
}

// reads data from the stream until the target string of the given length is found
// search terminated if the terminator string is found
// returns true if target string is found, false if terminated or timed out
bool Stream::findUntil(char *target, size_t targetLen, char *terminator, size_t termLen)
{
if (terminator == NULL) {
MultiTarget t[1] = {{target, targetLen, 0}};
return findMulti(t, 1) == 0 ? true : false;
} else {
MultiTarget t[2] = {{target, targetLen, 0}, {terminator, termLen, 0}};
return findMulti(t, 2) == 0 ? true : false;
}
}

// returns the first valid (long) integer value from the current position.
// lookahead determines how parseInt looks ahead in the stream.
// See LookaheadMode enumeration at the top of the file.
// Lookahead is terminated by the first character that is not a valid part of an integer.
// Once parsing commences, 'ignore' will be skipped in the stream.
long Stream::parseInt(LookaheadMode lookahead, char ignore)
{
bool isNegative = false;
long value = 0;
int c;

c = peekNextDigit(lookahead, false);
// ignore non numeric leading characters
if(c < 0)
return 0; // zero returned if timeout

do{
if(c == ignore)
	c-c; // ignore this character
else if(c == '-')
	isNegative = true;
else if(c >= '0' && c <= '9')        // is c a digit?
	value = value * 10 + c - '0';
read();  // consume the character we got with peek
c = timedPeek();
}
while( (c >= '0' && c <= '9') || c == ignore );

if(isNegative)
value = -value;
return value;
}

// as parseInt but returns a floating point value
float Stream::parseFloat(LookaheadMode lookahead, char ignore)
{
bool isNegative = false;
bool isFraction = false;
long value = 0;
int c;
float fraction = 1.0;

c = peekNextDigit(lookahead, true);
// ignore non numeric leading characters
if(c < 0)
return 0; // zero returned if timeout

do{
if(c == ignore)
	c = c; // ignore
else if(c == '-')
	isNegative = true;
else if (c == '.')
	isFraction = true;
else if(c >= '0' && c <= '9')  {	// is c a digit?
	value = value * 10 + c - '0';
	if(isFraction)
		fraction *= 0.1;
}
read();  // consume the character we got with peek
c = timedPeek();
}
while( (c >= '0' && c <= '9')  || (c == '.' && !isFraction) || c == ignore );

if(isNegative)
value = -value;
if(isFraction)
return value * fraction;
else
return value;
}

// read characters from stream into buffer
// terminates if length characters have been read, or timeout (see setTimeout)
// returns the number of characters placed in the buffer
// the buffer is NOT null terminated.
//
size_t Stream::readBytes(char *buffer, size_t length)
{
size_t count = 0;
while (count < length) {
int c = timedRead();
if (c < 0) break;
	*buffer++ = (char)c;
count++;
}
return count;
}


// as readBytes with terminator character
// terminates if length characters have been read, timeout, or if the terminator character  detected
// returns the number of characters placed in the buffer (0 means no valid data found)

size_t Stream::readBytesUntil(char terminator, char *buffer, size_t length)
{
if (length < 1) return 0;
size_t index = 0;
while (index < length) {
int c = timedRead();
if (c < 0 || c == terminator) break;
	*buffer++ = (char)c;
index++;
}
return index; // return number of characters, not including null terminator
}

String Stream::readString()
{
String ret;
int c = timedRead();
while (c >= 0)
{
ret += (char)c;
c = timedRead();
}
return ret;
}

String Stream::readStringUntil(char terminator)
{
String ret;
int c = timedRead();
while (c >= 0 && c != terminator)
{
ret += (char)c;
c = timedRead();
}
return ret;
}

int Stream::findMulti(struct MultiTarget *targets, int tCount) {
// any zero length target string automatically matches and would make
// a mess of the rest of the algorithm.
for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
if (t->len <= 0)
	return t - targets;
}

while (1) {
int c = timedRead();
if (c < 0)
	return -1;

for (struct MultiTarget *t = targets; t < targets+tCount; ++t) {
	// the simple case is if we match, deal with that first.
	if (c == t->str[t->index]) {
		if (++t->index == t->len)
			return t - targets;
		else
			continue;
	}
	
	// if not we need to walk back and see if we could have matched further
	// down the stream (ie '1112' doesn't match the first position in '11112'
	// but it will match the second position so we can't just reset the current
	// index to 0 when we find a mismatch.
	if (t->index == 0)
		continue;
	
	int origIndex = t->index;
	do {
		--t->index;
		// first check if current char works against the new current index
		if (c != t->str[t->index])
			continue;
		
		// if it's the only char then we're good, nothing more to check
		if (t->index == 0) {
			t->index++;
			break;
		}
		
		// otherwise we need to check the rest of the found string
		int diff = origIndex - t->index;
		size_t i;
		for (i = 0; i < t->index; ++i) {
			if (t->str[i] != t->str[i + diff])
				break;
		}
		
		// if we successfully got through the previous loop then our current
		// index is good.
		if (i == t->index) {
			t->index++;
			break;
		}
		
		// otherwise we just try the next index
	} while (t->index);
}
}
// unreachable
return -1;
}
