/*
  Copyright 2015 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 <OpenImageIO/imagebuf.h>
#include <OpenImageIO/imagebufalgo.h>
#include <OpenImageIO/imagecache.h>
#include <OpenImageIO/imageio.h>
#include <OpenImageIO/unittest.h>

#include <iostream>

using namespace OIIO;



// Tests various ways for the subset of channels to be cached in a
// many-channel image.
void
test_get_pixels_cachechannels(int chbegin = 0, int chend = 4,
                              int cache_chbegin = 0, int cache_chend = -1)
{
    std::cout << "\nTesting IC get_pixels of chans [" << chbegin << "," << chend
              << ") with cache range [" << cache_chbegin << "," << cache_chend
              << "):\n";
    ImageCache* imagecache = ImageCache::create(false /*not shared*/);

    // Create a 10 channel file
    ustring filename("tenchannels.tif");
    const int nchans = 10;
    ImageBuf A(ImageSpec(64, 64, nchans, TypeDesc::FLOAT));
    const float pixelvalue[nchans] = { 0.0f, 0.1f, 0.2f, 0.3f, 0.4f,
                                       0.5f, 0.6f, 0.7f, 0.8f, 0.9f };
    ImageBufAlgo::fill(A, pixelvalue);
    A.write(filename);

    // Retrieve 2 pixels of [chbegin,chend), make sure we got the right values
    float p[2 * nchans] = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
                            -1, -1, -1, -1, -1, -1, -1, -1, -1, -1 };
    OIIO_CHECK_ASSERT(imagecache->get_pixels(filename, 0, 0, 0, 2, 0, 1, 0,
                                             1,  // pixel range
                                             chbegin, chend, TypeDesc::FLOAT, p,
                                             AutoStride, AutoStride, AutoStride,
                                             cache_chbegin, cache_chend));
    int nc = chend - chbegin;
    for (int x = 0; x < 2; ++x) {
        for (int c = 0; c < nc; ++c) {
            std::cout << ' ' << p[x * nc + c];
            OIIO_CHECK_EQUAL(p[x * nc + c], pixelvalue[c + chbegin]);
        }
        std::cout << "\n";
    }
    for (int c = 2 * nc; c < 2 * nchans; ++c)
        OIIO_CHECK_EQUAL(p[c], -1.0f);

    ImageCache::destroy(imagecache);
}



// Wimple wrapper to return a raw "null" ImageInput*.
static ImageInput*
NullInputCreator()
{
    // Note: we can't create it directly, but we can ask for a managed
    // pointer and then release the raw pointer from it.
    return ImageInput::create("0.null").release();
}


// Test the ability to add an application buffer to make it appear as if
// it's an image in the cache.
void
test_app_buffer()
{
    ImageCache* imagecache = ImageCache::create(false /*not shared*/);

    // Add a file entry with a "null" ImageInput proxy configured to look
    // like a 2x2 RGB float image.
    ustring fooname("foo");
    const int xres = 2, yres = 2, chans = 3;
    TypeDesc imgtype = TypeDesc::FLOAT;
    ImageSpec config(xres, yres, chans, imgtype);
    config.tile_width  = xres;
    config.tile_height = yres;
    config.attribute("null:force", 1);  // necessary because no .null extension
    bool fadded = imagecache->add_file(fooname, NullInputCreator, &config);
    OIIO_CHECK_ASSERT(fadded);

    // Make sure it got added correctly
    ImageSpec retrieved_spec;
    imagecache->get_imagespec(fooname, retrieved_spec);
    OIIO_CHECK_EQUAL(retrieved_spec.width, xres);
    OIIO_CHECK_EQUAL(retrieved_spec.height, yres);
    OIIO_CHECK_EQUAL(retrieved_spec.format, imgtype);

    // Here's our image of data, in our own buffer.
    static float pixels[yres][xres][chans] = { { { 0, 0, 0 }, { 0, 1, 0 } },
                                               { { 1, 0, 0 }, { 1, 1, 0 } } };
    // Add a proxy tile that points to -- but does not copy -- the image.
    bool ok = imagecache->add_tile(fooname, 0 /* subimage */, 0 /* miplevel */,
                                   0, 0, 0,         /* origin */
                                   0, chans,        /* channel range */
                                   imgtype, pixels, /* the buffer */
                                   AutoStride, AutoStride,
                                   AutoStride, /* strides */
                                   false /* DO NOT COPY THE PIXELS! */);
    OIIO_CHECK_ASSERT(ok);

    // Check that we can retrieve the tile.
    ImageCache::Tile* tile = imagecache->get_tile(fooname, 0, 0, 0, 0, 0);
    OIIO_CHECK_ASSERT(tile != nullptr);
    imagecache->release_tile(tile);  // de-refcount what we got from get_tile

    // Check that the tile's pixels appear to actually be our own buffer
    TypeDesc format;
    const void* pels = imagecache->tile_pixels(tile, format);
    OIIO_CHECK_EQUAL(pels, pixels);
    OIIO_CHECK_EQUAL(format, TypeDesc::FLOAT);

    // Check that retrieving the pixel (as would be done by the texture
    // system) returns the right color. This would work for texture calls
    // and whatnot as well.
    float testpixel[3] = { -1, -1, -1 };
    imagecache->get_pixels(fooname, 0, 0, 1, 2, 1, 2, 0, 1, 0, 3,
                           TypeDesc::FLOAT, testpixel);
    OIIO_CHECK_EQUAL(testpixel[0], pixels[1][1][0]);
    OIIO_CHECK_EQUAL(testpixel[1], pixels[1][1][1]);
    OIIO_CHECK_EQUAL(testpixel[2], pixels[1][1][2]);

    ImageCache::destroy(imagecache);
}



int
main(int argc, char** argv)
{
    test_get_pixels_cachechannels(0, 10);
    test_get_pixels_cachechannels(0, 4);
    test_get_pixels_cachechannels(0, 4, 0, 6);
    test_get_pixels_cachechannels(0, 4, 0, 4);
    test_get_pixels_cachechannels(6, 9);
    test_get_pixels_cachechannels(6, 9, 6, 9);

    test_app_buffer();

    return unit_test_failures;
}
