/*
 * Copyright (c) 1988-1996 Sam Leffler
 * Copyright (c) 1991-1996 Silicon Graphics, Inc.
 *
 * Permission to use, copy, modify, distribute, and sell this software and
 * its documentation for any purpose is hereby granted without fee, provided
 * that (i) the above copyright notices and this permission notice appear in
 * all copies of the software and related documentation, and (ii) the names of
 * Sam Leffler and Silicon Graphics may not be used in any advertising or
 * publicity relating to the software without the specific, prior written
 * permission of Sam Leffler and Silicon Graphics.
 *
 * THE SOFTWARE IS PROVIDED "AS-IS" AND WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS, IMPLIED OR OTHERWISE, INCLUDING WITHOUT LIMITATION, ANY
 * WARRANTY OF MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE.
 *
 * IN NO EVENT SHALL SAM LEFFLER OR SILICON GRAPHICS BE LIABLE FOR
 * ANY SPECIAL, INCIDENTAL, INDIRECT OR CONSEQUENTIAL DAMAGES OF ANY KIND,
 * OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
 * WHETHER OR NOT ADVISED OF THE POSSIBILITY OF DAMAGE, AND ON ANY THEORY OF
 * LIABILITY, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */

/*
 * TIFF Library UNIX-specific Routines.
 */
#include "tiffiop.h"
#include <iostream>

using namespace std;

/*
  ISO C++ uses a 'std::streamsize' type to define counts.  This makes
  it similar to, (but perhaps not the same as) size_t.

  The std::ios::pos_type is used to represent stream positions as used
  by tellg(), tellp(), seekg(), and seekp().  This makes it similar to
  (but perhaps not the same as) 'off_t'.  The std::ios::streampos type
  is used for character streams, but is documented to not be an
  integral type anymore, so it should *not* be assigned to an integral
  type.

  The std::ios::off_type is used to specify relative offsets needed by
  the variants of seekg() and seekp() which accept a relative offset
  argument.

  Useful prototype knowledge:

  Obtain read position
    ios::pos_type basic_istream::tellg()

  Set read position
    basic_istream& basic_istream::seekg(ios::pos_type)
    basic_istream& basic_istream::seekg(ios::off_type, ios_base::seekdir)

  Read data
    basic_istream& istream::read(char *str, streamsize count)

  Number of characters read in last unformatted read
    streamsize istream::gcount();

  Obtain write position
    ios::pos_type basic_ostream::tellp()

  Set write position
    basic_ostream& basic_ostream::seekp(ios::pos_type)
    basic_ostream& basic_ostream::seekp(ios::off_type, ios_base::seekdir)

  Write data
    basic_ostream& ostream::write(const char *str, streamsize count)
*/

struct tiffis_data;
struct tiffos_data;

