///////////////////////////////////////////////////////////////////////////
//
// Copyright (c) 2004, Industrial Light & Magic, a division of Lucas
// Digital Ltd. LLC
// 
// All rights reserved.
// 
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
// *       Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// *       Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// *       Neither the name of Industrial Light & Magic nor the names of
// its contributors may be used to endorse or promote products derived
// from this software without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
///////////////////////////////////////////////////////////////////////////


//-----------------------------------------------------------------------------
//
//	class TileOffsets
//
//-----------------------------------------------------------------------------

#include <ImfTileOffsets.h>
#include <ImfXdr.h>
#include <ImfIO.h>
#include <Iex.h>

namespace Imf {


TileOffsets::TileOffsets (LevelMode mode,
			  int numXLevels, int numYLevels,
			  const int *numXTiles, const int *numYTiles)
:
    _mode (mode),
    _numXLevels (numXLevels),
    _numYLevels (numYLevels)
{
    switch (_mode)
    {
      case ONE_LEVEL:
      case MIPMAP_LEVELS:

        _offsets.resize (_numXLevels);

        for (unsigned int l = 0; l < _offsets.size(); ++l)
        {
            _offsets[l].resize (numYTiles[l]);

            for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
	    {
                _offsets[l][dy].resize (numXTiles[l]);
            }
        }
        break;

      case RIPMAP_LEVELS:

        _offsets.resize (_numXLevels * _numYLevels);

        for (unsigned int ly = 0; ly < _numYLevels; ++ly)
        {
            for (unsigned int lx = 0; lx < _numXLevels; ++lx)
            {
                int l = ly * _numXLevels + lx;
                _offsets[l].resize (numYTiles[ly]);

                for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
                {
                    _offsets[l][dy].resize (numXTiles[lx]);
                }
            }
        }
        break;
    }
}


bool
TileOffsets::anyOffsetsAreInvalid () const
{
    for (unsigned int l = 0; l < _offsets.size(); ++l)
	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
		if (_offsets[l][dy][dx] <= 0)
		    return true;
    
    return false;
}


void
TileOffsets::findTiles (IStream &is)
{
    for (unsigned int l = 0; l < _offsets.size(); ++l)
    {
	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
	{
	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
	    {
		Int64 tileOffset = is.tellg();

		int tileX;
		Xdr::read <StreamIO> (is, tileX);

		int tileY;
		Xdr::read <StreamIO> (is, tileY);

		int levelX;
		Xdr::read <StreamIO> (is, levelX);

		int levelY;
		Xdr::read <StreamIO> (is, levelY);

		int dataSize;
		Xdr::read <StreamIO> (is, dataSize);

		Xdr::skip <StreamIO> (is, dataSize);

		if (!isValidTile(tileX, tileY, levelX, levelY))
		    return;

		operator () (tileX, tileY, levelX, levelY) = tileOffset;
	    }
	}
    }
}


void
TileOffsets::reconstructFromFile (IStream &is)
{
    //
    // Try to reconstruct a missing tile offset table by sequentially
    // scanning through the file, and recording the offsets in the file
    // of the tiles we find.
    //

    Int64 position = is.tellg();

    try
    {
	findTiles (is);
    }
    catch (...)
    {
        //
        // Suppress all exceptions.  This function is called only to
	// reconstruct the tile offset table for incomplete files,
	// and exceptions are likely.
        //
    }

    is.clear();
    is.seekg (position);
}


void
TileOffsets::readFrom (IStream &is)
{
    //
    // Read in the tile offsets from the file's tile offset table
    //

    for (unsigned int l = 0; l < _offsets.size(); ++l)
	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
		Xdr::read <StreamIO> (is, _offsets[l][dy][dx]);

    //
    // Check if any tile offsets are invalid.
    //
    // Invalid offsets mean that the file is probably incomplete
    // (the offset table is the last thing written to the file).
    // Either some process is still busy writing the file, or
    // writing the file was aborted.
    //
    // We should still be able to read the existing parts of the
    // file.  In order to do this, we have to make a sequential
    // scan over the scan tile to reconstruct the tile offset
    // table.
    //

    if (anyOffsetsAreInvalid())
	reconstructFromFile (is);
}


Int64
TileOffsets::writeTo (OStream &os) const
{
    //
    // Write the tile offset table to the file, and
    // return the position of the start of the table
    // in the file.
    //
    
    Int64 pos = os.tellp();

    if (pos == -1)
	Iex::throwErrnoExc ("Cannot determine current file position (%T).");

    for (unsigned int l = 0; l < _offsets.size(); ++l)
	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
		Xdr::write <StreamIO> (os, _offsets[l][dy][dx]);

    return pos;
}


bool
TileOffsets::isEmpty () const
{
    for (unsigned int l = 0; l < _offsets.size(); ++l)
	for (unsigned int dy = 0; dy < _offsets[l].size(); ++dy)
	    for (unsigned int dx = 0; dx < _offsets[l][dy].size(); ++dx)
		if (_offsets[l][dy][dx] != 0)
		    return false;
    return true;
}


bool
TileOffsets::isValidTile (int dx, int dy, int lx, int ly) const
{
    switch (_mode)
    {
      case ONE_LEVEL:

        if (lx == 0 &&
	    ly == 0 &&
	    _offsets.size() > 0 &&
            _offsets[0].size() > dy &&
            _offsets[0][dy].size() > dx)
	{
            return true;
	}

        break;

      case MIPMAP_LEVELS:

        if (lx < _numXLevels &&
	    ly < _numYLevels &&
            _offsets.size() > lx &&
            _offsets[lx].size() > dy &&
            _offsets[lx][dy].size() > dx)
	{
            return true;
	}

        break;

      case RIPMAP_LEVELS:

        if (lx < _numXLevels &&
	    ly < _numYLevels &&
            _offsets.size() > lx + ly * _numXLevels &&
            _offsets[lx + ly * _numXLevels].size() > dy &&
            _offsets[lx + ly * _numXLevels][dy].size() > dx)
	{
            return true;
	}

        break;

      default:

        return false;
    }
    
    return false;
}


Int64 &
TileOffsets::operator () (int dx, int dy, int lx, int ly)
{
    //
    // Looks up the value of the tile with tile coordinate (dx, dy)
    // and level number (lx, ly) in the _offsets array, and returns
    // the cooresponding offset.
    //

    switch (_mode)
    {
      case ONE_LEVEL:

        return _offsets[0][dy][dx];
        break;

      case MIPMAP_LEVELS:

        return _offsets[lx][dy][dx];
        break;

      case RIPMAP_LEVELS:

        return _offsets[lx + ly * _numXLevels][dy][dx];
        break;

      default:

        throw Iex::ArgExc ("Unknown LevelMode format.");
    }
}


Int64 &
TileOffsets::operator () (int dx, int dy, int l)
{
    return operator () (dx, dy, l, l);
}


const Int64 &
TileOffsets::operator () (int dx, int dy, int lx, int ly) const
{
    //
    // Looks up the value of the tile with tile coordinate (dx, dy)
    // and level number (lx, ly) in the _offsets array, and returns
    // the cooresponding offset.
    //

    switch (_mode)
    {
      case ONE_LEVEL:

        return _offsets[0][dy][dx];
        break;

      case MIPMAP_LEVELS:

        return _offsets[lx][dy][dx];
        break;

      case RIPMAP_LEVELS:

        return _offsets[lx + ly * _numXLevels][dy][dx];
        break;

      default:

        throw Iex::ArgExc ("Unknown LevelMode format.");
    }
}


const Int64 &
TileOffsets::operator () (int dx, int dy, int l) const
{
    return operator () (dx, dy, l, l);
}


} // namespace Imf
