/*
 * << Haru Free PDF Library >> -- hpdf_streams.c
 *
 * URL: http://libharu.org
 *
 * Copyright (c) 1999-2006 Takeshi Kanno <takeshi_kanno@est.hi-ho.ne.jp>
 * Copyright (c) 2007-2008 Antony Dovgal <tony@daylessday.org>
 *
 * Permission to use, copy, modify, distribute and sell this software
 * and its documentation for any purpose is hereby granted without fee,
 * provided that the above copyright notice appear in all copies and
 * that both that copyright notice and this permission notice appear
 * in supporting documentation.
 * It is provided "as is" without express or implied warranty.
 *
 */

#include <errno.h>

#ifndef HPDF_NOZLIB
#include <zlib.h>
#include <zconf.h>
#endif /* HPDF_NOZLIB */

#include "hpdfconf.h"
#include "hpdfcons.h"
#include "hpdfutil.h"
#include "hpdfstre.h"

HPDF_STATUS
HPDF_MemStream_WriteFunc  (HPDF_Stream      stream,
                           const HPDF_BYTE  *ptr,
                           HPDF_UINT        siz);


HPDF_STATUS
HPDF_MemStream_SeekFunc  (HPDF_Stream      stream,
                          HPDF_INT         pos,
                          HPDF_WhenceMode  mode);


HPDF_STATUS
HPDF_MemStream_ReadFunc  (HPDF_Stream  stream,
                          HPDF_BYTE    *buf,
                          HPDF_UINT    *size);


HPDF_INT32
HPDF_MemStream_TellFunc  (HPDF_Stream  stream);


HPDF_UINT32
HPDF_MemStream_SizeFunc  (HPDF_Stream  stream);


void
HPDF_MemStream_FreeFunc  (HPDF_Stream  stream);

HPDF_STATUS
HPDF_MemStream_InWrite  (HPDF_Stream      stream,
                         const HPDF_BYTE  **ptr,
                         HPDF_UINT        *count);

HPDF_STATUS
HPDF_Stream_WriteToStreamWithDeflate  (HPDF_Stream  src,
                                       HPDF_Stream  dst,
                                       HPDF_Encrypt  e);


HPDF_STATUS
HPDF_FileReader_ReadFunc  (HPDF_Stream  stream,
                          HPDF_BYTE    *ptr,
                          HPDF_UINT    *siz);


HPDF_STATUS
HPDF_FileReader_SeekFunc  (HPDF_Stream      stream,
                           HPDF_INT         pos,
                           HPDF_WhenceMode  mode);


HPDF_INT32
HPDF_FileStream_TellFunc  (HPDF_Stream  stream);


HPDF_UINT32
HPDF_FileStream_SizeFunc  (HPDF_Stream  stream);


HPDF_STATUS
HPDF_FileWriter_WriteFunc  (HPDF_Stream      stream,
                            const HPDF_BYTE  *ptr,
                            HPDF_UINT        siz);

void
HPDF_FileStream_FreeFunc  (HPDF_Stream  stream);



/*
 *  HPDF_Stream_Read
 *
 *  stream : Pointer to a HPDF_Stream object.
 *  ptr : Pointer to a buffer to copy read data.
 *  size : Pointer to a variable which indecates buffer size.
 *
 *  HPDF_Stream_read returns HPDF_OK when success. On failer, it returns
 *  error-code returned by reading function of this stream.
 *
 */

HPDF_STATUS
HPDF_Stream_Read  (HPDF_Stream  stream,
                   HPDF_BYTE    *ptr,
                   HPDF_UINT    *size)
{
    if (!(stream->read_fn))
        return HPDF_SetError (stream->error, HPDF_INVALID_OPERATION, 0);

    /*
    if (HPDF_Error_GetCode(stream->error) != HPDF_NOERROR)
        return HPDF_THIS_FUNC_WAS_SKIPPED;
    */

    return stream->read_fn(stream, ptr, size);
}


/*
 *  HPDF_Stream_ReadLn
 *
 *  stream : Pointer to a HPDF_Stream object.
 *  s : Pointer to a buffer to copy read data.
 *  size : buffer-size of s.
 *
 *  Read from stream until the buffer is exhausted or line-feed charactor is
 *  read.
 *
 */
HPDF_STATUS
HPDF_Stream_ReadLn  (HPDF_Stream  stream,
                     char    *s,
                     HPDF_UINT    *size)
{
    char buf[HPDF_STREAM_BUF_SIZ];
    HPDF_UINT r_size = *size;
    HPDF_UINT read_size = HPDF_STREAM_BUF_SIZ;

    HPDF_PTRACE((" HPDF_Stream_ReadLn\n"));

    if (!stream || !s || *size == 0)
        return HPDF_SetError (stream->error, HPDF_INVALID_PARAMETER, 0);

    if (!(stream->seek_fn) || !(stream->read_fn))
        return HPDF_SetError (stream->error, HPDF_INVALID_OPERATION, 0);

    if (r_size < HPDF_STREAM_BUF_SIZ)
        read_size = r_size;

    *size = 0;

    while (r_size > 1) {
        char *pbuf = buf;
        HPDF_STATUS ret = HPDF_Stream_Read (stream, (HPDF_BYTE *)buf, &read_size);

        if (ret != HPDF_OK && read_size == 0)
            return ret;

        r_size -= read_size;

        while (read_size > 0) {
            if (*pbuf == 0x0A || *pbuf == 0x0D) {
                *s = 0;
                read_size--;

                /* handling CR-LF marker */
                if (*pbuf == 0x0D || read_size > 1) {
                    pbuf++;

                    if (*pbuf == 0x0A)
                        read_size--;
                }

                if (read_size > 0)
                    return HPDF_Stream_Seek (stream, 0 - read_size,
                                HPDF_SEEK_CUR);
                else
                    return HPDF_OK;
            }

            *s++ = *pbuf++;
            read_size--;
            (*size)++;
        }

        if (r_size < HPDF_STREAM_BUF_SIZ)
            read_size = r_size;
        else
            read_size = HPDF_STREAM_BUF_SIZ;

        if (ret == HPDF_STREAM_EOF)
            return HPDF_STREAM_EOF;
    }

    *s = 0;

    return HPDF_STREAM_READLN_CONTINUE;
}


