/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program 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 program 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 this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/
#include <ctype.h>
#include "hsStream.h"
#include "hsMemory.h"
#include "hsUtils.h"

#include "hsTemplates.h"
#include "hsStlUtils.h"

#if HS_BUILD_FOR_UNIX
#include <unistd.h>
#endif

#if HS_BUILD_FOR_MAC
    #include <Files.h>
    #include <stdio.h>
    #include <unistd.h>
#endif

#if HS_BUILD_FOR_PS2
#include <eekernel.h>
#include <sifdev.h>
#endif

#include "hsWindows.h"
#if HS_BUILD_FOR_WIN32
#include <io.h>
#endif
#include "hsStlUtils.h"

//////////////////////////////////////////////////////////////////////////////////

#if HS_CPU_BENDIAN
    static void swapIt(Int32 *swap)
    {
        Byte*   c = (Byte*)swap;
        Byte        t = c[0];

        c[0] = c[3];
        c[3] = t;
        t = c[1];
        c[1] = c[2];
        c[2] = t;
    }
    static void swapIt(int *swap)
    {
        swapIt((Int32*)swap);
    }
    static void swapIt(float *swap)
    {
        swapIt((Int32*)swap);
    }

    static void swapIt(double *swap)
    {
        float* a = (float*)&swap;
        float* b = (float*)(((Byte*)&swap)+4);
        swapIt(a);
        swapIt(b);
    }

    static void swapIt(Int16 *swap)
    {
        Byte *c = (Byte*)swap;
        Byte t;
        t = c[0];
        c[0] = c[1];
        c[1] = t;
    }
    #define unswapIt(value)
#else
    #define swapIt(value)
    static void unswapIt(Int32 *swap)
    {
        Byte*   c = (Byte*)swap;
        Byte        t = c[0];

        c[0] = c[3];
        c[3] = t;
        t = c[1];
        c[1] = c[2];
        c[2] = t;
    }
    static void unswapIt(int *swap)
    {
        unswapIt((Int32*)swap);
    }
    static void unswapIt(float *swap)
    {
        unswapIt((Int32*)swap);
    }

    static void unswapIt(double *swap)
    {
        float* a = (float*)&swap;
        float* b = (float*)(((Byte*)&swap)+4);
        swapIt(a);
        swapIt(b);
    }

    static void unswapIt(Int16 *swap)
    {
        Byte *c = (Byte*)swap;
        Byte t;
        t = c[0];
        c[0] = c[1];
        c[1] = t;
    }
#endif

//////////////////////////////////////////////////////////////////////////////////

void hsStream::FastFwd()
{
    hsThrow("FastFwd unimplemented by subclass of stream");
}

UInt32 hsStream::GetPosition() const
{
    return fPosition;
}

void hsStream::SetPosition(UInt32 position)
{
    if (position == fPosition)
        return;
    Rewind();
    Skip(position);
}

void hsStream::Truncate()
{
    hsThrow("Truncate unimplemented by subclass of stream");
}

UInt32 hsStream::GetSizeLeft()
{
    UInt32 ret = 0;
    if (GetPosition() > GetEOF())
    {
        hsThrow("Position is beyond EOF");
    }
    else
    {
        ret = GetEOF() - GetPosition();
    }

    return ret;
}

//////////////////////////////////////////////////////////////////////////////////

UInt32 hsStream::GetEOF()
{
    hsThrow( "GetEOF() unimplemented by subclass of stream");
    return 0;
}

void hsStream::CopyToMem(void* mem)
{
    hsThrow( "CopyToMem unimplemented by subclass of stream");
}

//////////////////////////////////////////////////////////////////////////////////

hsStream::~hsStream()
{
}

UInt32 hsStream::WriteString(const char cstring[])
{
    return Write(hsStrlen(cstring), cstring);
}

UInt32 hsStream::WriteFmt(const char * fmt, ...)
{
    va_list av;
    va_start( av, fmt );
    UInt32 n = WriteFmtV( fmt, av );
    va_end( av );
    return n;
}

UInt32 hsStream::WriteFmtV(const char * fmt, va_list av)
{
    std::string buf;
    xtl::formatv( buf, fmt, av );
    return Write( buf.length(), buf.data() );
}

UInt32 hsStream::WriteSafeStringLong(const char *string)
{
    UInt32 len = hsStrlen(string);      
    WriteSwap32(len);
    if (len > 0)
    {   
        char *buff = TRACKED_NEW char[len+1];
        int i;
        for (i = 0; i < len; i++)
        {
            buff[i] = ~string[i];
        }
        buff[len] = '\0';
        UInt32 result = Write(len, buff);
        delete [] buff;
        return result;
    }
    else
        return 0;
}

UInt32 hsStream::WriteSafeWStringLong(const wchar_t *string)
{
    UInt32 len = wcslen(string);
    WriteSwap32(len);
    if (len > 0)
    {
        int i;
        for (i=0; i<len; i++)
        {
            wchar_t buff = ~string[i];
            WriteSwap16((UInt16)buff);
        }
        WriteSwap16((UInt16)L'\0');
    }
    return 0;
}

char *hsStream::ReadSafeStringLong()
{
    char *name = nil;
    UInt32 numChars = ReadSwap32();
    if (numChars > 0 && numChars <= GetSizeLeft())
    {
        name = TRACKED_NEW char[numChars+1];
        Read(numChars, name);
        name[numChars] = '\0';

        // if the high bit is set, flip the bits. Otherwise it's a normal string, do nothing.
        if (name[0] & 0x80) 
        {
            int i;
            for (i = 0; i < numChars; i++)
                name[i] = ~name[i];
        }       
    }

    return name;
}

wchar_t *hsStream::ReadSafeWStringLong()
{
    wchar_t *retVal = nil;
    UInt32 numChars = ReadSwap32();
    if (numChars > 0 && numChars <= (GetSizeLeft()/2)) // divide by two because each char is two bytes
    {
        retVal = TRACKED_NEW wchar_t[numChars+1];
        int i;
        for (i=0; i<numChars; i++)
            retVal[i] = (wchar_t)ReadSwap16();
        retVal[numChars] = (wchar_t)ReadSwap16(); // we wrote the null out, read it back in

        if (retVal[0]* 0x80)
        {
            int i;
            for (i=0; i<numChars; i++)
                retVal[i] = ~retVal[i];
        }
    }

    return retVal;
}

