/* Arduino SdFat Library
 * Copyright (C) 2012 by William Greiman
 *
 * This file is part of the Arduino SdFat Library
 *
 * This Library is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with the Arduino SdFat Library.  If not, see
 * <http://www.gnu.org/licenses/>.
 */
#include <float.h>
#include <istream.h>
//------------------------------------------------------------------------------
/**
 * Extract a character if one is available.
 *
 * \return The character or -1 if a failure occurs.  A failure is indicated
 * by the stream state.
 */
int istream::get() {
  int c;
  m_gcount = 0;
  c = getch();
  if (c < 0) {
    setstate(failbit);
  } else {
    m_gcount = 1;
  }
  return c;
}
//------------------------------------------------------------------------------
/**
 * Extract a character if one is available.
 *
 * \param[out] c location to receive the extracted character.
 *
 * \return always returns *this. A failure is indicated by the stream state.
 */
istream& istream::get(char& c) {
  int tmp = get();
  if (tmp >= 0) c = tmp;
  return *this;
}
//------------------------------------------------------------------------------
/**
 * Extract characters. 
 *
 * \param[out] str Location to receive extracted characters.
 * \param[in] n Size of str.
 * \param[in] delim Delimiter
 *
 * Characters are extracted until extraction fails, n is less than 1,
 * n-1 characters are extracted, or the next character equals
 * \a delim (delim is not extracted). If no characters are extracted
 * failbit is set.  If end-of-file occurs the eofbit is set.
 *
 * \return always returns *this. A failure is indicated by the stream state.
 */
istream& istream::get(char *str, streamsize n, char delim) {
  int c;
  FatPos_t pos;
  m_gcount = 0;
  while ((m_gcount + 1)  < n) {
    c = getch(&pos);
    if (c < 0) {
      break;
    }
    if (c == delim) {
      setpos(&pos);
      break;
    }
    str[m_gcount++] = c;
  }
  if (n > 0) str[m_gcount] = '\0';
  if (m_gcount == 0) setstate(failbit);
  return *this;
}
//------------------------------------------------------------------------------
void istream::getBool(bool *b) {
  if ((flags() & boolalpha) == 0) {
    getNumber(b);
    return;
  }
  PGM_P truePtr = PSTR("true");
  PGM_P falsePtr = PSTR("false");
  const uint8_t true_len = 4;
  const uint8_t false_len = 5;
  bool trueOk = true;
  bool falseOk = true;
  uint8_t i = 0;
  int c = readSkip();
  while (1) {
    falseOk = falseOk && c == pgm_read_byte(falsePtr + i);
    trueOk = trueOk && c == pgm_read_byte(truePtr + i);
    if (trueOk == false && falseOk == false) break;
    i++;
    if (trueOk && i == true_len) {
      *b = true;
      return;
    }
    if (falseOk && i == false_len) {
      *b = false;
      return;
    }
    c = getch();
  }
  setstate(failbit);
}
//------------------------------------------------------------------------------
void istream::getChar(char* ch) {
  int16_t c = readSkip();
  if (c < 0) {
    setstate(failbit);
  } else {
    *ch = c;
  }
}
//------------------------------------------------------------------------------
//
// http://www.exploringbinary.com/category/numbers-in-computers/
//
int16_t const EXP_LIMIT = 100;
static const uint32_t uint32_max = (uint32_t)-1;
bool istream::getDouble(double* value) {
  bool got_digit = false;
  bool got_dot = false;
  bool neg;
  int16_t c;
  bool expNeg = false;
  int16_t exp = 0;
  int16_t fracExp = 0;
  uint32_t frac = 0;
  FatPos_t endPos;
  double pow10;
  double v;

  getpos(&endPos);
  c = readSkip();
  neg = c == '-';
  if (c == '-' || c == '+') {
    c = getch();
  }
  while (1) {
    if (isdigit(c)) {
      got_digit = true;
      if (frac < uint32_max/10) {
        frac = frac * 10 + (c  - '0');
        if (got_dot) fracExp--;
      } else {
        if (!got_dot) fracExp++;
      }
    } else if (!got_dot && c == '.') {
      got_dot = true;
    } else {
      break;
    }
    if (fracExp < -EXP_LIMIT || fracExp > EXP_LIMIT) goto fail;
    c = getch(&endPos);
  }
  if (!got_digit) goto fail;
  if (c == 'e' || c == 'E') {
    c = getch();
    expNeg = c == '-';
    if (c == '-' || c == '+') {
      c = getch();
    }
    while (isdigit(c)) {
      if (exp > EXP_LIMIT) goto fail;
      exp = exp * 10 + (c - '0');
      c = getch(&endPos);
    }
  }
  v = static_cast<double>(frac);
  exp = expNeg ? fracExp - exp : fracExp + exp;
  expNeg = exp < 0;
  if (expNeg) exp = -exp;
  pow10 = 10.0;
  while (exp) {
    if (exp & 1) {
      if (expNeg) {
        // check for underflow
        if (v < FLT_MIN * pow10  && frac != 0) goto fail;
        v /= pow10;
      } else {
        // check for overflow
        if (v > FLT_MAX / pow10) goto fail;
        v *= pow10;
      }
    }
    pow10 *= pow10;
    exp >>= 1;
  }
  setpos(&endPos);
  *value = neg ? -v : v;
  return true;

 fail:
  // error restore position to last good place
  setpos(&endPos);
  setstate(failbit);
  return false;
}
//------------------------------------------------------------------------------
/**
 * Extract characters
 *
 * \param[out] str Location to receive extracted characters.
 * \param[in] n Size of str.
 * \param[in] delim Delimiter
 *
 * Characters are extracted until extraction fails,
 * the next character equals \a delim (delim is extracted), or n-1
 * characters are extracted.
 *
 * The failbit is set if no characters are extracted or n-1 characters
 * are extracted.  If end-of-file occurs the eofbit is set.
 *
 * \return always returns *this. A failure is indicated by the stream state.
 */