/*
 * HPDF_Stream_Write
 *
 *  stream : Pointer to a HPDF_Stream object.
 *  ptr : Pointer to a buffer to write.
 *  siz : The size of buffer to write.
 *
 *  HPDF_Stream_Write returns HPDF_OK when success. On failer, it returns
 *  error-code returned by writing function of this stream.
 *
 */
HPDF_STATUS
HPDF_Stream_Write  (HPDF_Stream      stream,
                    const HPDF_BYTE  *ptr,
                    HPDF_UINT        size)
{
    HPDF_STATUS ret;

    HPDF_PTRACE((" HPDF_Stream_Write\n"));

    if (!(stream->write_fn))
        return HPDF_SetError(stream->error, HPDF_INVALID_OPERATION, 0);

    /*
    if (HPDF_Error_GetCode(stream->error) != HPDF_NOERROR)
        return HPDF_THIS_FUNC_WAS_SKIPPED;
    */

    ret = stream->write_fn(stream, ptr, size);

    if (ret != HPDF_OK)
        return ret;

    stream->size += size;

    return HPDF_OK;
}


HPDF_STATUS
HPDF_Stream_WriteChar  (HPDF_Stream  stream,
                        char    value)
{
    return HPDF_Stream_Write(stream, (HPDF_BYTE *)&value, sizeof(char));
}


HPDF_STATUS
HPDF_Stream_WriteStr  (HPDF_Stream      stream,
                       const char  *value)
{
    HPDF_UINT len = HPDF_StrLen(value, -1);

    return HPDF_Stream_Write(stream, (HPDF_BYTE *)value, len);
}

HPDF_STATUS
HPDF_Stream_WriteUChar  (HPDF_Stream  stream,
                         HPDF_BYTE    value)
{
    return HPDF_Stream_Write(stream, &value, sizeof(HPDF_BYTE));
}

HPDF_STATUS
HPDF_Stream_WriteInt  (HPDF_Stream  stream,
                       HPDF_INT     value)
{
    char buf[HPDF_INT_LEN + 1];

    char* p = HPDF_IToA(buf, value, buf + HPDF_INT_LEN);

    return HPDF_Stream_Write(stream, (HPDF_BYTE *)buf, (HPDF_UINT)(p - buf));
}

HPDF_STATUS
HPDF_Stream_WriteUInt  (HPDF_Stream  stream,
                        HPDF_UINT    value)
{
    return HPDF_Stream_WriteInt(stream, (HPDF_INT)value);
}

HPDF_STATUS
HPDF_Stream_WriteReal  (HPDF_Stream  stream,
                        HPDF_REAL    value)
{
    char buf[HPDF_REAL_LEN + 1];

    char* p = HPDF_FToA(buf, value, buf + HPDF_REAL_LEN);

    return HPDF_Stream_Write(stream, (HPDF_BYTE *)buf, (HPDF_UINT)(p - buf));
}

void
HPDF_Stream_Free  (HPDF_Stream  stream)
{
    if (!stream)
        return;

    if (stream->free_fn)
        stream->free_fn(stream);

    stream->sig_bytes = 0;

    HPDF_FreeMem(stream->mmgr, stream);
}

HPDF_STATUS
HPDF_Stream_Seek  (HPDF_Stream      stream,
                   HPDF_INT         pos,
                   HPDF_WhenceMode  mode)
{
    HPDF_PTRACE((" HPDF_Stream_Seek\n"));

    if (!(stream->seek_fn))
        return HPDF_SetError (stream->error, HPDF_INVALID_OPERATION, 0);

    if (HPDF_Error_GetCode(stream->error) != 0)
        return HPDF_THIS_FUNC_WAS_SKIPPED;

    return stream->seek_fn(stream, pos, mode);
}


HPDF_INT32
HPDF_Stream_Tell  (HPDF_Stream  stream)
{
    HPDF_PTRACE((" HPDF_Stream_Tell\n"));

    if (!(stream->tell_fn))
        return HPDF_SetError (stream->error, HPDF_INVALID_OPERATION, 0);

    if (HPDF_Error_GetCode(stream->error) != 0)
        return HPDF_THIS_FUNC_WAS_SKIPPED;

    return stream->tell_fn(stream);
}


HPDF_UINT32
HPDF_Stream_Size  (HPDF_Stream  stream)
{
    HPDF_PTRACE((" HPDF_Stream_Tell\n"));

    if (stream->write_fn)
        return stream->size;

    if (!(stream->size_fn)) {
        HPDF_SetError (stream->error, HPDF_INVALID_OPERATION, 0);
        return 0;
    }

    if (HPDF_Error_GetCode(stream->error) != 0)
        return 0;

    return stream->size_fn(stream);
}