UInt32 hsStream::WriteSafeString(const char *string)
{
    int len = hsStrlen(string);
    hsAssert(len<0xf000, xtl::format("string len of %d is too long for WriteSafeString %s, use WriteSafeStringLong", 
        string, len).c_str() );

    WriteSwap16(len | 0xf000);
    if (len > 0)
    {
        char *buff = TRACKED_NEW char[len+1];
        int i;
        for (i = 0; i < len; i++)
        {
            buff[i] = ~string[i];
        }
        buff[len] = '\0';
        UInt32 result = Write(len, buff);
        delete [] buff;
        return result;
    }
    else
        return 0;
}

UInt32 hsStream::WriteSafeWString(const wchar_t *string)
{
    int len = wcslen(string);
    hsAssert(len<0xf000, xtl::format("string len of %d is too long for WriteSafeWString, use WriteSafeWStringLong",
        len).c_str() );

    WriteSwap16(len | 0xf000);
    if (len > 0)
    {
        int i;
        for (i=0; i<len; i++)
        {
            wchar_t buff = ~string[i];
            WriteSwap16((UInt16)buff);
        }
        WriteSwap16((UInt16)L'\0');
    }
    return 0;
}

char *hsStream::ReadSafeString()
{
    char *name = nil;
    UInt16 numChars = ReadSwap16();

#ifndef REMOVE_ME_SOON
    // Backward compat hack - remove in a week or so (from 6/30/03)
    hsBool oldFormat = !(numChars & 0xf000);
    if (oldFormat)
        ReadSwap16();
#endif

    numChars &= ~0xf000;
    hsAssert(numChars <= GetSizeLeft(), "Bad string");
    if (numChars > 0 && numChars <= GetSizeLeft())
    {
        name = TRACKED_NEW char[numChars+1];
        Read(numChars, name);
        name[numChars] = '\0';  

        // if the high bit is set, flip the bits. Otherwise it's a normal string, do nothing.
        if (name[0] & 0x80) 
        {
            int i;
            for (i = 0; i < numChars; i++)
                name[i] = ~name[i];
        }
    }

    return name;
}

wchar_t *hsStream::ReadSafeWString()
{
    wchar_t *retVal = nil;
    UInt32 numChars = ReadSwap16();
    
    numChars &= ~0xf000;
    hsAssert(numChars <= GetSizeLeft()/2, "Bad string");
    if (numChars > 0 && numChars <= (GetSizeLeft()/2)) // divide by two because each char is two bytes
    {
        retVal = TRACKED_NEW wchar_t[numChars+1];
        int i;
        for (i=0; i<numChars; i++)
            retVal[i] = (wchar_t)ReadSwap16();
        retVal[numChars] = (wchar_t)ReadSwap16(); // we wrote the null out, read it back in

        if (retVal[0]* 0x80)
        {
            int i;
            for (i=0; i<numChars; i++)
                retVal[i] = ~retVal[i];
        }
    }

    return retVal;
}

hsBool  hsStream::Read4Bytes(void *pv)  // Virtual, faster version in sub classes
{
    int knt = this->Read(sizeof(UInt32), pv);
    if (knt != 4)
        return false;
    return true;
}

hsBool  hsStream::Read12Bytes(void *buffer) // Reads 12 bytes, return true if success
{
    int knt = this->Read(12,buffer);
    if (knt != 12)
        return false;
    return true;
}

hsBool  hsStream::Read8Bytes(void *buffer)  // Reads 12 bytes, return true if success
{
    int knt = this->Read(8,buffer);
    if (knt !=8)
        return false;
    return true;
}

hsBool hsStream::ReadBool() // Virtual, faster version in sub classes
{
    return this->ReadByte();
}

bool hsStream::Readbool() // Virtual, faster version in sub classes
{
    return this->ReadByte() ? true : false;
}

void hsStream::ReadBool(int count, hsBool values[])
{
    this->Read(count, values);

    if (sizeof(hsBool) > 1)
    {   const UInt8* src = (UInt8*)values;

        //  go backwards so we don't overwrite ourselves
        for (int i = count - 1; i >= 0; --i)
            values[i] = src[i];
    }
}

UInt8 hsStream::ReadByte()
{
    UInt8   value;

    this->Read(sizeof(UInt8), &value);
    return value;
}

hsBool hsStream::AtEnd()
{
    hsAssert(0,"No hsStream::AtEnd() implemented for this stream class");
    return false;
}

hsBool hsStream::IsTokenSeparator(char c)
{
    return (isspace(c) || c==',' || c=='=');
}

hsBool hsStream::GetToken(char *s, UInt32 maxLen, const char beginComment, const char endComment)
{
    char c;
    char endCom;
        endCom = endComment;

    while( true )
    {
        while( !AtEnd() && IsTokenSeparator(c = ReadByte()) )
            c = c;
            ;
        if( AtEnd() )
            return false;

        if( beginComment != c )
            break;

        // skip to end of comment
        while( !AtEnd() && (endCom != (c = ReadByte())) )
            c= c;
            ;
    }

    s[0] = c;
    UInt32 k = 1;
    while( !AtEnd() && !IsTokenSeparator(c = ReadByte()) )
    {
        if( k < maxLen )
            s[k++] = c;
    }
    s[k] = 0;


    if( (k > 0)&&!_stricmp(s, "skip") )
    {
        int depth = 1;
        while( depth && GetToken(s, maxLen, beginComment, endCom) )
        {
            if( !_stricmp(s, "skip") )
                depth++;
            else
            if( !_stricmp(s, "piks") )
                depth--;
        }
        return GetToken(s, maxLen, beginComment, endCom);
    }

    return true;
}

hsBool hsStream::ReadLn(char *s, UInt32 maxLen, const char beginComment, const char endComment)
{
    char c;
    char endCom;
        endCom = endComment;

    while( true )
    {
        while( !AtEnd() && strchr("\r\n",c = ReadByte()) )
            c = c;
            ;
        if( AtEnd() )
            return false;

        if( beginComment != c )
            break;

        // skip to end of comment
        while( !AtEnd() && (endCom != (c = ReadByte())) )
            c= c;
            ;
    }

    s[0] = c;
    UInt32 k = 1;
    while( !AtEnd() && !strchr("\r\n",c = ReadByte()) )
    {
        if( k < maxLen )
            s[k++] = c;
    }
    s[k] = 0;


    if( (k > 0)&&!_stricmp(s, "skip") )
    {
        int depth = 1;
        while( depth && ReadLn(s, maxLen, beginComment, endCom) )
        {
            if( !_stricmp(s, "skip") )
                depth++;
            else
            if( !_stricmp(s, "piks") )
                depth--;
        }
        return ReadLn(s, maxLen, beginComment, endCom);
    }

    return true;
}

