///////////////////////////////////////////////////////////////////////////
//
// 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.
//
///////////////////////////////////////////////////////////////////////////


//-----------------------------------------------------------------------------
//
//	exrdisplay -- a simple program to display Imf::Rgba images
//
//-----------------------------------------------------------------------------

#include <FL/Fl.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Value_Slider.H>
#include <FL/Fl_Check_Button.H>
#include <FL/Fl_Box.H>

#include <ImageView.h>
#ifdef HAVE_FRAGMENT_SHADERS
#include <ImageViewFragShader.h>
#endif
#include <ImfArray.h>
#include <loadImage.h>
#include <scaleImage.h>

#include <iostream>
#include <algorithm>
#include <string>
#include <exception>
#include <string.h>
#include <stdlib.h>

using namespace Imath;
using namespace Imf;
using namespace std;


struct MainWindow
{
    Fl_Window *			window;
    Fl_Box *			exposureLabel;
    Fl_Value_Slider *		exposureSlider;
    Fl_Box *			defogLabel;
    Fl_Value_Slider *		defogSlider;
    Fl_Box *			kneeLowLabel;
    Fl_Value_Slider *		kneeLowSlider;
    Fl_Box *			kneeHighLabel;
    Fl_Value_Slider *		kneeHighSlider;
    ImageView *			image;
    Imf::Array<Imf::Rgba>	pixels;

    static void		exposureSliderCallback (Fl_Widget *widget, void *data);
    static void		defogSliderCallback (Fl_Widget *widget, void *data);
    static void		kneeLowSliderCallback (Fl_Widget *widget, void *data);
    static void		kneeHighSliderCallback (Fl_Widget *widget, void *data);
};


void	
MainWindow::exposureSliderCallback (Fl_Widget *widget, void *data)
{
    MainWindow *mainWindow = (MainWindow *) data;
    mainWindow->image->setExposure (mainWindow->exposureSlider->value());
}


void	
MainWindow::defogSliderCallback (Fl_Widget *widget, void *data)
{
    MainWindow *mainWindow = (MainWindow *) data;
    mainWindow->image->setDefog (mainWindow->defogSlider->value());
}


void	
MainWindow::kneeLowSliderCallback (Fl_Widget *widget, void *data)
{
    MainWindow *mainWindow = (MainWindow *) data;
    mainWindow->image->setKneeLow (mainWindow->kneeLowSlider->value());
}


void	
MainWindow::kneeHighSliderCallback (Fl_Widget *widget, void *data)
{
    MainWindow *mainWindow = (MainWindow *) data;
    mainWindow->image->setKneeHigh (mainWindow->kneeHighSlider->value());
}