extern "C"
{

    static tmsize_t _tiffosReadProc(thandle_t, void *, tmsize_t);
    static tmsize_t _tiffisReadProc(thandle_t fd, void *buf, tmsize_t size);
    static tmsize_t _tiffosWriteProc(thandle_t fd, void *buf, tmsize_t size);
    static tmsize_t _tiffisWriteProc(thandle_t, void *, tmsize_t);
    static uint64_t _tiffosSeekProc(thandle_t fd, uint64_t off, int whence);
    static uint64_t _tiffisSeekProc(thandle_t fd, uint64_t off, int whence);
    static uint64_t _tiffosSizeProc(thandle_t fd);
    static uint64_t _tiffisSizeProc(thandle_t fd);
    static int _tiffosCloseProc(thandle_t fd);
    static int _tiffisCloseProc(thandle_t fd);
    static int _tiffDummyMapProc(thandle_t, void **base, toff_t *size);
    static void _tiffDummyUnmapProc(thandle_t, void *base, toff_t size);
    static TIFF *_tiffStreamOpen(const char *name, const char *mode, void *fd);

    struct tiffis_data
    {
        istream *stream;
        ios::pos_type start_pos;
    };

    struct tiffos_data
    {
        ostream *stream;
        ios::pos_type start_pos;
    };

    static tmsize_t _tiffosReadProc(thandle_t, void *, tmsize_t) { return 0; }

    static tmsize_t _tiffisReadProc(thandle_t fd, void *buf, tmsize_t size)
    {
        tiffis_data *data = reinterpret_cast<tiffis_data *>(fd);

        // Verify that type does not overflow.
        streamsize request_size = size;
        if (static_cast<tmsize_t>(request_size) != size)
            return static_cast<tmsize_t>(-1);

        data->stream->read((char *)buf, request_size);

        return static_cast<tmsize_t>(data->stream->gcount());
    }

    static tmsize_t _tiffosWriteProc(thandle_t fd, void *buf, tmsize_t size)
    {
        tiffos_data *data = reinterpret_cast<tiffos_data *>(fd);
        ostream *os = data->stream;
        ios::pos_type pos = os->tellp();

        // Verify that type does not overflow.
        streamsize request_size = size;
        if (static_cast<tmsize_t>(request_size) != size)
            return static_cast<tmsize_t>(-1);

        os->write(reinterpret_cast<const char *>(buf), request_size);

        return static_cast<tmsize_t>(os->tellp() - pos);
    }

    static tmsize_t _tiffisWriteProc(thandle_t, void *, tmsize_t) { return 0; }

    static uint64_t _tiffosSeekProc(thandle_t fd, uint64_t off, int whence)
    {
        tiffos_data *data = reinterpret_cast<tiffos_data *>(fd);
        ostream *os = data->stream;

        // if the stream has already failed, don't do anything
        if (os->fail())
            return static_cast<uint64_t>(-1);

        switch (whence)
        {
            case SEEK_SET:
            {
                // Compute 64-bit offset
                uint64_t new_offset =
                    static_cast<uint64_t>(data->start_pos) + off;

                // Verify that value does not overflow
                ios::off_type offset = static_cast<ios::off_type>(new_offset);
                if (static_cast<uint64_t>(offset) != new_offset)
                    return static_cast<uint64_t>(-1);

                os->seekp(offset, ios::beg);
                break;
            }
            case SEEK_CUR:
            {
                // Verify that value does not overflow
                ios::off_type offset = static_cast<ios::off_type>(off);
                if (static_cast<uint64_t>(offset) != off)
                    return static_cast<uint64_t>(-1);

                os->seekp(offset, ios::cur);
                break;
            }
            case SEEK_END:
            {
                // Verify that value does not overflow
                ios::off_type offset = static_cast<ios::off_type>(off);
                if (static_cast<uint64_t>(offset) != off)
                    return static_cast<uint64_t>(-1);

                os->seekp(offset, ios::end);
                break;
            }
        }

        // Attempt to workaround problems with seeking past the end of the
        // stream.  ofstream doesn't have a problem with this but
        // ostrstream/ostringstream does. In that situation, add intermediate
        // '\0' characters.
        if (os->fail())
        {
            ios::iostate old_state;
            ios::pos_type origin;

            old_state = os->rdstate();
            // reset the fail bit or else tellp() won't work below
            os->clear(os->rdstate() & ~ios::failbit);
            switch (whence)
            {
                case SEEK_SET:
                default:
                    origin = data->start_pos;
                    break;
                case SEEK_CUR:
                    origin = os->tellp();
                    break;
                case SEEK_END:
                    os->seekp(0, ios::end);
                    origin = os->tellp();
                    break;
            }
            // restore original stream state
            os->clear(old_state);

            // only do something if desired seek position is valid
            if ((static_cast<uint64_t>(origin) + off) >
                static_cast<uint64_t>(data->start_pos))
            {
                uint64_t num_fill;

                // clear the fail bit
                os->clear(os->rdstate() & ~ios::failbit);

                // extend the stream to the expected size
                os->seekp(0, ios::end);
                num_fill = (static_cast<uint64_t>(origin)) + off - os->tellp();
                for (uint64_t i = 0; i < num_fill; i++)
                    os->put('\0');

                // retry the seek
                os->seekp(static_cast<ios::off_type>(
                              static_cast<uint64_t>(origin) + off),
                          ios::beg);
            }
        }

        return static_cast<uint64_t>(os->tellp());
    }

    static uint64_t _tiffisSeekProc(thandle_t fd, uint64_t off, int whence)
    {
        tiffis_data *data = reinterpret_cast<tiffis_data *>(fd);

        switch (whence)
        {
            case SEEK_SET:
            {
                // Compute 64-bit offset
                uint64_t new_offset =
                    static_cast<uint64_t>(data->start_pos) + off;

                // Verify that value does not overflow
                ios::off_type offset = static_cast<ios::off_type>(new_offset);
                if (static_cast<uint64_t>(offset) != new_offset)
                    return static_cast<uint64_t>(-1);

                data->stream->seekg(offset, ios::beg);
                break;
            }
            case SEEK_CUR:
            {
                // Verify that value does not overflow
                ios::off_type offset = static_cast<ios::off_type>(off);
                if (static_cast<uint64_t>(offset) != off)
                    return static_cast<uint64_t>(-1);

                data->stream->seekg(offset, ios::cur);
                break;
            }
            case SEEK_END:
            {
                // Verify that value does not overflow
                ios::off_type offset = static_cast<ios::off_type>(off);
                if (static_cast<uint64_t>(offset) != off)
                    return static_cast<uint64_t>(-1);

                data->stream->seekg(offset, ios::end);
                break;
            }
        }

        return (uint64_t)(data->stream->tellg() - data->start_pos);
    }

    static uint64_t _tiffosSizeProc(thandle_t fd)
    {
        tiffos_data *data = reinterpret_cast<tiffos_data *>(fd);
        ostream *os = data->stream;
        ios::pos_type pos = os->tellp();
        ios::pos_type len;

        os->seekp(0, ios::end);
        len = os->tellp();
        os->seekp(pos);

        return (uint64_t)len;
    }

    static uint64_t _tiffisSizeProc(thandle_t fd)
    {
        tiffis_data *data = reinterpret_cast<tiffis_data *>(fd);
        ios::pos_type pos = data->stream->tellg();
        ios::pos_type len;

        data->stream->seekg(0, ios::end);
        len = data->stream->tellg();
        data->stream->seekg(pos);

        return (uint64_t)len;
    }

    static int _tiffosCloseProc(thandle_t fd)
    {
        // Our stream was not allocated by us, so it shouldn't be closed by us.
        delete reinterpret_cast<tiffos_data *>(fd);
        return 0;
    }

    static int _tiffisCloseProc(thandle_t fd)
    {
        // Our stream was not allocated by us, so it shouldn't be closed by us.
        delete reinterpret_cast<tiffis_data *>(fd);
        return 0;
    }

    static int _tiffDummyMapProc(thandle_t, void **base, toff_t *size)
    {
        (void)base;
        (void)size;
        return (0);
    }

    static void _tiffDummyUnmapProc(thandle_t, void *base, toff_t size)
    {
        (void)base;
        (void)size;
    }

    /*
     * Open a TIFF file descriptor for read/writing.
     */
    static TIFF *_tiffStreamOpen(const char *name, const char *mode, void *fd)
    {
        TIFF *tif;

        if (strchr(mode, 'w'))
        {
            tiffos_data *data = new tiffos_data;
            data->stream = reinterpret_cast<ostream *>(fd);
            data->start_pos = data->stream->tellp();

            // Open for writing.
            tif = TIFFClientOpen(
                name, mode, reinterpret_cast<thandle_t>(data), _tiffosReadProc,
                _tiffosWriteProc, _tiffosSeekProc, _tiffosCloseProc,
                _tiffosSizeProc, _tiffDummyMapProc, _tiffDummyUnmapProc);
            if (!tif)
            {
                delete data;
            }
        }
        else
        {
            tiffis_data *data = new tiffis_data;
            data->stream = reinterpret_cast<istream *>(fd);
            data->start_pos = data->stream->tellg();
            // Open for reading.
            tif = TIFFClientOpen(
                name, mode, reinterpret_cast<thandle_t>(data), _tiffisReadProc,
                _tiffisWriteProc, _tiffisSeekProc, _tiffisCloseProc,
                _tiffisSizeProc, _tiffDummyMapProc, _tiffDummyUnmapProc);
            if (!tif)
            {
                delete data;
            }
        }

        return (tif);
    }

} /* extern "C" */

TIFF *TIFFStreamOpen(const char *name, ostream *os)
{
    // If os is either a ostrstream or ostringstream, and has no data
    // written to it yet, then tellp() will return -1 which will break us.
    // We workaround this by writing out a dummy character and
    // then seek back to the beginning.
    if (!os->fail() && static_cast<int>(os->tellp()) < 0)
    {
        *os << '\0';
        os->seekp(0);
    }

    // NB: We don't support mapped files with streams so add 'm'
    return _tiffStreamOpen(name, "wm", os);
}

TIFF *TIFFStreamOpen(const char *name, istream *is)
{
    // NB: We don't support mapped files with streams so add 'm'
    return _tiffStreamOpen(name, "rm", is);
}