UInt16 hsStream::ReadSwap16()
{
    UInt16  value;
    this->Read(sizeof(UInt16), &value);
    swapIt((Int16*)&value);
    return value;
}

void hsStream::ReadSwap16(int count, UInt16 values[])
{
    this->Read(count * sizeof(UInt16), values);
#if HS_CPU_BENDIAN
    for (int i = 0; i < count; i++)
        swapIt((Int16*)&values[i]);
#endif
}

UInt32 hsStream::ReadSwap32()
{
    UInt32  value;
    Read4Bytes(&value);
    swapIt((Int32*)&value);
    return value;
}

void hsStream::ReadSwap32(int count, UInt32 values[])
{
    this->Read(count * sizeof(UInt32), values);
#if HS_CPU_BENDIAN
    for (int i = 0; i < count; i++)
        swapIt((Int32*)&values[i]);
#endif
}

UInt32 hsStream::ReadUnswap32()
{
    UInt32  value;
    Read4Bytes(&value);
    unswapIt((Int32*)&value);
    return value;
}

#if HS_CAN_USE_FLOAT
    double hsStream::ReadSwapDouble()
    {
        double  ival;
        Read8Bytes(&ival);
        double *pval = (double *)&ival;     // all in the name of speed, 
        swapIt(pval);
        return *pval;
    }

    void hsStream::ReadSwapDouble(int count, double values[])
    {
        this->Read(count * sizeof(double), values);
#if HS_CPU_BENDIAN
                for (int i = 0; i < count; i++)
                        swapIt(&values[i]);
#endif
        }


    float hsStream::ReadSwapFloat()
    {
        UInt32  ival;
        Read4Bytes(&ival);
        float *pval = (float *)&ival;       // all in the name of speed, 
        swapIt(pval);
        return *pval;
    }

    void hsStream::ReadSwapFloat(int count, float values[])
    {
        this->Read(count * sizeof(float), values);
#if HS_CPU_BENDIAN
        for (int i = 0; i < count; i++)
            swapIt(&values[i]);
#endif
    }

    float hsStream::ReadUnswapFloat()
    {
        float value;
        this->Read(sizeof(float), &value);
        unswapIt(&value);
        return value;
    }
#endif


void hsStream::WriteBool(hsBool value)
{
    UInt8 dst = (value != 0);

    this->Write(sizeof(UInt8), &dst);
}

void hsStream::Writebool(bool value)
{
    UInt8 dst = (value != 0);

    this->Write(sizeof(UInt8), &dst);
}

void hsStream::WriteBool(int count, const hsBool values[])
{
    if (sizeof(hsBool) > 1)
    {   hsTempArray<UInt8> storage(count);
        UInt8*           dst = (UInt8*)values;
    
        for (int i = 0; i < count; i++)
            dst[i] = (values[i] != 0);
        this->Write(count, dst);
    }
    else
        this->Write(count, values);
}

void hsStream::WriteByte(UInt8 value)
{
    this->Write(sizeof(UInt8), &value);
}

void  hsStream::WriteSwap16(UInt16 value)
{
    swapIt((Int16*)&value);
    this->Write(sizeof(Int16), &value);
}

void  hsStream::WriteSwap16(int count, const UInt16 values[])
{
    for (int i = 0; i < count; i++)
        this->WriteSwap16(values[i]);
}

void  hsStream::WriteSwap32(UInt32 value)
{
    swapIt((Int32*)&value);
    this->Write(sizeof(Int32), &value);
}

void  hsStream::WriteSwap32(int count, const UInt32 values[])
{
    for (int i = 0; i < count; i++)
        this->WriteSwap32(values[i]);
}

void hsStream::WriteUnswap32(UInt32 value)
{
    unswapIt((Int32*)&value);
    this->Write(sizeof(Int32), &value);
}

#if HS_CAN_USE_FLOAT
    void hsStream::WriteSwapDouble(double value)
    {
        swapIt(&value);
        this->Write(sizeof(double), &value);
    }

    void hsStream::WriteSwapDouble(int count, const double values[])
    {
        for (int i = 0; i < count; i++)
            this->WriteSwapDouble(values[i]);
    }

    void hsStream::WriteSwapFloat(float value)
    {
        swapIt(&value);
        this->Write(sizeof(float), &value);
    }

    void hsStream::WriteSwapFloat(int count, const float values[])
    {
        for (int i = 0; i < count; i++)
            this->WriteSwapFloat(values[i]);
    }

    void hsStream::WriteUnswapFloat(float value)
    {
        unswapIt(&value);
        this->Write(sizeof(float), &value);
    }
#endif

void hsStream::WriteSwapAtom(UInt32 tag, UInt32 size)
{
    this->WriteSwap32(tag);
    this->WriteSwap32(size);
}

UInt32 hsStream::ReadSwapAtom(UInt32* sizePtr)
{
    UInt32  tag = this->ReadSwap32();
    UInt32  size = this->ReadSwap32();

    if (sizePtr)
        *sizePtr = size;
    return tag;
}

//////////////////////////////////////////////////////////////////////////////////

#define kFileStream_Uninitialized       ~0

hsBool hsFileStream::Open(const char *name, const char *mode)
{
#ifdef HS_BUILD_FOR_PS2
    hsAssert(fRef == kFileStream_Uninitialized, "hsFileStream:Open  Stream already opened");

    Int32 ref = hsPS2Open(name, mode);
    if (ref == -1)
        return false;

    fRef = (UInt32) ref;
    fFileSize = sceLseek(fRef, 0, SCE_SEEK_END);
    sceLseek(fRef, 0, SCE_SEEK_SET);
    fBufferIsEmpty = true;
    fWriteBufferUsed = false;
    fVirtualFilePointer = 0;
    fBufferBase = 0;

    return true;
#else
    hsAssert(0, "hsFileStream::Open  NotImplemented");
    return false;
#endif
}