HPDF_STATUS
HPDF_Stream_WriteEscapeName  (HPDF_Stream      stream,
                              const char  *value)
{
    char tmp_char[HPDF_LIMIT_MAX_NAME_LEN * 3 + 2];
    HPDF_UINT len;
    HPDF_INT i;
    const HPDF_BYTE* pos1;
    char* pos2;

    HPDF_PTRACE((" HPDF_Stream_WriteEscapeName\n"));

    len = HPDF_StrLen (value, HPDF_LIMIT_MAX_NAME_LEN);
    pos1 = (HPDF_BYTE*)value;
    pos2 = tmp_char;

    *pos2++ = '/';
    for (i = 0; i < (HPDF_INT32)len; i++) {
        HPDF_BYTE c = *pos1++;
        if (HPDF_NEEDS_ESCAPE(c)) {
            *pos2++ = '#';
            *pos2 = (char)(c >> 4);
            if (*pos2 <= 9)
                *pos2 += 0x30;
            else
                *pos2 += 0x41 - 10;
            pos2++;

            *pos2 = (char)(c & 0x0f);
            if (*pos2 <= 9)
                *pos2 += 0x30;
            else
                *pos2 += 0x41 - 10;
            pos2++;
        } else
            *pos2++ = c;
    }
    *pos2 = 0;

    return HPDF_Stream_Write (stream, (HPDF_BYTE *)tmp_char, HPDF_StrLen(tmp_char, -1));
}

HPDF_STATUS
HPDF_Stream_WriteEscapeText2  (HPDF_Stream    stream,
                               const char    *text,
                               HPDF_UINT      len)
{
    char buf[HPDF_TEXT_DEFAULT_LEN];
    HPDF_UINT idx = 0;
    HPDF_INT i;
    const char* p = text;
    HPDF_STATUS ret;

    HPDF_PTRACE((" HPDF_Stream_WriteEscapeText2\n"));

    if (!len)
        return HPDF_OK;

    buf[idx++] = '(';

    for (i = 0; i < len; i++) {
        HPDF_BYTE c = (HPDF_BYTE)*p++;
        if (HPDF_NEEDS_ESCAPE(c)) {
            buf[idx++] = '\\';

            buf[idx] = c >> 6;
            buf[idx] += 0x30;
            idx++;
            buf[idx] = (c & 0x38) >> 3;
            buf[idx] += 0x30;
            idx++;
            buf[idx] = (c & 0x07);
            buf[idx] += 0x30;
            idx++;
        }
        else
            buf[idx++] = c;

        if (idx > HPDF_TEXT_DEFAULT_LEN - 4) {
            ret = HPDF_Stream_Write (stream, (HPDF_BYTE *)buf, idx);
            if (ret != HPDF_OK)
                return ret;
            idx = 0;
        }
    }
    buf[idx++] = ')';

    ret = HPDF_Stream_Write (stream, (HPDF_BYTE *)buf, idx);

    return ret;
}

HPDF_STATUS
HPDF_Stream_WriteEscapeText  (HPDF_Stream    stream,
                              const char    *text)
{
    HPDF_UINT len;

    HPDF_PTRACE((" HPDF_Stream_WriteEscapeText\n"));

    len = (text == NULL) ? 0 : HPDF_StrLen (text, HPDF_LIMIT_MAX_STRING_LEN);

    return HPDF_Stream_WriteEscapeText2(stream, text, len);
}

HPDF_STATUS
HPDF_Stream_WriteBinary  (HPDF_Stream      stream,
                          const HPDF_BYTE  *data,
                          HPDF_UINT        len,
                          HPDF_Encrypt     e)
{
    char buf[HPDF_TEXT_DEFAULT_LEN];
    HPDF_BYTE ebuf[HPDF_TEXT_DEFAULT_LEN];
    HPDF_BYTE *pbuf = NULL;
    HPDF_BOOL flg = HPDF_FALSE;
    HPDF_UINT idx = 0;
    HPDF_UINT i;
    const HPDF_BYTE* p;
    HPDF_STATUS ret = HPDF_OK;

    HPDF_PTRACE((" HPDF_Stream_WriteBinary\n"));

    if (e) {
        if (len <= HPDF_TEXT_DEFAULT_LEN)
            pbuf = ebuf;
        else {
            pbuf = (HPDF_BYTE *)HPDF_GetMem (stream->mmgr, len);
            flg = HPDF_TRUE;
        }

        HPDF_Encrypt_CryptBuf (e, data, pbuf, len);
        p = pbuf;
    } else {
        p = data;
    }

    for (i = 0; i < len; i++, p++) {
        char c = *p >> 4;

        if (c <= 9)
            c += 0x30;
        else
            c += 0x41 - 10;
        buf[idx++] = c;

        c = *p & 0x0f;
        if (c <= 9)
            c += 0x30;
        else
            c += 0x41 - 10;
        buf[idx++] = c;

        if (idx > HPDF_TEXT_DEFAULT_LEN - 2) {
            ret = HPDF_Stream_Write (stream, (HPDF_BYTE *)buf, idx);
            if (ret != HPDF_OK) {
                if (flg)
                    HPDF_FreeMem (stream->mmgr, pbuf);
                return ret;
            }
            idx = 0;
        }
    }

    if (idx > 0) {
        ret = HPDF_Stream_Write (stream, (HPDF_BYTE *)buf, idx);
    }

    if (flg)
        HPDF_FreeMem (stream->mmgr, pbuf);

    return ret;
}


