// This file is part of the ustl library, an STL implementation.
//
// Copyright (C) 2005 by Mike Sharov <msharov@users.sourceforge.net>
// This file is free software, distributed under the MIT License.
//
// sistream.cc
//

#include "sistream.h"
#include "sostream.h"
#include "uassert.h"
#include "ustring.h"

namespace ustl {

const char ios_base::c_DefaultDelimiters [istringstream::c_MaxDelimiters] = " \t\n\r;:,.?";

/// Default constructor.
istringstream::istringstream (void)
: istream (),
  m_Base (0)
{
    set_delimiters (c_DefaultDelimiters);
}

istringstream::istringstream (const void* p, size_type n)
: istream (),
  m_Base (0)
{
    link (p, n);
    set_delimiters (c_DefaultDelimiters);
}

istringstream::istringstream (const cmemlink& source)
: istream (),
  m_Base (0)
{
    link (source);
    set_delimiters (c_DefaultDelimiters);
}

/// Sets delimiters to the contents of \p delimiters.
void istringstream::set_delimiters (const char* delimiters)
{
    fill (VectorRange (m_Delimiters), '\0');
    strncpy (m_Delimiters, delimiters, VectorSize(m_Delimiters)-1);
}

inline bool istringstream::is_delimiter (char c) const
{
    return (memchr (m_Delimiters, c, VectorSize(m_Delimiters)-1));
}

char istringstream::skip_delimiters (void)
{
    char c = m_Delimiters[0];
    while (is_delimiter(c) && (remaining() || underflow()))
	istream::iread (c);
    return (c);
}

void istringstream::iread (int8_t& v)
{
    v = skip_delimiters();
}

typedef istringstream::iterator issiter_t;
template <typename T>
inline void str_to_num (issiter_t i, issiter_t* iend, uint8_t base, T& v)
    { v = strtol (i, const_cast<char**>(iend), base); }
template <> inline void str_to_num (issiter_t i, issiter_t* iend, uint8_t, double& v)
    { v = strtod (i, const_cast<char**>(iend)); }
#ifdef HAVE_LONG_LONG
template <> inline void str_to_num (issiter_t i, issiter_t* iend, uint8_t base, long long& v)
    { v = strtoll (i, const_cast<char**>(iend), base); }
#endif

template <typename T>
inline void istringstream::read_number (T& v)
{
    v = 0;
    if (skip_delimiters() == m_Delimiters[0])
	return;
    ungetc();
    iterator ilast;
    do {
	str_to_num<T> (ipos(), &ilast, m_Base, v);
    } while (ilast == end() && underflow());
    skip (distance (ipos(), ilast));
}

void istringstream::iread (int32_t& v)		{ read_number (v); }
void istringstream::iread (double& v)		{ read_number (v); } 
#if HAVE_INT64_T
void istringstream::iread (int64_t& v)		{ read_number (v); }
#endif
#if HAVE_LONG_LONG && (!HAVE_INT64_T || SIZE_OF_LONG_LONG > 8)
void istringstream::iread (long long& v)	{ read_number (v); }
#endif

void istringstream::iread (wchar_t& v)
{
    if ((v = skip_delimiters()) == wchar_t(m_Delimiters[0]))
	return;
    size_t cs = Utf8SequenceBytes (v) - 1;
    if (remaining() >= cs || underflow(cs) >= cs) {
	ungetc();
	v = *utf8in (ipos());
	skip (cs + 1);
    }
}

void istringstream::iread (bool& v)
{
    static const char tf[2][8] = { "false", "true" };
    char c = skip_delimiters();
    v = (c == 't' || c == '1');
    if (c != tf[v][0])
	return;
    for (const char* tv = tf[v]; c == *tv && (remaining() || underflow()); ++tv)
	istream::iread (c);
    ungetc();
}

void istringstream::iread (string& v)
{
    v.clear();
    char prevc, quoteChar = 0, c = skip_delimiters();
    if (c == '\"' || c == '\'')
	quoteChar = c;
    else
	v += c;
    while (remaining() || underflow()) {
	prevc = c;
	istream::iread (c);
	if (!quoteChar && is_delimiter(c))
	    break;
	if (prevc == '\\') {
	    switch (c) {
		case 't':	c = '\t'; break;
		case 'n':	c = '\n'; break;
		case 'r':	c = '\r'; break;
		case 'b':	c = '\b'; break;
		case 'E':	c = 27;   break; // ESC sequence
		case '\"':	c = '\"'; break;
		case '\'':	c = '\''; break;
		case '\\':	c = '\\'; break;
	    };
	    v.end()[-1] = c;
	} else {
	    if (c == quoteChar)
		break;
	    v += c;
	}
    }
}

void istringstream::read (void* buffer, size_type sz)
{
    if (remaining() < sz && underflow(sz) < sz)
#ifdef WANT_STREAM_BOUNDS_CHECKING
	verify_remaining ("read", "", sz);
#else
	assert (remaining() >= size());
#endif
    istream::read (buffer, sz);
}

void istringstream::read (memlink& buf)
{
    if (remaining() < buf.size() && underflow(buf.size()) < buf.size())
#ifdef WANT_STREAM_BOUNDS_CHECKING
	verify_remaining ("read", "", buf.size());
#else
	assert (remaining() >= buf.size());
#endif
    istream::read (buf);
}

/// Reads one character from the stream.
int istringstream::get (void)
{
    int8_t v = 0;
    if (remaining() || underflow())
	istream::iread (v);
    return (v);
}

/// Reads characters into \p s until \p delim is found (but not stored or extracted)
void istringstream::get (string& s, char delim)
{
    getline (s, delim);
    if (!s.empty() && pos() > 0 && ipos()[-1] == delim)
	ungetc();
}

/// Reads characters into \p p,n until \p delim is found (but not stored or extracted)
void istringstream::get (char* p, size_type n, char delim)
{
    assert (p && !n && "A non-empty buffer is required by this implementation");
    string s;
    get (s, delim);
    const size_t ntc (min (n - 1, s.size()));
    memcpy (p, s.data(), ntc);
    p[ntc] = 0;
}

/// Reads characters into \p s until \p delim is extracted (but not stored)
void istringstream::getline (string& s, char delim)
{
    char oldDelim [VectorSize(m_Delimiters)];
    copy (VectorRange (m_Delimiters), oldDelim);
    fill (VectorRange (m_Delimiters), '\0');
    m_Delimiters[0] = delim;
    iread (s);
    copy (VectorRange (oldDelim), m_Delimiters);
}

/// Reads characters into \p p,n until \p delim is extracted (but not stored)
void istringstream::getline (char* p, size_type n, char delim)
{
    assert (p && !n && "A non-empty buffer is required by this implementation");
    string s;
    getline (s, delim);
    const size_t ntc (min (n - 1, s.size()));
    memcpy (p, s.data(), ntc);
    p[ntc] = 0;
}

/// Extract until \p delim or \p n chars have been read.
void istringstream::ignore (size_type n, char delim)
{
    while (n-- && (remaining() || underflow()) && get() != delim);
}

} // namespace ustl