hsBool hsFileStream::Open(const wchar *name, const wchar *mode)
{
    hsAssert(0, "hsFileStream::Open  NotImplemented");
    return false;
}

hsBool hsFileStream::Close ()
{
#ifdef HS_BUILD_FOR_PS2
    if (fRef != kFileStream_Uninitialized)
    {
        hsPS2Close(fRef);
        fRef = kFileStream_Uninitialized;
    }
    return true;
#else
    hsAssert(0, "hsFileStream::Close  NotImplemented");
    return false;
#endif
}

UInt32 hsFileStream::GetFileRef()
{
    return fRef;
}

void hsFileStream::SetFileRef(UInt32 ref)
{
    hsAssert(ref != kFileStream_Uninitialized, "bad ref");
    fRef = ref;
#if HS_BUILD_FOR_PS2
    fFileSize = sceLseek(fRef, 0, SCE_SEEK_END);
    sceLseek(fRef, 0, SCE_SEEK_SET);
    fBufferIsEmpty= true;
    fWriteBufferUsed= false;
    fVirtualFilePointer= 0;
    fBufferBase= 0;
#endif
}

hsFileStream::hsFileStream()
{
    fRef = kFileStream_Uninitialized;
#if HS_BUILD_FOR_PS2
    fBufferIsEmpty= true;
    fWriteBufferUsed= false;
#endif
}

hsFileStream::~hsFileStream()
{
}

UInt32 hsFileStream::Read(UInt32 bytes,  void* buffer)
{
    hsAssert(fRef != kFileStream_Uninitialized, "fRef uninitialized");

    fBytesRead += bytes;
    fPosition += bytes;

#if HS_BUILD_FOR_MAC
    Int16   err;

    err = FSRead(fRef, (long*)&bytes, buffer);
    if (err == noErr)
        return bytes;
    else
        return 0;
#elif HS_BUILD_FOR_PS2
    Int32 ret;
    Int32 nReadBytes= 0;
    while(bytes){
        if( !fBufferIsEmpty ){  // read at already chatched.
            Int32 DataBytesInBuffer= fBufferBase + kBufferSize - fVirtualFilePointer;
            Int32 ChatchedReadSize= DataBytesInBuffer < bytes ? DataBytesInBuffer : bytes;
            memcpy( buffer, &fBuffer[fVirtualFilePointer-fBufferBase], ChatchedReadSize );
            nReadBytes += ChatchedReadSize;
            buffer= (void *)(((char*)buffer) + ChatchedReadSize);
            fVirtualFilePointer += ChatchedReadSize;
            bytes -= ChatchedReadSize;
            fBufferIsEmpty= (fBufferBase + kBufferSize <= fVirtualFilePointer);
        }
        if( kBufferSize <= bytes ){ // read directry, for Large block read.
            hsAssert( fBufferIsEmpty, "read buffer was not used.");
            Int32 DirectReadSize= bytes - bytes % kBufferSize;
            ret= sceRead(fRef, buffer, DirectReadSize);
            if( ret == -1 ){
                return 0;
            }
            hsAssert( ret == DirectReadSize, "require read size != return size");
            nReadBytes += DirectReadSize;
            buffer= (void *)(((char*)buffer) + DirectReadSize);
            fVirtualFilePointer += DirectReadSize;
            bytes -= DirectReadSize;
        }
        if( 0 < bytes && fBufferIsEmpty ){  // fill buffer
            hsAssert( fVirtualFilePointer % kBufferSize == 0 , "read buffer is not alignment.");
            ret= sceRead(fRef, fBuffer, kBufferSize );
            if( ret == -1 ){
                return 0;
            }
            fBufferBase= fVirtualFilePointer;
            fBufferIsEmpty= false;
        }
    }
    return nReadBytes;

#elif HS_BUILD_FOR_WIN32
    UInt32 rBytes;
    ReadFile((HANDLE)fRef, buffer, bytes, &rBytes, nil);
    if(bytes == rBytes)
        return bytes;
    else
        return 0;
#else
    return 0;
#endif
}

UInt32 hsFileStream::Write(UInt32 bytes, const void* buffer)
{
    hsAssert(fRef != kFileStream_Uninitialized, "fRef uninitialized");

    fBytesRead += bytes;
    fPosition += bytes;

#if HS_BUILD_FOR_MAC
    Int16   err;

    err = FSWrite(fRef, (long*)&bytes, buffer);
    if (err == noErr)
        return bytes;
    else
    {   
        hsDebugMessage("hsFileStream::Write failed", err);
        return 0;
    }
#elif HS_BUILD_FOR_PS2
       Int32 ret;
    
    fWriteBufferUsed =true; // buffered write was not implement, not yet.
    
    ret = sceWrite(fRef, (void*)buffer ,bytes);
    if(ret != -1)
      return ret;
    else
      return 0;
#elif HS_BUILD_FOR_WIN32
    UInt32 wBytes;
    WriteFile((HANDLE)fRef, buffer, bytes, &wBytes, nil);
    if(bytes == wBytes)
        return bytes;
    else
    {
        char str[128];
        sprintf(str, "hsFileStream::Write failed.  err %d", GetLastError());
        hsAssert(false, str);
        return 0;
    }
#else
    return 0;
#endif
}


hsBool hsFileStream::AtEnd()
{
#if HS_BUILD_FOR_MAC
    Int32 eof;
    Int32 pos;
    ::GetEOF(fRef, &eof);
    ::GetFPos(fRef, &pos);
    return pos >= eof;
#elif HS_BUILD_FOR_PS2
    Int32 rVal = 0;
    if( fWriteBufferUsed || fVirtualFilePointer == 0 ){
        // bufferd write was not implement, yiet.
        rVal = sceLseek(fRef, 0, SCE_SEEK_CUR);
        return rVal >= fFileSize;
    }
    else{   // bufferd read
        return fVirtualFilePointer >= fFileSize;
    }
    
#elif HS_BUILD_FOR_WIN32
    UInt32 bytes;
    PeekNamedPipe((void*)fRef, nil, 0, nil, &bytes, nil);
    return bytes>0;
#else
    hsAssert(0,"No hsStream::AtEnd() implemented for this stream class");
    return false;
#endif
}