HPDF_STATUS
HPDF_Stream_WriteToStreamWithDeflate  (HPDF_Stream  src,
                                       HPDF_Stream  dst,
                                       HPDF_Encrypt  e)
{
#ifndef HPDF_NOZLIB

#define DEFLATE_BUF_SIZ  ((HPDF_INT)(HPDF_STREAM_BUF_SIZ * 1.1) + 13)

    HPDF_STATUS ret;
    HPDF_BOOL flg;

    z_stream strm;
    Bytef inbuf[HPDF_STREAM_BUF_SIZ];
    Bytef otbuf[DEFLATE_BUF_SIZ];
    HPDF_BYTE ebuf[DEFLATE_BUF_SIZ];

    HPDF_PTRACE((" HPDF_Stream_WriteToStreamWithDeflate\n"));

    /* initialize input stream */
    ret = HPDF_Stream_Seek (src, 0, HPDF_SEEK_SET);
    if (ret != HPDF_OK)
        return ret;

    /* initialize decompression stream. */
    HPDF_MemSet(&strm, 0x00, sizeof(z_stream));
    strm.next_out = otbuf;
    strm.avail_out = DEFLATE_BUF_SIZ;

    ret = deflateInit_(&strm, Z_DEFAULT_COMPRESSION, ZLIB_VERSION,
            sizeof(z_stream));
    if (ret != Z_OK)
        return HPDF_SetError (src->error, HPDF_ZLIB_ERROR, ret);

    strm.next_in = inbuf;
    strm.avail_in = 0;

    flg = HPDF_FALSE;
    for (;;) {
        HPDF_UINT size = HPDF_STREAM_BUF_SIZ;

        ret = HPDF_Stream_Read (src, inbuf, &size);

        strm.next_in = inbuf;
        strm.avail_in = size;

        if (ret != HPDF_OK) {
            if (ret == HPDF_STREAM_EOF) {
                flg = HPDF_TRUE;
                if (size == 0)
                    break;
            } else {
                deflateEnd(&strm);
                return ret;
            }
        }

        while (strm.avail_in > 0) {
            ret = deflate(&strm, Z_NO_FLUSH);
            if (ret != Z_OK && ret != Z_STREAM_END) {
                deflateEnd(&strm);
                return HPDF_SetError (src->error, HPDF_ZLIB_ERROR, ret);
            }

            if (strm.avail_out == 0) {
                if (e) {
                    HPDF_Encrypt_CryptBuf (e, otbuf, ebuf, DEFLATE_BUF_SIZ);
                    ret = HPDF_Stream_Write(dst, ebuf, DEFLATE_BUF_SIZ);
                } else
                    ret = HPDF_Stream_Write (dst, otbuf, DEFLATE_BUF_SIZ);

                if (ret != HPDF_OK) {
                    deflateEnd(&strm);
                    return HPDF_SetError (src->error, HPDF_ZLIB_ERROR, ret);
                }

                strm.next_out = otbuf;
                strm.avail_out = DEFLATE_BUF_SIZ;
            }
        }

        if (flg)
            break;
    }

    flg = HPDF_FALSE;
    for (;;) {
        ret = deflate(&strm, Z_FINISH);
        if (ret != Z_OK && ret != Z_STREAM_END) {
            deflateEnd(&strm);
            return HPDF_SetError (src->error, HPDF_ZLIB_ERROR, ret);
        }

        if (ret == Z_STREAM_END)
            flg = HPDF_TRUE;

        if (strm.avail_out < DEFLATE_BUF_SIZ) {
            HPDF_UINT osize = DEFLATE_BUF_SIZ - strm.avail_out;
            if (e) {
                HPDF_Encrypt_CryptBuf (e, otbuf, ebuf, osize);
                ret = HPDF_Stream_Write(dst, ebuf, osize);
            } else
                ret = HPDF_Stream_Write (dst, otbuf, osize);

            if (ret != HPDF_OK) {
                deflateEnd(&strm);
                return HPDF_SetError (src->error, HPDF_ZLIB_ERROR, ret);
            }

            strm.next_out = otbuf;
            strm.avail_out = DEFLATE_BUF_SIZ;
        }

        if (flg)
            break;
    }

    deflateEnd(&strm);
    return HPDF_OK;
#else /* HPDF_NOZLIB */
    return HPDF_UNSUPPORTED_FUNC;
#endif /* HPDF_NOZLIB */
}

HPDF_STATUS
HPDF_Stream_WriteToStream  (HPDF_Stream  src,
                            HPDF_Stream  dst,
                            HPDF_UINT    filter,
                            HPDF_Encrypt  e)
{
    HPDF_STATUS ret;
    HPDF_BYTE buf[HPDF_STREAM_BUF_SIZ];
    HPDF_BYTE ebuf[HPDF_STREAM_BUF_SIZ];
    HPDF_BOOL flg;

    HPDF_PTRACE((" HPDF_Stream_WriteToStream\n"));

    if (!dst || !(dst->write_fn)) {
        HPDF_SetError (src->error, HPDF_INVALID_OBJECT, 0);
        return HPDF_INVALID_OBJECT;
    }

    if (HPDF_Error_GetCode (src->error) != HPDF_NOERROR ||
            HPDF_Error_GetCode (dst->error) != HPDF_NOERROR)
        return HPDF_THIS_FUNC_WAS_SKIPPED;

#ifndef HPDF_NOZLIB
    if (filter & HPDF_STREAM_FILTER_FLATE_DECODE)
        return HPDF_Stream_WriteToStreamWithDeflate (src, dst, e);
#endif /* HPDF_NOZLIB */

    /* initialize input stream */
    if (HPDF_Stream_Size (src) == 0)
        return HPDF_OK;

    ret = HPDF_Stream_Seek (src, 0, HPDF_SEEK_SET);
    if (ret != HPDF_OK)
        return ret;

    flg = HPDF_FALSE;
    for (;;) {
        HPDF_UINT size = HPDF_STREAM_BUF_SIZ;

        ret = HPDF_Stream_Read (src, buf, &size);

        if (ret != HPDF_OK) {
            if (ret == HPDF_STREAM_EOF) {
                flg = HPDF_TRUE;
                if (size == 0)
                    break;
            } else {
                return ret;
            }
        }

        if (e) {
            HPDF_Encrypt_CryptBuf (e, buf, ebuf, size);
            ret = HPDF_Stream_Write(dst, ebuf, size);
        } else {
            ret = HPDF_Stream_Write(dst, buf, size);
        }

        if (ret != HPDF_OK)
            return ret;

        if (flg)
            break;
    }

    return HPDF_OK;
}

