/*
  Copyright 2008 Larry Gritz and the other authors and contributors.
  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 the software's owners 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.

  (This is the Modified BSD License)
*/

#include <cstdio>
#include <cstdlib>
#include <cmath>
#include <cstdarg>
#include <iostream>
#include <vector>

#include "OpenImageIO/dassert.h"
#include "OpenImageIO/typedesc.h"
#include "OpenImageIO/filesystem.h"
#include "OpenImageIO/plugin.h"
#include "OpenImageIO/thread.h"
#include "OpenImageIO/strutil.h"

#include "OpenImageIO/imageio.h"
#include "imageio_pvt.h"

#include <boost/scoped_array.hpp>


OIIO_NAMESPACE_ENTER
{
    using namespace pvt;

bool
ImageOutput::write_scanline (int y, int z, TypeDesc format,
                             const void *data, stride_t xstride)
{
    // Default implementation: don't know how to write scanlines
    return false;
}



bool
ImageOutput::write_scanlines (int ybegin, int yend, int z,
                              TypeDesc format, const void *data,
                              stride_t xstride, stride_t ystride)
{
    // Default implementation: write each scanline individually
    stride_t native_pixel_bytes = (stride_t) m_spec.pixel_bytes (true);
    if (format == TypeDesc::UNKNOWN && xstride == AutoStride)
        xstride = native_pixel_bytes;
    stride_t zstride = AutoStride;
    m_spec.auto_stride (xstride, ystride, zstride, format, m_spec.nchannels,
                        m_spec.width, yend-ybegin);
    bool ok = true;
    for (int y = ybegin;  ok && y < yend;  ++y) {
        ok &= write_scanline (y, z, format, data, xstride);
        data = (char *)data + ystride;
    }
    return ok;
}



bool
ImageOutput::write_tile (int x, int y, int z, TypeDesc format,
                         const void *data, stride_t xstride,
                         stride_t ystride,
                         stride_t zstride)
{
    // Default implementation: don't know how to write tiles
    return false;
}



bool ImageOutput::write_tiles (int xbegin, int xend, int ybegin, int yend,
                               int zbegin, int zend, TypeDesc format,
                               const void *data, stride_t xstride,
                               stride_t ystride, stride_t zstride)
{
    if (! m_spec.valid_tile_range (xbegin, xend, ybegin, yend, zbegin, zend))
        return false;

    // Default implementation: write each tile individually
    stride_t native_pixel_bytes = (stride_t) m_spec.pixel_bytes (true);
    if (format == TypeDesc::UNKNOWN && xstride == AutoStride)
        xstride = native_pixel_bytes;
    m_spec.auto_stride (xstride, ystride, zstride, format, m_spec.nchannels,
                        xend-xbegin, yend-ybegin);

    bool ok = true;
    stride_t pixelsize = format.size() * m_spec.nchannels;
    boost::scoped_array<char> buf;
    for (int z = zbegin;  z < zend;  z += std::max(1,m_spec.tile_depth)) {
        int zd = std::min (zend-z, m_spec.tile_depth);
        for (int y = ybegin;  y < yend;  y += m_spec.tile_height) {
            char *tilestart = ((char *)data + (z-zbegin)*zstride
                               + (y-ybegin)*ystride);
            int yh = std::min (yend-y, m_spec.tile_height);
            for (int x = xbegin;  ok && x < xend;  x += m_spec.tile_width) {
                int xw = std::min (xend-x, m_spec.tile_width);
                // Full tiles are written directly into the user buffer, but
                // Partial tiles (such as at the image edge) are copied into
                // a padded buffer to stage them.
                if (xw == m_spec.tile_width && yh == m_spec.tile_height &&
                    zd == m_spec.tile_depth) {
                    ok &= write_tile (x, y, z, format, tilestart,
                                     xstride, ystride, zstride);
                } else {
                    if (! buf.get())
                        buf.reset (new char [pixelsize * m_spec.tile_pixels()]);
                    OIIO::copy_image (m_spec.nchannels, xw, yh, zd,
                                tilestart, pixelsize, xstride, ystride, zstride,
                                &buf[0], pixelsize, pixelsize*m_spec.tile_width,
                                pixelsize*m_spec.tile_pixels());
                    ok &= write_tile (x, y, z, format, &buf[0],
                                      pixelsize, pixelsize*m_spec.tile_width,
                                      pixelsize*m_spec.tile_pixels());
                }
                tilestart += m_spec.tile_width * xstride;
            }
        }
    }
    return ok;
}



bool
ImageOutput::write_rectangle (int xbegin, int xend, int ybegin, int yend,
                              int zbegin, int zend, TypeDesc format,
                              const void *data, stride_t xstride,
                              stride_t ystride, stride_t zstride)
{
    return false;
}



bool
ImageOutput::write_deep_scanlines (int ybegin, int yend, int z,
                                   const DeepData &deepdata)
{
    return false;  // default: doesn't support deep images
}



bool
ImageOutput::write_deep_tiles (int xbegin, int xend, int ybegin, int yend,
                               int zbegin, int zend,
                               const DeepData &deepdata)
{
    return false;  // default: doesn't support deep images
}



bool
ImageOutput::write_deep_image (const DeepData &deepdata)
{
    if (m_spec.depth > 1) {
        error ("write_deep_image is not supported for volume (3D) images.");
        return false;
        // FIXME? - not implementing 3D deep images for now.  The only
        // format that supports deep images at this time is OpenEXR, and
        // it doesn't support volumes.
    }
    if (m_spec.tile_width) {
        // Tiled image
        return write_deep_tiles (m_spec.x, m_spec.x+m_spec.width,
                                 m_spec.y, m_spec.y+m_spec.height,
                                 m_spec.z, m_spec.z+m_spec.depth,
                                 deepdata);
    } else {
        // Scanline image
        return write_deep_scanlines (m_spec.y, m_spec.y+m_spec.height, 0,
                                     deepdata);
    }
}



int
ImageOutput::send_to_output (const char *format, ...)
{
    // FIXME -- I can't remember how this is supposed to work
    return 0;
}



int
ImageOutput::send_to_client (const char *format, ...)
{
    // FIXME -- I can't remember how this is supposed to work
    return 0;
}



void
ImageOutput::append_error (const std::string& message) const
{
    ASSERT (m_errmessage.size() < 1024*1024*16 &&
            "Accumulated error messages > 16MB. Try checking return codes!");
    if (m_errmessage.size())
        m_errmessage += '\n';
    m_errmessage += message;
}



const void *
ImageOutput::to_native_scanline (TypeDesc format,
                                 const void *data, stride_t xstride,
                                 std::vector<unsigned char> &scratch,
                                 unsigned int dither,
                                 int yorigin, int zorigin)
{
    return to_native_rectangle (0, m_spec.width, 0, 1, 0, 1, format, data,
                                xstride, 0, 0, scratch, dither,
                                m_spec.x, yorigin, zorigin);
}



const void *
ImageOutput::to_native_tile (TypeDesc format, const void *data,
                             stride_t xstride, stride_t ystride, stride_t zstride,
                             std::vector<unsigned char> &scratch,
                             unsigned int dither,
                             int xorigin, int yorigin, int zorigin)
{
    return to_native_rectangle (0, m_spec.tile_width, 0, m_spec.tile_height,
                                0, std::max(1,m_spec.tile_depth), format, data,
                                xstride, ystride, zstride, scratch,
                                dither, xorigin, yorigin, zorigin);
}



const void *
ImageOutput::to_native_rectangle (int xbegin, int xend, int ybegin, int yend,
                                  int zbegin, int zend,
                                  TypeDesc format, const void *data,
                                  stride_t xstride, stride_t ystride, stride_t zstride,
                                  std::vector<unsigned char> &scratch,
                                  unsigned int dither,
                                  int xorigin, int yorigin, int zorigin)
{
    // native_pixel_bytes is the size of a pixel in the FILE, including
    // the per-channel format, if specified when the file was opened.
    stride_t native_pixel_bytes = (stride_t) m_spec.pixel_bytes (true);
    // perchanfile is true if the file has different per-channel formats
    bool perchanfile = m_spec.channelformats.size() && supports("channelformats");
    // It's an error to pass per-channel data formats to a writer that
    // doesn't support it.
    if (m_spec.channelformats.size() && !perchanfile)
        return NULL;
    // native_data is true if the user is passing data in the native format
    bool native_data = (format == TypeDesc::UNKNOWN ||
                        (format == m_spec.format && !perchanfile));
    // If the user is passing native data and they've left xstride set
    // to Auto, then we know it's the native pixel size.
    if (native_data && xstride == AutoStride)
        xstride = native_pixel_bytes;
    // Fill in the rest of the strides that haven't been set.
    m_spec.auto_stride (xstride, ystride, zstride, format,
                        m_spec.nchannels, xend-xbegin, yend-ybegin);

    // Compute width and height from the rectangle extents
    int width = xend - xbegin;
    int height = yend - ybegin;
    int depth = zend - zbegin;

    // Do the strides indicate that the data area is contiguous?
    bool contiguous;
    if (native_data) {
        // If it's native data, it had better be contiguous by the
        // file's definition.
        contiguous = (xstride == (stride_t)(m_spec.pixel_bytes(native_data)));
    } else {
        // If it's not native data, we only care if the user's buffer
        // is contiguous.
        contiguous = (xstride == (stride_t)(format.size()*m_spec.nchannels));
    }
    contiguous &= ((ystride == xstride*width || height == 1) &&
                   (zstride == ystride*height || depth == 1));

    if (native_data && contiguous) {
        // Data are already in the native format and contiguous
        // just return a ptr to the original data.
        return data;
    }

    imagesize_t rectangle_pixels = width * height * depth;
    imagesize_t rectangle_values = rectangle_pixels * m_spec.nchannels;
    imagesize_t rectangle_bytes = rectangle_pixels * native_pixel_bytes;

    // Cases to handle:
    // 1. File has per-channel data, user passes native data -- this has
    //    already returned above, since the data didn't need munging.
    // 2. File has per-channel data, user passes some other data type
    // 3. File has uniform data, user passes some other data type
    // 4. File has uniform data, user passes the right data -- note that
    //    this case already returned if the user data was contiguous

    // Handle the per-channel format case (#2) where the user is passing
    // a non-native buffer.
    if (perchanfile) {
        if (native_data) {
            ASSERT (contiguous && "Per-channel native output requires contiguous strides");
        }
        ASSERT (format != TypeDesc::UNKNOWN);
        ASSERT (m_spec.channelformats.size() == (size_t)m_spec.nchannels);
        scratch.resize (rectangle_bytes);
        size_t offset = 0;
        for (int c = 0;  c < m_spec.nchannels;  ++c) {
            TypeDesc chanformat = m_spec.channelformats[c];
            convert_image (1 /* channels */, width, height, depth,
                           (char *)data + c*format.size(), format,
                           xstride, ystride, zstride, 
                           &scratch[offset], chanformat,
                           native_pixel_bytes, AutoStride, AutoStride,
                           c == m_spec.alpha_channel ? 0 : -1,
                           c == m_spec.z_channel ? 0 : -1);
            offset += chanformat.size ();
        }
        return &scratch[0];
    }

    // The remaining code is where all channels in the file have the
    // same data type, which may or may not be what the user passed in
    // (cases #3 and #4 above).
    imagesize_t contiguoussize = contiguous ? 0 : rectangle_values * native_pixel_bytes;
    contiguoussize = (contiguoussize+3) & (~3); // Round up to 4-byte boundary
    DASSERT ((contiguoussize & 3) == 0);
    imagesize_t floatsize = rectangle_values * sizeof(float);
    scratch.resize (contiguoussize + floatsize + rectangle_bytes);

    // Force contiguity if not already present
    if (! contiguous) {
        data = contiguize (data, m_spec.nchannels, xstride, ystride, zstride,
                           (void *)&scratch[0], width, height, depth, format);
    }

    // Rather than implement the entire cross-product of possible
    // conversions, use float as an intermediate format, which generally
    // will always preserve enough precision.
    const float *buf;
    if (format == TypeDesc::FLOAT) {
        // Already in float format -- leave it as-is.
        buf = (float *)data;
    } else {
        // Convert from 'format' to float.
        buf = convert_to_float (data, (float *)&scratch[contiguoussize],
                                rectangle_values, format);
    }

    if (dither && format.is_floating_point() &&
            m_spec.format.basetype == TypeDesc::UINT8) {
        float *ditherarea = (float *)&scratch[contiguoussize];
        stride_t pixelsize = m_spec.nchannels * sizeof(float);
        if (buf != ditherarea) {
            // Need to make a copy for dither so we don't destroy user's data.
            OIIO::copy_image (m_spec.nchannels, width, height, depth,
                              buf, pixelsize, pixelsize, pixelsize*width,
                              pixelsize*width*height, ditherarea,
                              pixelsize, pixelsize*width, pixelsize*width*height);
            buf = ditherarea;
        }
        OIIO::add_dither (m_spec.nchannels, width, height, depth, ditherarea,
                          pixelsize, pixelsize*width, pixelsize*width*height,
                          1.0f/255.0f, m_spec.alpha_channel, m_spec.z_channel,
                          dither, 0, xorigin, yorigin, zorigin);
    }

    // Convert from float to native format.
    return parallel_convert_from_float (buf, &scratch[contiguoussize+floatsize], 
                                        rectangle_values, m_spec.format);
}



bool
ImageOutput::write_image (TypeDesc format, const void *data,
                          stride_t xstride, stride_t ystride, stride_t zstride,
                          ProgressCallback progress_callback,
                          void *progress_callback_data)
{
    bool native = (format == TypeDesc::UNKNOWN);
    stride_t pixel_bytes = native ? (stride_t) m_spec.pixel_bytes (native)
                                  : format.size() * m_spec.nchannels;
    if (xstride == AutoStride)
        xstride = pixel_bytes;
    m_spec.auto_stride (xstride, ystride, zstride, format,
                        m_spec.nchannels, m_spec.width, m_spec.height);

    if (supports ("rectangles")) {
        // Use a rectangle if we can
        return write_rectangle (0, m_spec.width, 0, m_spec.height, 0, m_spec.depth,
                                format, data, xstride, ystride, zstride);
    }

    bool ok = true;
    if (progress_callback && progress_callback (progress_callback_data, 0.0f))
        return ok;
    if (m_spec.tile_width && supports ("tiles")) {
        // Tiled image
        for (int z = 0;  z < m_spec.depth;  z += m_spec.tile_depth) {
            int zend = std::min (z+m_spec.z+m_spec.tile_depth,
                                 m_spec.z+m_spec.depth);
            for (int y = 0;  y < m_spec.height;  y += m_spec.tile_height) {
                int yend = std::min (y+m_spec.y+m_spec.tile_height,
                                     m_spec.y+m_spec.height);
                const char *d = (const char *)data + z*zstride + y*ystride;
                ok &= write_tiles (m_spec.x, m_spec.x+m_spec.width,
                                   y+m_spec.y, yend, z+m_spec.z, zend,
                                   format, d, xstride, ystride, zstride);
                if (progress_callback &&
                    progress_callback (progress_callback_data,
                                       (float)(z*m_spec.height+y)/(m_spec.height*m_spec.depth)))
                    return ok;
            }
        }
    } else {
        // Scanline image
        const int chunk = 256;
        for (int z = 0;  z < m_spec.depth;  ++z)
            for (int y = 0;  y < m_spec.height && ok;  y += chunk) {
                int yend = std::min (y+m_spec.y+chunk, m_spec.y+m_spec.height);
                const char *d = (const char *)data + z*zstride + y*ystride;
                ok &= write_scanlines (y+m_spec.y, yend, z+m_spec.z,
                                       format, d, xstride, ystride);
                if (progress_callback &&
                    progress_callback (progress_callback_data,
                                       (float)(z*m_spec.height+y)/(m_spec.height*m_spec.depth)))
                    return ok;
            }
    }
    if (progress_callback)
        progress_callback (progress_callback_data, 1.0f);

    return ok;
}



bool
ImageOutput::copy_image (ImageInput *in)
{
    if (! in) {
        error ("copy_image: no input supplied");
        return false;
    }

    // Make sure the images are compatible in size
    const ImageSpec &inspec (in->spec());
    if (inspec.width != spec().width || inspec.height != spec().height || 
        inspec.depth != spec().depth || inspec.nchannels != spec().nchannels) {
        error ("Could not copy %d x %d x %d channels to %d x %d x %d channels",
               inspec.width, inspec.height, inspec.nchannels,
               spec().width, spec().height, spec().nchannels);
        return false;
    }

    // in most cases plugins don't allow to copy 0x0 images
    // but there are some exceptions (like in FITS plugin)
    // when we want to do this. Because 0x0 means there is no image
    // data in the file, we simply return true so the application thought
    // that everything went right.
    if (! spec().image_bytes())
        return true;

    if (spec().deep) {
        // Special case for ''deep'' images
        DeepData deepdata;
        bool ok = in->read_native_deep_image (deepdata);
        if (ok)
            ok = write_deep_image (deepdata);
        else
            error ("%s", in->geterror());  // copy err from in to out
        return ok;
    }

    // Naive implementation -- read the whole image and write it back out.
    // FIXME -- a smarter implementation would read scanlines or tiles at
    // a time, to minimize mem footprint.
    bool native = supports("channelformats") && inspec.channelformats.size();
    TypeDesc format = native ? TypeDesc::UNKNOWN : inspec.format;
    boost::scoped_array<char> pixels (new char [inspec.image_bytes(native)]);
    bool ok = in->read_image (format, &pixels[0]);
    if (ok)
        ok = write_image (format, &pixels[0]);
    else
        error ("%s", in->geterror());  // copy err from in to out
    return ok;
}



bool
ImageOutput::copy_tile_to_image_buffer (int x, int y, int z, TypeDesc format,
                                        const void *data, stride_t xstride,
                                        stride_t ystride, stride_t zstride,
                                        void *image_buffer)
{
    if (! m_spec.tile_width || ! m_spec.tile_height) {
        error ("Called write_tile for non-tiled image.");
        return false;
    }

    const ImageSpec &spec (this->spec());
    spec.auto_stride (xstride, ystride, zstride, format, spec.nchannels,
                        spec.tile_width, spec.tile_height);
    stride_t buf_xstride = spec.pixel_bytes();
    stride_t buf_ystride = spec.scanline_bytes();
    stride_t buf_zstride = spec.scanline_bytes() * spec.height;
    stride_t offset = (x-spec.x)*buf_xstride 
                    + (y-spec.y)*buf_ystride
                    + (z-spec.z)*buf_zstride;
    int xend = std::min (x+spec.tile_width,  spec.x+spec.width);
    int yend = std::min (y+spec.tile_height, spec.y+spec.height);
    int zend = std::min (z+spec.tile_depth,  spec.z+spec.depth);
    int width = xend-x, height = yend-y, depth = zend-z;

    // Add dither if requested -- requires making a temporary staging area
    boost::scoped_array<float> ditherarea;
    unsigned int dither = spec.get_int_attribute ("oiio:dither", 0);
    if (dither && format.is_floating_point() &&
            spec.format.basetype == TypeDesc::UINT8) {
        stride_t pixelsize = spec.nchannels * sizeof(float);
        ditherarea.reset (new float [pixelsize * spec.tile_pixels()]);
        OIIO::convert_image (spec.nchannels, width, height, depth,
                             data, format, xstride, ystride, zstride,
                             ditherarea.get(), TypeDesc::FLOAT,
                             pixelsize, pixelsize*spec.tile_width,
                             pixelsize*spec.tile_width*spec.tile_height);
        data = ditherarea.get();
        format = TypeDesc::FLOAT;
        xstride = pixelsize;
        ystride = xstride * spec.tile_width;
        zstride = ystride * spec.tile_height;
        OIIO::add_dither (spec.nchannels, width, height, depth, (float *)data,
                          pixelsize, pixelsize*width, pixelsize*width*height,
                          1.0f/255.0f, spec.alpha_channel, spec.z_channel,
                          dither, 0, x, y, z);
    }

    return OIIO::convert_image (spec.nchannels, width, height, depth,
                                data, format, xstride, ystride, zstride,
                                (char *)image_buffer + offset, spec.format,
                                buf_xstride, buf_ystride, buf_zstride);
}


}
OIIO_NAMESPACE_EXIT