void hsFileStream::Skip(UInt32 delta)
{
    fBytesRead += delta;
    fPosition += delta;

#if HS_BUILD_FOR_MAC
    short err = SetFPos(fRef, fsFromMark, delta);
    hsAssert(err == noErr, "SetFPos failed");
#elif HS_BUILD_FOR_PS2
    const Int32 NewPointer= fVirtualFilePointer+delta;
    if( fWriteBufferUsed || fVirtualFilePointer == 0 ){
        // bufferd write was not implement, yiet.
        sceLseek(fRef, delta, SCE_SEEK_CUR);
    }
    else{   // bufferd read.
        if( !fBufferIsEmpty ){
            Int32 CurBlock= fVirtualFilePointer / kBufferSize;
            Int32 NewBlock= NewPointer / kBufferSize;
            if( CurBlock == NewBlock ){
                fVirtualFilePointer += delta;
                return;
            }
            fBufferIsEmpty= false;
        }
        Int32 NewBaseMod= NewPointer % kBufferSize;
        Int32 NewBase= NewPointer - NewBaseMod;
        if( NewBaseMod ){
            sceLseek( fRef, NewBase, SCE_SEEK_SET );
            sceRead( fRef, fBuffer, kBufferSize );
            fVirtualFilePointer= NewPointer;
            fBufferBase= NewBase;
            fBufferIsEmpty= false;
        }
        else{
            // just block border.
            fVirtualFilePointer= NewPointer;
            fBufferBase= NewBase;
        }
    }
#elif HS_BUILD_FOR_WIN32
    hsDebugMessage("hsFileStream::Skip unimplemented", 0);
#endif
}

void hsFileStream::Rewind()
{
    fBytesRead = 0;
    fPosition = 0;

#if HS_BUILD_FOR_MAC
    short err = SetFPos(fRef, fsFromStart, 0);
    hsAssert(err == noErr, "SetFPos failed");
#elif HS_BUILD_FOR_PS2
    if( fWriteBufferUsed || fVirtualFilePointer == 0 ){
        // bufferd write was not implement, yiet.
        sceLseek(fRef,0,SCE_SEEK_SET);
    }
    else{   // bufferd read.
        sceLseek(fRef, 0, SCE_SEEK_SET);
        fBufferIsEmpty= true;
        fVirtualFilePointer= 0;
        fBufferBase= 0;
    }
#elif HS_BUILD_FOR_WIN32
    hsDebugMessage("hsFileStream::Rewind unimplemented", 0);
#endif
}

void hsFileStream::Truncate()
{
    hsDebugMessage("hsFileStream::Truncate unimplemented", 0);
}

//////////////////////////////////////////////////////////////////////////////////////

#if !HS_BUILD_FOR_PS2
#if !(HS_BUILD_FOR_REFERENCE)

hsUNIXStream::~hsUNIXStream()
{
    // Don't Close here, because Sub classes Don't always want that behaviour!
}

hsBool hsUNIXStream::Open(const char *name, const char *mode)
{
    fPosition = 0;
    fRef = hsFopen(name, mode);
    return (fRef) ? true : false;
}

hsBool hsUNIXStream::Open(const wchar *name, const wchar *mode)
{
    fPosition = 0;
    fRef = _wfopen(name, mode);
    return (fRef) ? true : false;
}

hsBool hsUNIXStream::Close()
{
    int rtn = true;
    if (fRef)
        rtn = fclose(fRef);
    fRef = nil;
    delete [] fBuff;
    fBuff = nil;

    return !rtn;
}

UInt32 hsUNIXStream::Read(UInt32 bytes,  void* buffer)
{
    if (!fRef || !bytes)
        return 0;
    int numItems = ::fread(buffer, 1 /*size*/, bytes /*count*/, fRef);
    fBytesRead += numItems;
    fPosition += numItems;
    if ((unsigned)numItems < bytes) {
        if (feof(fRef)) {
            // EOF ocurred
            char str[128];
            sprintf(str, "Hit EOF on UNIX Read, only read %d out of requested %d bytes\n", numItems, bytes);
            hsDebugMessage(str, 0);
        }
        else {
            hsDebugMessage("Error on UNIX Read", ferror(fRef));
        }
    }
    return numItems;
}

hsBool  hsUNIXStream::AtEnd()
{
    if (!fRef)
        return 1;
    hsBool rVal;
    int x = getc(fRef);
    rVal = feof(fRef) != 0;
    ungetc(x, fRef);
    return rVal;
}

UInt32 hsUNIXStream::Write(UInt32 bytes, const void* buffer)
{
    if (!fRef)
        return 0;
    fPosition += bytes;
    return fwrite(buffer, bytes, 1, fRef);
}

void hsUNIXStream::SetPosition(UInt32 position)
{
    if (!fRef || (position == fPosition))
        return;
    fBytesRead = position;
    fPosition = position;
    (void)::fseek(fRef, position, SEEK_SET);
}

void hsUNIXStream::Skip(UInt32 delta)
{
    if (!fRef)
        return;
    fBytesRead += delta;
    fPosition += delta;
    (void)::fseek(fRef, delta, SEEK_CUR);
}

void hsUNIXStream::Rewind()
{
    if (!fRef)
        return;
    fBytesRead = 0;
    fPosition = 0;
    (void)::fseek(fRef, 0, SEEK_SET);
}

void hsUNIXStream::FastFwd()
{
    if (!fRef)
        return;
    (void)::fseek(fRef, 0, SEEK_END);
    fBytesRead = fPosition = ftell(fRef);
}

UInt32  hsUNIXStream::GetEOF()
{
    if( !fRef )
        return 0;

    long oldPos = ftell( fRef );
    (void)::fseek( fRef, 0, SEEK_END );
    UInt32 end = (UInt32)ftell( fRef );
    (void)::fseek( fRef, oldPos, SEEK_SET );

    return end;
}

void hsUNIXStream::Truncate()
{
    if (!fRef)
        return;
#if! __MWERKS__
    int handle = _fileno(fRef);
#if !HS_BUILD_FOR_UNIX
    _chsize(handle, fPosition);
#else
    ftruncate(handle, fPosition);
#endif
#else
#if 1
    UInt32 handle = (UInt32)fRef->handle;
    OSErr err = ::SetEOF(handle, fPosition); 
    if(err != noErr)
    {
        hsThrow("Truncate error!");
    }
#endif
#endif
}