HPDF_Stream
HPDF_FileReader_New  (HPDF_MMgr   mmgr,
                      const char  *fname)
{
    HPDF_Stream stream;
    HPDF_FILEP fp = HPDF_FOPEN (fname, "rb");

    HPDF_PTRACE((" HPDF_FileReader_New\n"));

    if (!fp) {
        HPDF_SetError (mmgr->error, HPDF_FILE_OPEN_ERROR, errno);
        return NULL;
    }

    stream = (HPDF_Stream)HPDF_GetMem(mmgr, sizeof(HPDF_Stream_Rec));

    if (stream) {
        HPDF_MemSet(stream, 0, sizeof(HPDF_Stream_Rec));
        stream->sig_bytes = HPDF_STREAM_SIG_BYTES;
        stream->type = HPDF_STREAM_FILE;
        stream->error = mmgr->error;
        stream->mmgr = mmgr;
        stream->read_fn = HPDF_FileReader_ReadFunc;
        stream->seek_fn = HPDF_FileReader_SeekFunc;
        stream->tell_fn = HPDF_FileStream_TellFunc;
        stream->size_fn = HPDF_FileStream_SizeFunc;
        stream->free_fn = HPDF_FileStream_FreeFunc;
        stream->attr = fp;
    }

    return stream;
}

/*
 *  HPDF_FileReader_ReadFunc
 *
 *  Reading data function for HPDF_FileReader.
 *
 */

HPDF_STATUS
HPDF_FileReader_ReadFunc  (HPDF_Stream  stream,
                          HPDF_BYTE    *ptr,
                          HPDF_UINT    *siz)
{
    HPDF_FILEP fp = (HPDF_FILEP)stream->attr;
    HPDF_UINT rsiz;

    HPDF_PTRACE((" HPDF_FileReader_ReadFunc\n"));

    HPDF_MemSet(ptr, 0, *siz);
    rsiz = HPDF_FREAD(ptr, 1, *siz, fp);

    if (rsiz != *siz) {
        if (HPDF_FEOF(fp)) {

            *siz = rsiz;

            return HPDF_STREAM_EOF;
        }

        return HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR, HPDF_FERROR(fp));
    }

    return HPDF_OK;
}

/*
 *  HPDF_FileReader_SeekFunc
 *
 *  Seeking data function for HPDF_FileReader.
 *
 *  stream : Pointer to a HPDF_Stream object.
 *  pos : New position of stream object.
 *  HPDF_whence_mode : Seeking mode describing below.
 *                     HPDF_SEEK_SET : Absolute file position
 *                     HPDF_SEEK_CUR : Relative to the current file position
 *                     HPDF_SEEK_END : Relative to the current end of file.
 *
 *  HPDF_FileReader_seek_fn returns HPDF_OK when successful. On failer
 *  the result is HPDF_FILE_IO_ERROR and HPDF_Error_GetCode2() returns the
 *  error which returned by file seeking function of platform.
 *
 */

HPDF_STATUS
HPDF_FileReader_SeekFunc  (HPDF_Stream     stream,
                           HPDF_INT         pos,
                           HPDF_WhenceMode  mode)
{
    HPDF_FILEP fp = (HPDF_FILEP)stream->attr;
    HPDF_INT whence;

    HPDF_PTRACE((" HPDF_FileReader_SeekFunc\n"));

    switch (mode) {
        case HPDF_SEEK_CUR:
            whence = SEEK_CUR;
            break;
        case HPDF_SEEK_END:
            whence = SEEK_END;
            break;
        default:
            whence = SEEK_SET;
    }

    if (HPDF_FSEEK (fp, pos, whence) != 0) {
        return HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR, HPDF_FERROR(fp));
    }

    return HPDF_OK;
}


HPDF_INT32
HPDF_FileStream_TellFunc  (HPDF_Stream   stream)
{
    HPDF_INT32 ret;
    HPDF_FILEP fp = (HPDF_FILEP)stream->attr;

    HPDF_PTRACE((" HPDF_FileReader_TellFunc\n"));

    if ((ret = HPDF_FTELL (fp)) < 0) {
        return HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR,
                HPDF_FERROR(fp));
    }

    return ret;
}


HPDF_UINT32
HPDF_FileStream_SizeFunc  (HPDF_Stream   stream)
{
    HPDF_INT size;
    HPDF_INT ptr;
    HPDF_FILEP fp = (HPDF_FILEP)stream->attr;

    HPDF_PTRACE((" HPDF_FileReader_SizeFunc\n"));

    /* save current file-pointer */
    if ((ptr = HPDF_FTELL (fp)) < 0) {
        HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR,
                HPDF_FERROR(fp));
        return 0;
    }

    /* move file-pointer to the end of the file */
    if (HPDF_FSEEK (fp, 0, SEEK_END) < 0) {
        HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR,
                HPDF_FERROR(fp));
        return 0;
    }

    /* get the pointer of the end of the file */
    if ((size = HPDF_FTELL (fp)) < 0) {
        HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR,
                HPDF_FERROR(fp));
        return 0;
    }

    /* restore current file-pointer */
    if (HPDF_FSEEK (fp, ptr, SEEK_SET) < 0) {
        HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR,
                HPDF_FERROR(fp));
        return 0;
    }

    return (HPDF_UINT32)size;
}


HPDF_Stream
HPDF_FileWriter_New  (HPDF_MMgr        mmgr,
                      const char  *fname)
{
    HPDF_Stream stream;
    HPDF_FILEP fp = HPDF_FOPEN (fname, "wb");

    HPDF_PTRACE((" HPDF_FileWriter_New\n"));

    if (!fp) {
        HPDF_SetError (mmgr->error, HPDF_FILE_OPEN_ERROR, errno);
        return NULL;
    }

    stream = (HPDF_Stream)HPDF_GetMem (mmgr, sizeof(HPDF_Stream_Rec));

    if (stream) {
        HPDF_MemSet (stream, 0, sizeof(HPDF_Stream_Rec));
        stream->sig_bytes = HPDF_STREAM_SIG_BYTES;
        stream->error = mmgr->error;
        stream->mmgr = mmgr;
        stream->write_fn = HPDF_FileWriter_WriteFunc;
        stream->free_fn = HPDF_FileStream_FreeFunc;
        stream->tell_fn = HPDF_FileStream_TellFunc;
        stream->attr = fp;
        stream->type = HPDF_STREAM_FILE;
    }

    return stream;
}