MainWindow *
makeMainWindow (const char imageFile[],
		const char channel[],
		bool preview,
		int lx,
		int ly,
		bool noDisplayWindow,
		bool noAspect,
		bool zeroOneExposure,
		bool normalize,
		bool useFragmentShader,
		const char * fragmentShaderName)
{
    MainWindow *mainWindow = new MainWindow;

    //
    // Read the image file.
    //

    Box2i displayWindow;
    Box2i dataWindow;
    float pixelAspect;

    if (preview)
    {
	loadPreviewImage (imageFile,
			  displayWindow,
			  dataWindow,
			  pixelAspect,
			  mainWindow->pixels);
    }
    else if (lx >= 0 || ly >= 0)
    {
	if (channel)
	{
	    loadTiledImageChannel (imageFile,
				   channel,
				   lx, ly,
				   displayWindow,
				   dataWindow,
				   pixelAspect,
				   mainWindow->pixels);
	}
	else
	{
	    loadTiledImage (imageFile,
			    lx, ly,
			    displayWindow,
			    dataWindow,
			    pixelAspect,
			    mainWindow->pixels);
	}
    }
    else
    {
	if (channel)
	{
	    loadImageChannel (imageFile,
			      channel,
			      displayWindow,
			      dataWindow,
			      pixelAspect,
			      mainWindow->pixels);
	}
	else
	{
	    loadImage (imageFile,
		       displayWindow,
		       dataWindow,
		       pixelAspect,
		       mainWindow->pixels);
	}
    }
    
    int w  = displayWindow.max.x - displayWindow.min.x + 1;
    int h  = displayWindow.max.y - displayWindow.min.y + 1;
    int dw = dataWindow.max.x - dataWindow.min.x + 1;
    int dh = dataWindow.max.y - dataWindow.min.y + 1;
    int dx = dataWindow.min.x - displayWindow.min.x;
    int dy = dataWindow.min.y - displayWindow.min.y;

    if (noDisplayWindow)
    {
	w = dw;
	h = dh;
	dx = 0;
	dy = 0;
    }

    if (noAspect)
    {
	pixelAspect = 1;
    }

    //
    // Normalize the pixel data if necessary.
    //

    if (normalize)
	normalizePixels (dw, dh, mainWindow->pixels);

    //
    // Stretch the image horizontally or vertically to make the
    // pixels square (assuming that we are going to display the
    // image on a screen with square pixels).
    //

    if (pixelAspect > 1)
	scaleX (pixelAspect, w, h, dw, dh, dx, dy, mainWindow->pixels);
    else
	scaleY (1 / pixelAspect, w, h, dw, dh, dx, dy, mainWindow->pixels);

    //
    // Build main window
    //

    int mw = max (200, w);

    Fl::set_color (FL_GRAY, 150, 150, 150);

    mainWindow->window =
	new Fl_Window (mw + 10, h + 110, imageFile);

    //
    // Add exposure slider
    //

    mainWindow->exposureLabel =
	new Fl_Box (5, 5, 60, 20, "exposure");

    mainWindow->exposureSlider =
	new Fl_Value_Slider (70, 5, mw - 65, 20, "");

    enum Fl_When when = useFragmentShader ? FL_WHEN_CHANGED : FL_WHEN_RELEASE;

    mainWindow->exposureSlider->type (FL_HORIZONTAL);
    mainWindow->exposureSlider->range (-10.0, +10.0);
    mainWindow->exposureSlider->step (1, 8);
    mainWindow->exposureSlider->value (zeroOneExposure? 1.0: 0.0);
    mainWindow->exposureSlider->when (when);

    mainWindow->exposureSlider->callback
	(MainWindow::exposureSliderCallback, mainWindow);

    //
    // Add defog slider
    //

    mainWindow->defogLabel =
	new Fl_Box (5, 30, 60, 20, "defog");

    mainWindow->defogSlider =
	new Fl_Value_Slider (70, 30, mw - 65, 20, "");

    mainWindow->defogSlider->type (FL_HORIZONTAL);
    mainWindow->defogSlider->range (0.0, 0.01);
    mainWindow->defogSlider->step (1, 10000);
    mainWindow->defogSlider->value (0.0);
    mainWindow->defogSlider->when (when);

    mainWindow->defogSlider->callback
	(MainWindow::defogSliderCallback, mainWindow);

    //
    // Add kneeLow slider
    //

    mainWindow->kneeLowLabel =
	new Fl_Box (5, 55, 60, 20, "knee low");

    mainWindow->kneeLowSlider =
	new Fl_Value_Slider (70, 55, mw - 65, 20, "");

    mainWindow->kneeLowSlider->type (FL_HORIZONTAL);
    mainWindow->kneeLowSlider->range (-3.0, 3.0);
    mainWindow->kneeLowSlider->step (1, 8);
    mainWindow->kneeLowSlider->value (0.0);
    mainWindow->kneeLowSlider->when (when);

    mainWindow->kneeLowSlider->callback
	(MainWindow::kneeLowSliderCallback, mainWindow);

    //
    // Add kneeHigh slider
    //

    mainWindow->kneeHighLabel =
	new Fl_Box (5, 80, 60, 20, "knee high");

    mainWindow->kneeHighSlider =
	new Fl_Value_Slider (70, 80, mw - 65, 20, "");

    mainWindow->kneeHighSlider->type (FL_HORIZONTAL);
    mainWindow->kneeHighSlider->range (3.5, 7.5);
    mainWindow->kneeHighSlider->step (1, 8);
    mainWindow->kneeHighSlider->value ((preview | zeroOneExposure)? 3.5: 5.0);
    mainWindow->kneeHighSlider->when (when);

    mainWindow->kneeHighSlider->callback
	(MainWindow::kneeHighSliderCallback, mainWindow);

    //
    // Add image view:
    //
    // w, h		width and height of the display window
    //
    // dw, dh		width and height of the data window
    //
    // dx, dy		offset of the data window's upper left
    // 			corner from the display window's upper
    // 			left corner
    //

#ifdef HAVE_FRAGMENT_SHADERS
    if (useFragmentShader)
    {
	mainWindow->image =
	    new ImageViewFragShader (5 + (mw - w) / 2, 105, 
				     w, h,
				     "",
				     mainWindow->pixels,
				     dw, dh,
				     dx, dy,
				     mainWindow->exposureSlider->value(),
				     mainWindow->defogSlider->value(),
				     mainWindow->kneeLowSlider->value(),
				     mainWindow->kneeHighSlider->value(),
				     fragmentShaderName);
    }
    else
    {
	mainWindow->image =
	    new ImageView (5 + (mw - w) / 2, 105, 
			   w, h,
			   "",
			   mainWindow->pixels,
			   dw, dh,
			   dx, dy,
			   mainWindow->exposureSlider->value(),
			   mainWindow->defogSlider->value(),
			   mainWindow->kneeLowSlider->value(),
			   mainWindow->kneeHighSlider->value());
    }
#else
    mainWindow->image =
	new ImageView (5 + (mw - w) / 2, 105,
		       w, h,
		       "",
		       mainWindow->pixels,
		       dw, dh,
		       dx, dy,
		       mainWindow->exposureSlider->value(),
		       mainWindow->defogSlider->value(),
		       mainWindow->kneeLowSlider->value(),
		       mainWindow->kneeHighSlider->value());
#endif

    mainWindow->image->box (FL_ENGRAVED_BOX);

    mainWindow->window->end();

    return mainWindow;
}