void hsUNIXStream::Flush()
{
    if (!fRef)
        return;
    (void)::fflush(fRef);
}

#endif
#endif

//////////////////////////////////////////////////////////////////////////////////////

plReadOnlySubStream::~plReadOnlySubStream()
{
}

void    plReadOnlySubStream::Open( hsStream *base, UInt32 offset, UInt32 length )
{
    fBase = base;
    fOffset = offset;
    fLength = length;

    fBase->SetPosition( fOffset );
    IFixPosition();
}

void    plReadOnlySubStream::IFixPosition( void )
{
    fPosition = fBase->GetPosition() - fOffset;
}

hsBool  plReadOnlySubStream::AtEnd()
{
    if( fPosition >= fLength )
        return true;
    return false;
}

UInt32  plReadOnlySubStream::Read(UInt32 byteCount, void* buffer)
{
    if( byteCount > GetSizeLeft() )
    {
        hsThrow("Attempting to read past end of stream");
        byteCount = GetSizeLeft();
    }

    UInt32 read = fBase->Read( byteCount, buffer );
    IFixPosition();
    return read;
}

UInt32  plReadOnlySubStream::Write(UInt32 byteCount, const void* buffer)
{
    hsAssert( false, "Write not allowed on an plReadOnlySubStream" );
    return 0;
}

void    plReadOnlySubStream::Skip(UInt32 deltaByteCount)
{
    fBase->Skip( deltaByteCount );
    IFixPosition();
}

void    plReadOnlySubStream::Rewind()
{
    fBase->SetPosition( fOffset );
    IFixPosition();
}

void    plReadOnlySubStream::FastFwd()
{
    fBase->SetPosition( fOffset + fLength );
    IFixPosition();
}

void    plReadOnlySubStream::Truncate()
{
    hsAssert( false, "Can't truncate a read-only stream" );
}

UInt32  plReadOnlySubStream::GetEOF()
{
    return fLength;
}

//////////////////////////////////////////////////////////////////////////////////////

#define kRAMStreamChunkSize     1024

hsRAMStream::hsRAMStream() : fAppender(1, kRAMStreamChunkSize)
{
    fIter.ResetToHead(&fAppender);
}

hsRAMStream::hsRAMStream(UInt32 chunkSize) : fAppender(1, chunkSize)
{
    fIter.ResetToHead(&fAppender);
}

hsRAMStream::~hsRAMStream()
{
}

void hsRAMStream::Reset()
{
    fBytesRead = 0;
    fPosition = 0;

    fAppender.Reset();
    fIter.ResetToHead(&fAppender);
}

hsBool hsRAMStream::AtEnd()
{
    return (fBytesRead >= fAppender.Count() * fAppender.ElemSize());
}

UInt32 hsRAMStream::Read(UInt32 byteCount, void * buffer)
{
    if (fBytesRead + byteCount > fAppender.Count() * fAppender.ElemSize())
    {
        hsThrow("Attempting to read past end of stream");
        byteCount = (fAppender.Count() * fAppender.ElemSize()) - fBytesRead;
    }

    fBytesRead += byteCount;
    fPosition += byteCount;

    fIter.Next(byteCount, buffer);

    return byteCount;
}

UInt32 hsRAMStream::Write(UInt32 byteCount, const void* buffer)
{
    fPosition += byteCount;

    fAppender.PushTail(byteCount, buffer);

    return byteCount;
}

void hsRAMStream::Skip(UInt32 deltaByteCount)
{
    fPosition += deltaByteCount;
    fIter.Next(deltaByteCount, nil);
}

void hsRAMStream::Rewind()
{
    fBytesRead = 0;
    fPosition = 0;
    fIter.ResetToHead(&fAppender);
}

void hsRAMStream::Truncate()
{
    Reset();
}

UInt32 hsRAMStream::GetEOF()
{
    return fAppender.Count() * fAppender.ElemSize();
}

void hsRAMStream::CopyToMem(void* mem)
{
    (void)fAppender.CopyInto(mem);
}

//////////////////////////////////////////////////////////////////////

UInt32 hsNullStream::Read(UInt32 byteCount, void * buffer)
{
    hsThrow("hsNullStream: Can't read from this stream!");
    return 0;
}

UInt32 hsNullStream::Write(UInt32 byteCount, const void* buffer)
{
    fBytesRead += byteCount;
    fPosition += byteCount;

    return byteCount;
}

void hsNullStream::Skip(UInt32 deltaByteCount)
{
    fBytesRead += deltaByteCount;
    fPosition += deltaByteCount;
}

void hsNullStream::Rewind()
{
    fBytesRead = 0;
    fPosition = 0;
}

void hsNullStream::Truncate()
{
}

/////////////////////////////////////////////////////////////////////////////////

hsBool hsReadOnlyStream::AtEnd()
{
    return fData >= fStop;
}

UInt32 hsReadOnlyStream::Read(UInt32 byteCount, void* buffer)
{
    if (fData + byteCount > fStop)
    {
        hsThrow("Attempting to read past end of stream");
        byteCount = GetSizeLeft();
    }

    HSMemory::BlockMove(fData, buffer, byteCount);
    fData += byteCount;
    fBytesRead += byteCount;
    fPosition += byteCount;
    return byteCount;
}

UInt32 hsReadOnlyStream::Write(UInt32 byteCount, const void* buffer)
{
    hsThrow( "can't write to a readonly stream");
    return 0;
}

void hsReadOnlyStream::Skip(UInt32 deltaByteCount)
{
    fBytesRead += deltaByteCount;
    fPosition += deltaByteCount;
    fData += deltaByteCount;
    if (fData > fStop)
        hsThrow( "Skip went past end of stream");
}

void hsReadOnlyStream::Rewind()
{
    fBytesRead = 0;
    fPosition = 0;
    fData = fStart;
}

void hsReadOnlyStream::Truncate()
{
    hsThrow( "can't write to a readonly stream");
}

void hsReadOnlyStream::CopyToMem(void* mem)
{
    if (fData < fStop)
        HSMemory::BlockMove(fData, mem, fStop-fData);
}