HPDF_STATUS
HPDF_FileWriter_WriteFunc  (HPDF_Stream      stream,
                            const HPDF_BYTE  *ptr,
                            HPDF_UINT        siz)
{
    HPDF_FILEP fp;
    HPDF_UINT ret;

    HPDF_PTRACE((" HPDF_FileWriter_WriteFunc\n"));

    fp = (HPDF_FILEP)stream->attr;
    ret = HPDF_FWRITE (ptr, 1, siz, fp);

    if (ret != siz) {
        return HPDF_SetError (stream->error, HPDF_FILE_IO_ERROR, HPDF_FERROR(fp));
    }

    return HPDF_OK;
}


void
HPDF_FileStream_FreeFunc  (HPDF_Stream  stream)
{
    HPDF_FILEP fp;

    HPDF_PTRACE((" HPDF_FileStream_FreeFunc\n"));

    fp = (HPDF_FILEP)stream->attr;

    if (fp)
        HPDF_FCLOSE(fp);

    stream->attr = NULL;
}

HPDF_STATUS
HPDF_MemStream_InWrite  (HPDF_Stream      stream,
                         const HPDF_BYTE  **ptr,
                         HPDF_UINT        *count)
{
    HPDF_MemStreamAttr attr = (HPDF_MemStreamAttr)stream->attr;
    HPDF_UINT rsize = attr->buf_siz - attr->w_pos;

    HPDF_PTRACE((" HPDF_MemStream_InWrite\n"));

    if (*count <= 0)
        return HPDF_OK;

    if (rsize >= *count) {
        HPDF_MemCpy (attr->w_ptr, *ptr, *count);
        attr->w_ptr += *count;
        attr->w_pos += *count;
        *count = 0;
    } else {
        if (rsize > 0) {
            HPDF_MemCpy (attr->w_ptr, *ptr, rsize);
            *ptr += rsize;
            *count -= rsize;
        }
        attr->w_ptr = (HPDF_BYTE*)HPDF_GetMem (stream->mmgr, attr->buf_siz);

        if (attr->w_ptr == NULL)
           return HPDF_Error_GetCode (stream->error);

        if (HPDF_List_Add (attr->buf, attr->w_ptr) != HPDF_OK) {
            HPDF_FreeMem (stream->mmgr, attr->w_ptr);
            attr->w_ptr = NULL;

            return HPDF_Error_GetCode (stream->error);
        }
        attr->w_pos = 0;
    }
    return HPDF_OK;
}


HPDF_STATUS
HPDF_MemStream_WriteFunc  (HPDF_Stream      stream,
                           const HPDF_BYTE  *ptr,
                           HPDF_UINT        siz)
{
    HPDF_UINT wsiz = siz;

    HPDF_PTRACE((" HPDF_MemStream_WriteFunc\n"));

    if (HPDF_Error_GetCode (stream->error) != 0)
        return HPDF_THIS_FUNC_WAS_SKIPPED;

    while (wsiz > 0) {
        HPDF_STATUS ret = HPDF_MemStream_InWrite (stream, &ptr, &wsiz);
        if (ret != HPDF_OK)
            return ret;
    }

    return HPDF_OK;
}


HPDF_INT32
HPDF_MemStream_TellFunc  (HPDF_Stream  stream)
{
    HPDF_INT32 ret;
    HPDF_MemStreamAttr attr = (HPDF_MemStreamAttr)stream->attr;

    HPDF_PTRACE((" HPDF_MemStream_TellFunc\n"));

    ret = attr->r_ptr_idx * attr->buf_siz;
    ret += attr->r_pos;

    return ret;
}


HPDF_UINT32
HPDF_MemStream_SizeFunc  (HPDF_Stream  stream)
{
    HPDF_PTRACE((" HPDF_MemStream_SizeFunc\n"));

    return stream->size;
}

HPDF_STATUS
HPDF_MemStream_SeekFunc  (HPDF_Stream      stream,
                          HPDF_INT         pos,
                          HPDF_WhenceMode  mode)
{
    HPDF_MemStreamAttr attr = (HPDF_MemStreamAttr)stream->attr;

    HPDF_PTRACE((" HPDF_MemStream_SeekFunc\n"));

    if (mode == HPDF_SEEK_CUR) {
        pos += (attr->r_ptr_idx * attr->buf_siz);
        pos += attr->r_pos;
    } else if (mode == HPDF_SEEK_END)
        pos = stream->size - pos;

    if (pos > stream->size || stream->size == 0)
        return HPDF_SetError (stream->error, HPDF_STREAM_EOF, 0);

    attr->r_ptr_idx = pos / attr->buf_siz;
    attr->r_pos = pos % attr->buf_siz;
    attr->r_ptr = (HPDF_BYTE*)HPDF_List_ItemAt (attr->buf, attr->r_ptr_idx);
    if (attr->r_ptr == NULL) {
        HPDF_SetError (stream->error, HPDF_INVALID_OBJECT, 0);
        return HPDF_INVALID_OBJECT;
    } else
        attr->r_ptr += attr->r_pos;

    return HPDF_OK;
}