void
usageMessage (const char argv0[], bool verbose = false)
{
    cerr << "usage: " << argv0 << " [options] imagefile" << endl;

    if (verbose)
    {
	cerr << "\n"
	        "Displays an OpenEXR image on the screen.\n"
		"\n"
		"Options:\n"
		"\n"
		"-p        displays the preview (thumbnail)\n"
		"          image instead of the main image\n"
		"\n"
		"-l lx ly  displays level (lx,ly) of a tiled\n"
		"          multiresolution image\n"
		"\n"
		"-w        displays all pixels in the data window,\n"
		"          ignoring the display window\n"
		"\n"
		"-a        ignores the image's pixel aspect ratio,\n"
		"          and does not scale the image to make\n"
		"          the pixels square\n"
		"\n"
		"-c x      loads only image channel x\n"
		"\n"
		"-1        sets exposure and knee sliders so that pixel\n"
		"          value 0.0 becomes black, and 1.0 becomes white\n"
		"\n"
		"-n        normalizes the pixels so that the smallest\n"
		"          value becomes 0.0 and the largest value\n"
		"          becomes 1.0\n"
		"\n"
		"-A        same as -c A -1 (displays alpha)\n"
		"\n"
		"-Z        same as -c Z -n (displays depth)\n"
		"\n"
		"-h        prints this message\n";

	 cerr << endl;
    }

    exit (1);
}