////////////////////////////////////////////////////////////////////////////////////
UInt32 hsWriteOnlyStream::Read(UInt32 byteCount, void* buffer)
{
    hsThrow( "can't read to a writeonly stream");
    return 0;
}

UInt32 hsWriteOnlyStream::Write(UInt32 byteCount, const void* buffer)
{
    if (fData + byteCount > fStop)
        hsThrow("Write past end of stream");
    HSMemory::BlockMove(buffer, fData, byteCount);
    fData += byteCount;
    fBytesRead += byteCount;
    fPosition += byteCount;
    return byteCount;
}


///////////////////////////////////////////////////////////////////////////////////

hsQueueStream::hsQueueStream(Int32 size) :
    fSize(size),
    fReadCursor(0),
    fWriteCursor(0)
{
    fQueue = TRACKED_NEW char[fSize];
}

hsQueueStream::~hsQueueStream()
{
    delete [] fQueue;
}

UInt32 hsQueueStream::Read(UInt32 byteCount, void * buffer)
{
    hsAssert(fWriteCursor >= 0 && fWriteCursor < fSize,"hsQueueStream: WriteCursor out of range.");
    hsAssert(fReadCursor >= 0 && fReadCursor < fSize,"hsQueueStream: ReadCursor out of range.");

    Int32 limit, length, total;
    
    limit = fWriteCursor >= fReadCursor ? fWriteCursor : fSize;
    length = hsMinimum(limit-fReadCursor,byteCount);
    HSMemory::BlockMove(fQueue+fReadCursor,buffer,length);
    fReadCursor += length;
    fReadCursor %= fSize;
    total = length;
        
    if (length < byteCount && limit != fWriteCursor)
    {
        limit = fWriteCursor;
        length = hsMinimum(limit,byteCount-length);
        HSMemory::BlockMove(fQueue,static_cast<char*>(buffer)+total,length);
        fReadCursor = length;
        total += length;
    }

    return total;
}

UInt32 hsQueueStream::Write(UInt32 byteCount, const void* buffer)
{
    hsAssert(fWriteCursor >= 0 && fWriteCursor < fSize,"hsQueueStream: WriteCursor out of range.");
    hsAssert(fReadCursor >= 0 && fReadCursor < fSize,"hsQueueStream: ReadCursor out of range.");

    Int32 length;

    length = hsMinimum(fSize-fWriteCursor,byteCount);
    HSMemory::BlockMove(buffer,fQueue+fWriteCursor,length);
    if (fReadCursor > fWriteCursor)
    {
#if 0
        if (fReadCursor < fWriteCursor+length+1)
            hsStatusMessage("ReadCursor wrapped\n");
#endif
        fReadCursor = hsMaximum(fReadCursor,fWriteCursor+length+1);
        fReadCursor %= fSize;
    }
    fWriteCursor += length;
    fWriteCursor %= fSize;

    if (length < byteCount)
    {
        Write(byteCount - length,static_cast<const char*>(buffer)+length);
    }

    return byteCount;
}

void hsQueueStream::Skip(UInt32 deltaByteCount)
{
    Int32 limit, length;
    
    limit = fWriteCursor >= fReadCursor ? fWriteCursor : fSize;
    length = hsMinimum(limit-fReadCursor,deltaByteCount);
    fReadCursor += length;

    if (length < deltaByteCount && limit != fWriteCursor)
    {
        limit = fWriteCursor;
        length = hsMinimum(limit,deltaByteCount-length);
        fReadCursor = length;
    }
    else
    {
        fReadCursor %= fSize;
}
}

void hsQueueStream::Rewind()
{
    fReadCursor = fWriteCursor+1;
    fReadCursor %= fSize;
}

void hsQueueStream::FastFwd()
{
    fReadCursor = fWriteCursor;
}

hsBool hsQueueStream::AtEnd()
{
    return fReadCursor == fWriteCursor;
}

///////////////////////////////////////////////////////////////////////////////
// hsBufferedStream
///////////////////////////////////////////////////////////////////////////////

inline void FastByteCopy(void* dest, const void* src, UInt32 bytes)
{
    // Don't use memcpy if the read is 4 bytes or less, it's faster to just do a
    // direct copy
    switch (bytes)
    {
    case 4:
        *((UInt32*)dest) = *((const UInt32*)src);
        break;
    case 2:
        *((UInt16*)dest) = *((const UInt16*)src);
        break;
    case 1:
        *((UInt8*)dest) = *((const UInt8*)src);
        break;
    default:
        memcpy(dest, src, bytes);
    }
}

//#define LOG_BUFFERED

hsBufferedStream::hsBufferedStream()
: fRef(nil)
, fFileSize(0)
, fBufferLen(0)
, fWriteBufferUsed(false)
#ifdef HS_DEBUGGING
, fBufferHits(0)
, fBufferMisses(0)
, fBufferReadIn(0)
, fBufferReadOut(0)
, fReadDirect(0)
, fLastReadPos(0)
, fFilename(nil)
, fCloseReason(nil)
#endif
{
}

hsBufferedStream::~hsBufferedStream()
{
#ifdef LOG_BUFFERED
    delete [] fFilename;
#endif // LOG_BUFFERED
}

hsBool hsBufferedStream::Open(const char* name, const char* mode)
{
    hsAssert(!fRef, "hsBufferedStream:Open Stream already opened");
    fRef = hsFopen(name, mode);
    if (!fRef)
        return false;

    SetFileRef(fRef);

#ifdef LOG_BUFFERED
    fBufferHits = fBufferMisses = 0;
    fBufferReadIn = fBufferReadOut = fReadDirect = fLastReadPos = 0;
    delete [] fFilename;
    fFilename = hsStrdup(name);
    fCloseReason = nil;
#endif // LOG_BUFFERED

    return true;
}

hsBool hsBufferedStream::Open(const wchar *name, const wchar *mode)
{
    hsAssert(0, "hsFileStream::Open  NotImplemented for wchar");
    return false;
}