HPDF_BYTE*
HPDF_MemStream_GetBufPtr  (HPDF_Stream  stream,
                           HPDF_UINT    index,
                           HPDF_UINT    *length)
{
    HPDF_BYTE *ret;
    HPDF_MemStreamAttr attr;

    HPDF_PTRACE((" HPDF_MemStream_GetBufPtr\n"));

    if (stream->type != HPDF_STREAM_MEMORY) {
        HPDF_SetError (stream->error, HPDF_INVALID_OBJECT, 0);
        return NULL;
    }

    attr = (HPDF_MemStreamAttr)stream->attr;

    ret = (HPDF_BYTE *)HPDF_List_ItemAt (attr->buf, index);
    if (ret == NULL) {
        HPDF_SetError (stream->error, HPDF_INVALID_PARAMETER, 0);
        *length = 0;
        return NULL;
    }

    *length = (attr->buf->count - 1 == index) ? attr->w_pos : attr->buf_siz;
    return ret;
}


void
HPDF_MemStream_FreeData  (HPDF_Stream  stream)
{
    HPDF_MemStreamAttr attr;
    HPDF_UINT i;

    HPDF_PTRACE((" HPDF_MemStream_FreeData\n"));

    if (!stream || stream->type != HPDF_STREAM_MEMORY)
        return;

    attr = (HPDF_MemStreamAttr)stream->attr;

    for (i = 0; i < attr->buf->count; i++)
        HPDF_FreeMem (stream->mmgr, HPDF_List_ItemAt (attr->buf, i));

    HPDF_List_Clear(attr->buf);

    stream->size = 0;
    attr->w_pos = attr->buf_siz;
    attr->w_ptr = NULL;
    attr->r_ptr_idx = 0;
    attr->r_pos = 0;
}

void
HPDF_MemStream_FreeFunc  (HPDF_Stream  stream)
{
    HPDF_MemStreamAttr attr;

    HPDF_PTRACE((" HPDF_MemStream_FreeFunc\n"));

    attr = (HPDF_MemStreamAttr)stream->attr;
    HPDF_MemStream_FreeData (stream);
    HPDF_List_Free (attr->buf);
    HPDF_FreeMem (stream->mmgr, attr);
    stream->attr = NULL;
}

HPDF_Stream
HPDF_MemStream_New  (HPDF_MMgr  mmgr,
                     HPDF_UINT  buf_siz)
{
    HPDF_Stream stream;

    HPDF_PTRACE((" HPDF_MemStream_New\n"));

    // Create new HPDF_Stream object.
    stream = (HPDF_Stream)HPDF_GetMem (mmgr, sizeof(HPDF_Stream_Rec));

    if (stream) {
        // Create attribute struct.
        HPDF_MemStreamAttr attr = (HPDF_MemStreamAttr)HPDF_GetMem (mmgr,
                sizeof(HPDF_MemStreamAttr_Rec));

        if (!attr) {
            HPDF_FreeMem (mmgr, stream);
            return NULL;
        }

        HPDF_MemSet (stream, 0, sizeof(HPDF_Stream_Rec));
        HPDF_MemSet (attr, 0, sizeof(HPDF_MemStreamAttr_Rec));

        attr->buf = HPDF_List_New (mmgr, HPDF_DEF_ITEMS_PER_BLOCK);
        if (!attr->buf) {
            HPDF_FreeMem (mmgr, stream);
            HPDF_FreeMem (mmgr, attr);
            return NULL;
        }

        stream->sig_bytes = HPDF_STREAM_SIG_BYTES;
        stream->type = HPDF_STREAM_MEMORY;
        stream->error = mmgr->error;
        stream->mmgr = mmgr;
        stream->attr = attr;
        attr->buf_siz = (buf_siz > 0) ? buf_siz : HPDF_STREAM_BUF_SIZ;
        attr->w_pos = attr->buf_siz;

        stream->write_fn = HPDF_MemStream_WriteFunc;
        stream->read_fn = HPDF_MemStream_ReadFunc;
        stream->seek_fn = HPDF_MemStream_SeekFunc;
        stream->tell_fn = HPDF_MemStream_TellFunc;
        stream->size_fn = HPDF_MemStream_SizeFunc;
        stream->free_fn = HPDF_MemStream_FreeFunc;
    }

    return stream;
}

HPDF_UINT
HPDF_MemStream_GetBufSize  (HPDF_Stream  stream)
{
    HPDF_MemStreamAttr attr;

    HPDF_PTRACE((" HPDF_MemStream_GetBufSize\n"));

    if (!stream || stream->type != HPDF_STREAM_MEMORY)
        return 0;

    attr = (HPDF_MemStreamAttr)stream->attr;
    return attr->buf_siz;
}

HPDF_UINT
HPDF_MemStream_GetBufCount  (HPDF_Stream  stream)
{
    HPDF_MemStreamAttr attr;

    HPDF_PTRACE((" HPDF_MemStream_GetBufCount\n"));

    if (!stream || stream->type != HPDF_STREAM_MEMORY)
        return 0;

    attr = (HPDF_MemStreamAttr)stream->attr;
    return attr->buf->count;
}

HPDF_STATUS
HPDF_MemStream_ReadFunc  (HPDF_Stream  stream,
                          HPDF_BYTE    *buf,
                          HPDF_UINT    *size)
{
    HPDF_MemStreamAttr attr = (HPDF_MemStreamAttr)stream->attr;
    HPDF_UINT buf_size;
    HPDF_UINT rlen = *size;

    HPDF_PTRACE((" HPDF_MemStream_ReadFunc\n"));

    *size = 0;

    while (rlen > 0) {
        HPDF_UINT tmp_len;

        if (attr->buf->count == 0)
            return HPDF_STREAM_EOF;

        if (attr->buf->count - 1 > attr->r_ptr_idx)
            tmp_len = attr->buf_siz - attr->r_pos;
        else if (attr->buf->count - 1 == attr->r_ptr_idx)
            tmp_len = attr->w_pos - attr->r_pos;
        else
            return HPDF_STREAM_EOF;

        if (!attr->r_ptr)
            attr->r_ptr = (HPDF_BYTE*)HPDF_List_ItemAt (attr->buf,
                            attr->r_ptr_idx);

        if (tmp_len >= rlen) {
            HPDF_MemCpy (buf, attr->r_ptr, rlen);
            attr->r_pos += rlen;
            *size += rlen;
            attr->r_ptr += rlen;
            return HPDF_OK;
        } else {
            buf = HPDF_MemCpy (buf, attr->r_ptr, tmp_len);
            rlen -= tmp_len;
            *size += tmp_len;

            if (attr->r_ptr_idx == attr->buf->count - 1) {
                attr->r_ptr += tmp_len;
                attr->r_pos += tmp_len;
                return HPDF_STREAM_EOF;
            }

            attr->r_ptr_idx++;
            attr->r_pos = 0;
            attr->r_ptr = HPDF_MemStream_GetBufPtr (stream, attr->r_ptr_idx,
                    &buf_size);
        }
    }

    return HPDF_OK;
}


