// tiff stream interface class implementation

#include "tiffstream.h"

const char *TiffStream::m_name = "TiffStream";

TiffStream::TiffStream()
{
    m_tif = NULL;

    m_inStream = NULL;
    m_outStream = NULL;
    m_ioStream = NULL;

    m_streamLength = 0;

    m_this = reinterpret_cast<thandle_t>(this);
};

TiffStream::~TiffStream()
{
    if (m_tif != NULL)
        TIFFClose(m_tif);
}

TIFF *TiffStream::makeFileStream(istream *str)
{
    m_inStream = str;
    m_outStream = NULL;
    m_ioStream = NULL;
    m_streamLength = getSize(m_this);

    m_tif = TIFFClientOpen(m_name, "r", m_this, read, write, seek, close, size,
                           map, unmap);
    return m_tif;
}

TIFF *TiffStream::makeFileStream(ostream *str)
{
    m_inStream = NULL;
    m_outStream = str;
    m_ioStream = NULL;
    m_streamLength = getSize(m_this);

    m_tif = TIFFClientOpen(m_name, "w", m_this, read, write, seek, close, size,
                           map, unmap);
    return m_tif;
}

TIFF *TiffStream::makeFileStream(iostream *str)
{
    m_inStream = NULL;
    m_outStream = NULL;
    m_ioStream = str;
    m_streamLength = getSize(m_this);

    m_tif = TIFFClientOpen(m_name, "r+w", m_this, read, write, seek, close,
                           size, map, unmap);
    return m_tif;
}

tsize_t TiffStream::read(thandle_t fd, tdata_t buf, tsize_t size)
{
    istream *istr;
    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    if (ts->m_inStream != NULL)
    {
        istr = ts->m_inStream;
    }
    else if (ts->m_ioStream != NULL)
    {
        istr = ts->m_ioStream;
    }

    int remain = ts->m_streamLength - ts->tell(fd);
    int actual = remain < size ? remain : size;
    istr->read(reinterpret_cast<char *>(buf), actual);
    return istr->gcount();
}

tsize_t TiffStream::write(thandle_t fd, tdata_t buf, tsize_t size)
{
    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    ostream *ostr;
    if (ts->m_outStream != NULL)
    {
        ostr = ts->m_outStream;
    }
    else if (ts->m_ioStream != NULL)
    {
        ostr = ts->m_ioStream;
    }

    streampos start = ostr->tellp();
    ostr->write(reinterpret_cast<const char *>(buf), size);
    return ostr->tellp() - start;
}

toff_t TiffStream::seek(thandle_t fd, toff_t offset, int origin)
{
    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    if (ts->seekInt(fd, offset, origin) == true)
        return offset;
    else
        return -1;
}

int TiffStream::close(thandle_t fd)
{
    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    if (ts->m_inStream != NULL)
    {
        ts->m_inStream = NULL;
        return 0;
    }
    else if (ts->m_outStream != NULL)
    {
        ts->m_outStream = NULL;
        return 0;
    }
    else if (ts->m_ioStream != NULL)
    {
        ts->m_ioStream = NULL;
        return 0;
    }
    return -1;
}

toff_t TiffStream::size(thandle_t fd)
{
    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    return ts->getSize(fd);
}

int TiffStream::map(thandle_t fd, tdata_t *phase, toff_t *psize) { return 0; }

void TiffStream::unmap(thandle_t fd, tdata_t base, tsize_t size) {}

unsigned int TiffStream::getSize(thandle_t fd)
{
    if (!isOpen(fd))
        return 0;

    unsigned int pos = tell(fd);
    seekInt(fd, 0, end);
    unsigned int size = tell(fd);
    seekInt(fd, pos, beg);

    return size;
}

unsigned int TiffStream::tell(thandle_t fd)
{
    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    if (ts->m_inStream != NULL)
    {
        return ts->m_inStream->tellg();
    }
    else if (ts->m_outStream != NULL)
    {
        return ts->m_outStream->tellp();
    }
    else if (ts->m_ioStream != NULL)
    {
        return ts->m_ioStream->tellg();
    }
    return 0;
}

bool TiffStream::seekInt(thandle_t fd, unsigned int offset, int origin)
{
    if (!isOpen(fd))
        return false;

    ios::seek_dir org;
    switch (origin)
    {
        case beg:
            org = ios::beg;
            break;
        case cur:
            org = ios::cur;
            break;
        case end:
            org = ios::end;
            break;
    }

    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    if (ts->m_inStream != NULL)
    {
        ts->m_inStream->seekg(offset, org);
        return true;
    }
    else if (ts->m_outStream != NULL)
    {
        ts->m_outStream->seekp(offset, org);
        return true;
    }
    else if (ts->m_ioStream != NULL)
    {
        ts->m_ioStream->seekg(offset, org);
        ts->m_ioStream->seekp(offset, org);
        return true;
    }
    return false;
}

bool TiffStream::isOpen(thandle_t fd)
{
    TiffStream *ts = reinterpret_cast<TiffStream *>(fd);
    return (ts->m_inStream != NULL || ts->m_outStream != NULL ||
            ts->m_ioStream != NULL);
}