hsBool hsBufferedStream::Close()
{
    int rtn = true;
    if (fRef)
        rtn = fclose(fRef);
    fRef = nil;

#ifdef LOG_BUFFERED
    hsUNIXStream s;
    static bool firstClose = true;
    if (firstClose)
    {
        firstClose = false;
        s.Open("log\\BufferedStream.csv", "wt");
        s.WriteString("File,Hits,Misses,Read In,Read Out,Read Direct,% Wasted,Reason\n");
    }
    else
        s.Open("log\\BufferedStream.csv", "at");

    int wasted = 100;
    if (fBufferReadIn + fReadDirect > 0)
        wasted -= int((float(fBufferReadOut+fReadDirect) / float(fBufferReadIn+fReadDirect)) * 100.f);

    s.WriteFmt("%s,%d,%d,%u,%u,%u,%d,%s\n",
        fFilename, fBufferHits, fBufferMisses, fBufferReadIn, fBufferReadOut, fReadDirect,
        wasted,
        fCloseReason ? fCloseReason : "Unknown");

    s.Close();
#endif // LOG_BUFFERED

    return !rtn;
}

FILE* hsBufferedStream::GetFileRef()
{
    return fRef;
}

void hsBufferedStream::SetFileRef(FILE* ref)
{
    hsAssert(ref, "bad ref");
    fRef = ref;

    fseek(fRef, 0, SEEK_END);
    fFileSize = ftell(fRef);
    fseek(fRef, 0, SEEK_SET);

    fBufferLen = 0;
    fPosition = 0;
    fWriteBufferUsed = false;
}

UInt32 hsBufferedStream::Read(UInt32 bytes, void* buffer)
{
    hsAssert(fRef, "fRef uninitialized");
    if (!fRef || bytes == 0)
        return 0;

    UInt32 numReadBytes = 0;

    while (bytes > 0 && fPosition < fFileSize)
    {
        // First, see if we've got anything in the buffer
        if (fBufferLen > 0)
        {
            // Figure out how much we can copy out of the buffer
            UInt32 bufferPos = fPosition % kBufferSize;
            UInt32 bytesInBuffer = fBufferLen - bufferPos;
            UInt32 cachedReadSize = bytesInBuffer < bytes ? bytesInBuffer : bytes;

            FastByteCopy(buffer, &fBuffer[bufferPos], cachedReadSize);

            fPosition += cachedReadSize;
            numReadBytes += cachedReadSize;
            bytes -= cachedReadSize;
            buffer = (void*)(((char*)buffer) + cachedReadSize);

            // If we read all the data out of the buffer, set it to empty
            if ((bufferPos + cachedReadSize) == fBufferLen)
                fBufferLen = 0;

#ifdef HS_DEBUGGING
            fLastReadPos = fPosition;
            fBufferHits++;
            fBufferReadOut += cachedReadSize;
#endif
        }

        // Now see if the remaining read (if any) is the size of the buffer or larger.
        // If it is, read as many complete blocks as possible directly into the output buffer.
        if (bytes >= kBufferSize && fPosition % kBufferSize == 0)
        {
            UInt32 directReadSize = bytes - (bytes % kBufferSize);
            hsAssert(ftell(fRef) % kBufferSize == 0 , "read buffer is not in alignment.");
            int amtRead = ::fread(buffer, 1, directReadSize, fRef);
            fPosition += amtRead;
            numReadBytes += amtRead;
            bytes -= amtRead;
            buffer = (void*)(((char*)buffer) + amtRead);
#ifdef HS_DEBUGGING
            fLastReadPos = fPosition;
            fReadDirect += directReadSize;
#endif
        }

        // If we've got bytes left to read and we didn't pass the end of the file, buffer a new block
        if (bytes > 0 && fPosition < fFileSize)
        {
            hsAssert(ftell(fRef) % kBufferSize == 0 , "read buffer is not in alignment.");
            fBufferLen = ::fread(fBuffer, 1, kBufferSize, fRef);

#ifdef HS_DEBUGGING
            // If our last read wasn't at the start of the new buffer, it's a miss.
            if (fLastReadPos != fPosition)
            {
                fBufferMisses++;
                fBufferHits--;
            }

            fBufferReadIn += fBufferLen;
#endif
        }
    }

    return numReadBytes;
}

UInt32 hsBufferedStream::Write(UInt32 bytes, const void* buffer)
{
    hsAssert(fRef, "fRef uninitialized");
    fWriteBufferUsed = true;
    int amtWritten = fwrite((void*)buffer, 1, bytes, fRef);
    fPosition += amtWritten;
    return amtWritten;
}

hsBool hsBufferedStream::AtEnd()
{
    if (fWriteBufferUsed)
    {
        if (!fRef)
            return true;
        bool rVal;
        int x = getc(fRef);
        rVal = feof(fRef) != 0;
        ungetc(x, fRef);
        return rVal;
    }
    else
    {
        // buffered read
        return fPosition >= fFileSize;
    }
}

void hsBufferedStream::Skip(UInt32 delta)
{
    if (fWriteBufferUsed)
    {
        // buffered write not implemented yet.
        fseek(fRef, delta, SEEK_CUR);
    }
    else
    {
        UInt32 blockStart = ((fPosition + delta) / kBufferSize) * kBufferSize;

        // We've got data in the buffer, see if we can just skip in that
        if (fBufferLen > 0)
        {
            Int32 newBufferPos = Int32(fPosition % kBufferSize) + Int32(delta);

            // If we skipped outside of our buffer, invalidate it
            if (newBufferPos < 0 || UInt32(newBufferPos) >= fBufferLen)
            {
                fBufferLen = 0;
                fseek(fRef, blockStart, SEEK_SET);
            }
        }
        else
            fseek(fRef, blockStart, SEEK_SET);
    }

    fPosition += delta;
}

void hsBufferedStream::Rewind()
{
    if (fWriteBufferUsed)
    {
        // buffered write not implemented yet.
        fseek(fRef, 0, SEEK_SET);
    }
    // If the currently buffered block isn't the first one, invalidate our buffer
    else if (fPosition >= kBufferSize)
        fBufferLen = 0;

    fPosition = 0;
}

UInt32 hsBufferedStream::GetEOF()
{
    if (fWriteBufferUsed)
    {
        if (!fRef)
            return 0;

        long oldPos = ftell(fRef);
        fseek(fRef, 0, SEEK_END);
        UInt32 end = (UInt32)ftell(fRef);
        fseek(fRef, oldPos, SEEK_SET);

        return end;
    }
    else
        return fFileSize;
}

void hsBufferedStream::Truncate()
{
    hsAssert(0, "hsBufferedStream::Truncate unimplemented");
}