HPDF_STATUS
HPDF_MemStream_Rewrite  (HPDF_Stream  stream,
                         HPDF_BYTE    *buf,
                         HPDF_UINT    size)
{
    HPDF_MemStreamAttr attr = (HPDF_MemStreamAttr)stream->attr;
    HPDF_UINT buf_size;
    HPDF_UINT rlen = size;

    HPDF_PTRACE((" HPDF_MemStream_Rewrite\n"));

    while (rlen > 0) {
        HPDF_UINT tmp_len;

        if (attr->buf->count <= attr->r_ptr_idx) {
            HPDF_STATUS ret = HPDF_MemStream_WriteFunc (stream, buf, rlen);
            attr->r_ptr_idx = attr->buf->count;
            attr->r_pos = attr->w_pos;
            attr->r_ptr = attr->w_ptr;
            return ret;
        } else if (attr->buf->count == attr->r_ptr_idx)
            tmp_len = attr->w_pos - attr->r_pos;
        else
            tmp_len = attr->buf_siz - attr->r_pos;

        if (tmp_len >= rlen) {
            HPDF_MemCpy(attr->r_ptr, buf, rlen);
            attr->r_pos += rlen;
            attr->r_ptr += rlen;
            return HPDF_OK;
        } else {
            HPDF_MemCpy(attr->r_ptr, buf, tmp_len);
            buf += tmp_len;
            rlen -= tmp_len;
            attr->r_ptr_idx++;

            if (attr->buf->count > attr->r_ptr_idx) {
                attr->r_pos = 0;
                attr->r_ptr = HPDF_MemStream_GetBufPtr (stream, attr->r_ptr_idx,
                        &buf_size);
            }
        }
    }
    return HPDF_OK;
}

/*
 *  HPDF_CallbackReader_new
 *
 *  Constractor for HPDF_CallbackReader.
 *
 *  mmgr : Pointer to a HPDF_MMgr object.
 *  read_fn : Pointer to a user function for reading data.
 *  seek_fn : Pointer to a user function for seeking data.
 *  data : Pointer to a data which defined by user.
 *
 *  return: If success, It returns pointer to new HPDF_Stream object,
 *          otherwise, it returns NULL.
 *
 */


HPDF_Stream
HPDF_CallbackReader_New  (HPDF_MMgr              mmgr,
                          HPDF_Stream_Read_Func  read_fn,
                          HPDF_Stream_Seek_Func  seek_fn,
                          HPDF_Stream_Tell_Func  tell_fn,
                          HPDF_Stream_Size_Func  size_fn,
                          void*                  data)
{
    HPDF_Stream stream;

    HPDF_PTRACE((" HPDF_CallbackReader_New\n"));

    stream = (HPDF_Stream)HPDF_GetMem (mmgr, sizeof(HPDF_Stream_Rec));

    if (stream) {
        HPDF_MemSet (stream, 0, sizeof(HPDF_Stream_Rec));
        stream->sig_bytes = HPDF_STREAM_SIG_BYTES;
        stream->error = mmgr->error;
        stream->mmgr = mmgr;
        stream->read_fn = read_fn;
        stream->seek_fn = seek_fn;
        stream->tell_fn = tell_fn;
        stream->size_fn = size_fn;
        stream->attr = data;
        stream->type = HPDF_STREAM_CALLBACK;
    }

    return stream;
}

/*
 *  HPDF_CallbackWriter_new
 *
 *  Constractor for HPDF_CallbackWriter.
 *
 *  mmgr : Pointer to a HPDF_MMgr object.
 *  read_fn : Pointer to a user function for writing data.
 *  data : Pointer to a data which defined by user.
 *
 *  return: If success, It returns pointer to new HPDF_Stream object,
 *          otherwise, it returns NULL.
 *
 */


HPDF_Stream
HPDF_CallbackWriter_New (HPDF_MMgr               mmgr,
                         HPDF_Stream_Write_Func  write_fn,
                         void*                   data)
{
    HPDF_Stream stream;

    HPDF_PTRACE((" HPDF_CallbackWriter_New\n"));

    stream = (HPDF_Stream)HPDF_GetMem (mmgr, sizeof(HPDF_Stream_Rec));

    if (stream) {
        HPDF_MemSet (stream, 0, sizeof(HPDF_Stream_Rec));
        stream->sig_bytes = HPDF_STREAM_SIG_BYTES;
        stream->error = mmgr->error;
        stream->mmgr = mmgr;
        stream->write_fn = write_fn;
        stream->attr = data;
        stream->type = HPDF_STREAM_CALLBACK;
    }

    return stream;
}



HPDF_STATUS
HPDF_Stream_Validate  (HPDF_Stream  stream)
{
    if (!stream || stream->sig_bytes != HPDF_STREAM_SIG_BYTES)
        return HPDF_FALSE;
    else
        return HPDF_TRUE;
}

