/*
 *
 *  C++ Portable Types Library (PTypes)
 *  Version 2.1.1  Released 27-Jun-2007
 *
 *  Copyright (C) 2001-2007 Hovik Melikyan
 *
 *  http://www.melikyan.com/ptypes/
 *
 */

#include <errno.h>
#include <string.h>
#include <limits.h>

#ifdef WIN32
#  include <windows.h>
#else
#  include <unistd.h>
#endif

#include "pstreams.h"


PTYPES_BEGIN


instm::instm(int ibufsize): iobase(ibufsize) 
{
}


instm::~instm() 
{
}


int instm::classid()
{
    return CLASS_INSTM;
}


int instm::dorawread(char* buf, int count)
{
    if (handle == invhandle)
	return -1;
#ifdef WIN32
    unsigned long ret;
    if (!ReadFile(HANDLE(handle), buf, count, &ret, nil)) 
#else
    int ret;
    if ((ret = ::read(handle, buf, count)) < 0)
#endif
    {
        int e = uerrno();
        if (e == EPIPE)
            ret = 0;
        else
            error(e, "Couldn't read");
    }
    return ret;
}


int instm::rawread(char* buf, int count) 
{
    requireactive();
    try 
    {
        int ret = dorawread(buf, count);
        if (ret <= 0) {
            ret = 0;
            eof = true;
            chstat(IO_EOF);
        }
        else 
        {
            abspos += ret;
            chstat(IO_READING);
        }
        return ret;
    }
    catch (estream*) 
    {
        eof = true;
        chstat(IO_EOF);
        throw;
    }
}


large instm::tellx()
{
    return abspos - bufend + bufpos;
}


void instm::bufvalidate() 
{
    requirebuf();
    bufclear();
    bufend = rawread(bufdata, bufsize);
}


large instm::seekx(large newpos, ioseekmode mode) 
{
    if (bufdata != 0 && mode != IO_END) 
    {
        if (mode == IO_CURRENT)
        {
            newpos += tellx();
            mode = IO_BEGIN;
        }

        // see if it is possible to seek within the buffer
        large newbufpos = newpos - (abspos - bufend);
        if (newbufpos >= 0 && newbufpos <= bufend) 
        {
            bufpos = (int)newbufpos;
            eof = false;
            return tellx();
        }
    }

    // if IO_END or if not possible to seek within the buffer
    return iobase::seekx(newpos, mode);
}


bool instm::get_eof() 
{
    if (!eof && bufdata != 0 && bufpos >= bufend)
        bufvalidate();
    return eof;
}


int instm::get_dataavail()
{
    get_eof();
    return bufend - bufpos;
}


char instm::preview() 
{
    if (!eof && bufpos >= bufend)
        bufvalidate();
    if (eof)
        return eofchar;
    return bufdata[bufpos];
}


void instm::putback()
{
    requireactive();
    if (bufpos == 0)
        fatal(CRIT_FIRST + 14, "putback() failed");
    bufpos--;
    eof = false;
}


bool instm::get_eol() 
{
    char c = preview();
    return (eof || c == 10 || c == 13);
}


void instm::skipeol() 
{
    switch (preview()) 
    {
    case 10: 
        get(); 
        break;
    case 13:
        get();
        if (preview() == 10)
            get();
        break;
    }
}


char instm::get() 
{
    char ret = preview();
    if (!eof)
        bufpos++;
    return ret;
}


string instm::token(const cset& chars, int limit) 
{
    requirebuf();
    string ret;
    while (!get_eof()) 
    {
        char* b = bufdata + bufpos;
        char* e = bufdata + bufend;
        char* p = b;
        while (p < e && (*p & chars))
            p++;
        int n = p - b;
        limit -= n;
        if (limit < 0)
        {
            bufpos += n + limit;
            error(ERANGE, "Token too long");
        }
        concat(ret, b, n);
        bufpos += n;
        if (p < e)
            break;
    }
    return ret;
}


string instm::token(const cset& chars) 
{
    return token(chars, INT_MAX);
}


static cset linechars = cset("*") - cset("~0a~0d");


string instm::line(int limit) 
{
    string ret = token(linechars, limit);
    skipeol();
    return ret;
}


string instm::line()
{
    string ret = token(linechars, INT_MAX);
    skipeol();
    return ret;
}


int instm::token(const cset& chars, char* buf, int count) 
{
    requirebuf();
    int ret = 0;
    while (count > 0 && !get_eof()) 
    {
        char* b = bufdata + bufpos;
        char* e = b + imin(count, bufend - bufpos);
        char* p = b;
        while (p < e && (*p & chars))
            p++;
        int n = p - b;
        memcpy(buf, b, n);
        buf += n;
        ret += n;
        count -= n;
        bufpos += n;
        if (p < e)
            break;
    }
    return ret;
}


int instm::line(char* buf, int size, bool eateol) 
{
    int ret = token(linechars, buf, size);
    if (eateol)
        skipeol();
    return ret;
}


int instm::read(void* buf, int count) 
{
    int ret = 0;
    if (bufdata == 0) 
        ret = rawread(pchar(buf), count);
    else 
    {
        while (count > 0 && !get_eof()) 
        {
            int n = imin(count, bufend - bufpos);
            memcpy(buf, bufdata + bufpos, n);
            buf = pchar(buf) + n;
            ret += n;
            count -= n;
            bufpos += n;
        }
    }
    return ret;
}


int instm::skip(int count) 
{
    int ret = 0;
    requirebuf();
    while (count > 0 && !get_eof()) 
    {
        int n = imin(count, bufend - bufpos);
        ret += n;
        count -= n;
        bufpos += n;
    }
    return ret;
}


int instm::skiptoken(const cset& chars) 
{
    int ret = 0;
    requirebuf();
    while (!get_eof())
    {
        char* b = bufdata + bufpos;
        char* e = bufdata + bufend;
        char* p = b;
        while (p < e && (*p & chars))
            p++;
        int n = p - b;
        bufpos += n;
        ret += n;
        if (p < e)
            break;
    }
    return ret;
}


void instm::skipline(bool eateol) 
{
    if (!get_eol())
        skiptoken(linechars);
    if (eateol)
        skipeol();
}


PTYPES_END