int
main(int argc, char **argv)
{
    const char *imageFile = 0;
    const char *channel = 0;
    bool preview = false;
    bool noDisplayWindow = false;
    bool noAspect = false;
    bool zeroOneExposure = false;
    bool normalize = false;
    bool useFragmentShader = false;
    const char * fragmentShaderName = 0;
    
    int lx = -1;
    int ly = -1;

    //
    // Parse the command line.
    //

    if (argc < 2)
	usageMessage (argv[0], true);

    int i = 1;

    while (i < argc)
    {
	if (!strcmp (argv[i], "-p"))	
	{
	    //
	    // Show the preview image
	    //

	    preview = true;
	    i += 1;
	}
	else if (!strcmp (argv[i], "-l"))
	{
	    //
	    // Assume that the image file is tiled,
	    // and show level (lx,ly) of the tiled image
	    //

	    if (i > argc - 3)
		usageMessage (argv[0]);

	    lx = strtol (argv[i + 1], 0, 0);
	    ly = strtol (argv[i + 2], 0, 0);
	    i += 3;
	}
	else if (!strcmp (argv[i], "-w"))
	{
	    //
	    // Ignore the file's display window
	    //

	    noDisplayWindow = true;
	    i += 1;
	}
	else if (!strcmp (argv[i], "-a"))
	{
	    //
	    // Ignore the file's pixel aspect ratio
	    //

	    noAspect = true;
	    i += 1;
	}
	else if (!strcmp (argv[i], "-c"))
	{
	    //
	    // Load only one image channel.
	    //

	    if (i > argc - 2)
		usageMessage (argv[0]);

	    channel = argv[i + 1];
	    i += 2;
	}
	else if (!strcmp (argv[i], "-1"))
	{
	    //
	    // Display 0.0 to 1.0 range.
	    //

	    zeroOneExposure = true;
	    i += 1;
	}
	else if (!strcmp (argv[i], "-n"))
	{
	    //
	    // Normalize pixels.
	    //

	    zeroOneExposure = true;
	    normalize = true;
	    i += 1;
	}
	else if (!strcmp (argv[i], "-A"))
	{
	    //
	    // Display alpha
	    //

	    zeroOneExposure = true;
	    normalize = false;
	    channel = "A";
	    i += 1;
	}
	else if (!strcmp (argv[i], "-Z"))
	{
	    //
	    // Display depth
	    //

	    zeroOneExposure = true;
	    normalize = true;
	    channel = "Z";
	    i += 1;
	}
	else if (!strcmp (argv[i], "-f"))
	{
	    //
	    // Use the built-in fragment shader.
	    //

	    useFragmentShader = true;
	    i += 1;
	}
	else if (!strcmp (argv[i], "-fn"))
	{
	    //
	    // Use the specified fragment shader.
	    //

	    useFragmentShader = true;
	    fragmentShaderName = argv[i + 1];
	    i += 2;
	}
	else if (!strcmp (argv[i], "-h"))
	{
	    //
	    // Print help message
	    //

	    usageMessage (argv[0], true);
	}
	else
	{
	    //
	    // image file name
	    //

	    imageFile = argv[i];
	    i += 1;
	}
    }

    if (imageFile == 0)
	usageMessage (argv[0]);

#ifndef HAVE_FRAGMENT_SHADERS
    if (useFragmentShader)
    {
	cerr << argv[0] << " was not compiled with fragment shader "
	    "support," << endl;
	cerr << "falling back to software" << endl;
	useFragmentShader = false;
	fragmentShaderName = 0;
    }
#endif

    //
    // Load the specified image file,
    // open a window on the screen, and
    // display the image.
    //

    int exitStatus = 0;

    try
    {
	MainWindow *mainWindow = makeMainWindow (imageFile,
						 channel,
						 preview,
						 lx, ly,
						 noDisplayWindow,
						 noAspect,
						 zeroOneExposure,
						 normalize,
						 useFragmentShader,
						 fragmentShaderName ?
						 fragmentShaderName : "");

	mainWindow->window->show (1, argv);
	exitStatus = Fl::run();
    }
    catch (const exception &e)
    {
	cerr << e.what() << endl;
	exitStatus = 1;
    }

    return exitStatus;
}