istream& istream::getline(char *str, streamsize n, char delim) {
  FatPos_t pos;
  int c;
  m_gcount = 0;
  if (n > 0) str[0] = '\0';
  while (1) {
    c = getch(&pos);
    if (c < 0) {
      break;
    }
    if (c == delim) {
      m_gcount++;
      break;
    }
    if ((m_gcount + 1)  >=  n) {
      setpos(&pos);
      setstate(failbit);
      break;
    }
    str[m_gcount++] = c;
    str[m_gcount] = '\0';
  }
  if (m_gcount == 0) setstate(failbit);
  return *this;
}
//------------------------------------------------------------------------------
bool istream::getNumber(uint32_t posMax, uint32_t negMax, uint32_t* num) {
  int16_t c;
  int8_t any = 0;
  int8_t have_zero = 0;
  uint8_t neg;
  uint32_t val = 0;
  uint32_t cutoff;
  uint8_t cutlim;
  FatPos_t endPos;
  uint8_t f = flags() & basefield;
  uint8_t base = f == oct ? 8 : f != hex ? 10 : 16;
  getpos(&endPos);
  c = readSkip();

  neg = c == '-' ? 1 : 0;
  if (c == '-' || c == '+') {
    c = getch();
  }

  if (base == 16 && c == '0') {  // TESTSUITE
    c = getch(&endPos);
    if (c == 'X' || c == 'x') {
      c = getch();
      // remember zero in case no hex digits follow x/X
      have_zero = 1;
    } else {
      any = 1;
    }
  }
  // set values for overflow test
  cutoff = neg ? negMax : posMax;
  cutlim = cutoff % base;
  cutoff /= base;

  while (1) {
    if (isdigit(c)) {
      c -= '0';
    } else if (isalpha(c)) {
      c -= isupper(c) ? 'A' - 10 : 'a' - 10;
    } else {
      break;
    }
    if (c >= base) {
      break;
    }
    if (val > cutoff || (val == cutoff && c > cutlim)) {
      // indicate overflow error
      any = -1;
      break;
    }
    val = val * base + c;
    c = getch(&endPos);
    any = 1;
  }
  setpos(&endPos);
  if (any > 0 || (have_zero && any >= 0)) {
    *num =  neg ? -val : val;
    return true;
  }
  setstate(failbit);
  return false;
}
//------------------------------------------------------------------------------
/**
 *
 */
void istream::getStr(char *str) {
  FatPos_t pos;
  uint16_t i = 0;
  uint16_t m = width() ? width() - 1 : 0XFFFE;
  if (m != 0) {
    getpos(&pos);
    int c = readSkip();

    while (i < m) {
      if (c < 0) {
        break;
      }
      if (isspace(c)) {
        setpos(&pos);
        break;
      }
      str[i++] = c;
      c = getch(&pos);
    }
  }
  str[i] = '\0';
  if (i == 0) setstate(failbit);
  width(0);
}
//------------------------------------------------------------------------------
/**
 * Extract characters and discard them.
 *
 * \param[in] n maximum number of characters to ignore.
 * \param[in] delim Delimiter.
 *
 * Characters are extracted until extraction fails, \a n characters
 * are extracted, or the next input character equals \a delim
 * (the delimiter is extracted).  If end-of-file occurs the eofbit is set.
 *
 * Failures are indicated by the state of the stream.
 *
 * \return *this
 *
 */
istream& istream::ignore(streamsize n, int delim) {
  int c;
  m_gcount = 0;
  while (m_gcount < n) {
    c = getch();
    if (c < 0) {
      break;
    }
    m_gcount++;
    if (c == delim) break;
  }
  return *this;
}
//------------------------------------------------------------------------------
/**
 * Return the next available character without consuming it.
 *
 * \return The character if the stream state is good else -1;
 *
 */
int istream::peek() {
  int16_t c;
  FatPos_t pos;
  m_gcount = 0;
  getpos(&pos);
  c = getch();
  if (c < 0) {
    if (!bad()) setstate(eofbit);
  } else {
    setpos(&pos);
  }
  return c;
}
//------------------------------------------------------------------------------
int16_t istream::readSkip() {
  int16_t c;
  do {
    c = getch();
  } while (isspace(c) && (flags() & skipws));
  return c;
}
//------------------------------------------------------------------------------
/** used to implement ws() */
void istream::skipWhite() {
  int c;
  FatPos_t pos;
  do {
    c = getch(&pos);
  } while (isspace(c));
  setpos(&pos);
}
