/**
 * @brief Create a web page with an HDR viewer
 *
 * This file is a part of LuminanceHDR package, based on pfstools.
 * ----------------------------------------------------------------------
 * Copyright (C) 2009 Rafal Mantiuk
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 * ----------------------------------------------------------------------
 *
 * @author Rafal Mantiuk, <mantiuk@mpi-sb.mpg.de>
 *
 * $Id: hdrhtml.cpp,v 1.8 2014/06/16 21:50:08 rafm Exp $
 */

#include "hdrhtml.h"

#include <vector>
#include <cmath>
#include <fstream>
#include <sstream>
#include <iostream>
#include <limits>
#include <cassert>
#include <cstdlib>
#include <locale>

#include "Libpfs/exception.h"

#if defined (WIN32) || defined (__APPLE__)
#include <QCoreApplication>
#endif
#include "hdrhtml-path.hxx"

#include <QImage>
#include <QString>

using namespace std;

namespace hdrhtml {
// ================================================
//        Parameters controllig the web page
// ================================================

const int f_step_res = 3; // How many steps per f-stop (do not change)
const int pix_per_fstop = 25;   // Distance in pixels between f-stops shown on the histogram
const char *hdrhtml_version = "1.0"; // Version of the HDRHTML code


// ================================================
//                Histogram
// ================================================

template<class T>
class Histogram
{
public:
  vector<T> x;                         // Bin centers
  vector<size_t> n;                    // No of items in a bin
  size_t bins;

  Histogram() : bins( 0 )
  {
  }

  ~Histogram()
  {
    free();
  }

  void free()
  {
    //delete []x;
    //delete []n;
    bins = 0;
  }

  void compute( const T *data, size_t d_size, int bins = 30, T min_val = 1, T max_val = -1, bool reject_outofrange = true )
  {
    assert( bins > 0 );

    free();
    this->bins = bins;

    if( min_val > max_val )             // missing min/max info
    {
      min_val = numeric_limits<T>::max();
      max_val = numeric_limits<T>::min();

      for( size_t k=0; k < d_size; k++ ) {
        if( data[k] > max_val ) max_val = data[k];
        if( data[k] < min_val ) min_val = data[k];
      }
    }

    x.resize(bins);
    n.resize(bins);

    T delta = (max_val-min_val) / (float)bins; // width of a single bin

//     T *e = new T[bins+1];        // bin edges
//     for( int k=0; k <= bins; k++ ) {
//       e[k] = min_val + (float)k * delta;
//     }
    for( int k=0; k < bins; k++ ) {
      x[k] = min_val + (float)k * delta + delta/2;
      n[k] = 0;
    }

    if( reject_outofrange ) {
      for( size_t k=0; k < d_size; k++ ) {
        int ind = floor( (data[k]-min_val) / (max_val-min_val) * (float)bins );
        if( ind < 0 )
          continue;
        if( ind >= bins )
          continue;
        n[ind]++;
      }
    } else {
      for( size_t k=0; k < d_size; k++ ) {
        int ind = floor( (data[k]-min_val) / (max_val-min_val) * (float)bins );
        if( ind < 0 ) {
          n[0]++;
          continue;
        }
        if( ind >= bins ) {
          n[bins-1]++;
          continue;
        }
        n[ind]++;
      }
    }



  }

};

// ================================================
//                  Lookup table
// ================================================

/**
 * Lookup table on a uniform array & interpolation
 *
 * x_i must be at least two elements
 * y_i must be initialized after creating an object
 */
class UniformArrayLUT
{
public:
  float *y_i;

  UniformArrayLUT( size_t lut_size, const float *x_i, float *y_i = NULL ) : y_i(NULL), x_i( x_i ), lut_size( lut_size ), delta( x_i[1]-x_i[0] )
  {
    if( y_i == NULL ) {
      this->y_i = new float[lut_size];
      own_y_i = true;
    } else {
      this->y_i = y_i;
      own_y_i = false;
    }
  }

  UniformArrayLUT() : y_i(NULL), x_i( 0 ), lut_size( 0 ), delta( 0. ) {}

  UniformArrayLUT(const UniformArrayLUT& other) : y_i(NULL), x_i( other.x_i ), lut_size( other.lut_size ), delta( other.delta )
  {
    this->y_i = new float[lut_size];
    own_y_i = true;
    memcpy(this->y_i, other.y_i, lut_size * sizeof(float));
  }

  UniformArrayLUT& operator = (const UniformArrayLUT& other)
  {
    if (this != &other)
    {
      this->lut_size = other.lut_size;
      this->delta = other.delta;
      this->x_i = other.x_i;
      this->y_i = new float[lut_size];
      own_y_i = true;
      memcpy(this->y_i, other.y_i, lut_size * sizeof(float));
    }
    return *this;
  }

  ~UniformArrayLUT()
  {
    if( own_y_i )
      delete []y_i;
  }

  float interp( float x )
  {
    const float ind_f = (x - x_i[0])/delta;
    const size_t ind_low = (size_t)(ind_f);
    const size_t ind_hi = (size_t)ceil(ind_f);

    if( (ind_f < 0) )           // Out of range checks
      return y_i[0];
    if( (ind_hi >= lut_size) )
      return y_i[lut_size-1];

    if( (ind_low == ind_hi) )
      return y_i[ind_low];      // No interpolation necessary

    return y_i[ind_low] + (y_i[ind_hi]-y_i[ind_low])*(ind_f-(float)ind_low); // Interpolation
  }

private:
  const float *x_i;
  size_t lut_size;
  float delta;

  bool own_y_i;
};

template<class T>
inline T clamp( T x, T min, T max )
{
  if( x < min )
    return min;
  if( x > max )
    return max;
  return x;
}

/**
 * Lookup table on an arbitrary array
 *
 * x_i must be at least two elements
 * y_i must be initialized after creating an object
 */
template<class Tx, class Ty>
class ArrayLUT
{
  const vector<Tx> x_i;
  size_t lut_size;

  bool own_y_i;
public:
  vector<Ty> y_i;

  ArrayLUT( size_t lut_size, const vector<Tx> &x_i, vector<Ty> &y_i ) : x_i( x_i ), lut_size( lut_size )
  {
    assert( lut_size > 0 );

    if( y_i.size() == 0 ) {
      this->y_i.resize(lut_size);
      own_y_i = true;
    } else {
      this->y_i = y_i;
      own_y_i = false;
    }
  }

  ArrayLUT() : x_i( 0 ), y_i(0), lut_size( 0 ) {}

  ArrayLUT(const ArrayLUT& other) : x_i( other.x_i ), lut_size( other.lut_size )
  {
      this->y_i = new Ty[lut_size];
      own_y_i = true;
      memcpy(this->y_i, other.y_i, lut_size * sizeof(Ty));
  }

  ArrayLUT& operator = (const ArrayLUT& other)
  {
    if (this != &other)
    {
      this->lut_size = other.lut_size;
      this->x_i = other.x_i;
      this->y_i = new Ty[lut_size];
      own_y_i = true;
      memcpy(this->y_i, other.y_i, lut_size * sizeof(Ty));
    }
    return *this;
  }

  ~ArrayLUT()
  {
    //if( own_y_i )
    //  delete []y_i;
  }

  Ty interp( Tx x )
  {
    if( (x <= x_i[0]) )           // Out of range checks
      return y_i[0];
    if( (x >= x_i[lut_size-1]) )
      return y_i[lut_size-1];

    // binary search
    size_t l = 0, r = lut_size-1;
    while( true ) {
      size_t m = (l+r)/2;
      if( m == l ) break;
      if( x < x_i[m] )
        r = m;
      else
        l = m;
    }

    float alpha = (float)(x - x_i[l])/(float)(x_i[r]-x_i[l]);

    return y_i[l] + (Ty)(alpha * (y_i[r]-y_i[l]));
  }


};

// ================================================
//                  Percentiles
// ================================================


/**
 * Compute prctiles using image cummulative histogram, which is less
 * accurate method but much faster than sorting.
 */
template<class T>
class Percentiles
{
  Histogram<T> hist;
  const size_t bin_n;
  size_t d_size;
public:

  /**
   * @param data - table with samples
   * @param d_size - number of samples
   */
  Percentiles( const T *data, size_t d_size ) :
    bin_n( 1000 ), d_size( d_size ) // Accuracy 0.1 prctile
  {
    hist.compute( data, d_size, bin_n, 1, -1, false );
    // Compute cummulative histogram
    for( size_t k = 1; k < bin_n; k++ )
      hist.n[k] += hist.n[k-1];

//    cerr << "d_size: " << d_size << "  hist.n: " << hist.n[bin_n-1] << "\n";
    assert( hist.n[bin_n-1] == d_size );
  }

  T prctile( double p )
  {
    ArrayLUT<size_t,T> lut( hist.bins, hist.n, hist.x );

    return lut.interp( (size_t)(p*(double)d_size/100.) );
  }



};


// ================================================
//            Text template file utils
// ================================================

typedef void (*replace_callback)( ostream &out, void *user_data, const char *parameter );

class ReplacePattern
{

public:

  const char* pattern;
  string replace_with;
  replace_callback callback;
  void *user_data;

  ReplacePattern( const char* pattern, string replace_with ) :
    pattern( pattern ), replace_with( replace_with ), callback( NULL ), user_data( NULL)
  {
  }

  ReplacePattern( const char* pattern, float replace_with_num ) :
    pattern( pattern ), callback( NULL ), user_data( NULL)
  {
    ostringstream num_str;
    num_str << replace_with_num;
    replace_with = num_str.str();
  }

  ReplacePattern( const char* pattern, int replace_with_num ) :
    pattern( pattern ), callback( NULL ), user_data( NULL)
  {
    ostringstream num_str;
    num_str << replace_with_num;
    replace_with = num_str.str();
  }

  ReplacePattern( const char* pattern, replace_callback callback, void *user_data = NULL ) :
    pattern( pattern ), callback( callback ), user_data( user_data )
  {
  }

  ReplacePattern() : pattern( NULL ), callback( NULL ), user_data( NULL)
  {
  }

  virtual void write_replacement( ostream &out, const char *parameter = NULL )
  {
    if( callback != NULL )
      callback( out, user_data, parameter );
    else
      out << replace_with;
  }


};

void create_from_template( ostream &outfs, const char *template_file_name,
  ReplacePattern *pattern_list )
{
  ifstream infs( template_file_name );
  if( !infs.good() ) {
    ostringstream error_message;
    error_message << "Cannot open '" << template_file_name << "' for reading";
    throw pfs::Exception( error_message.str().c_str() );
  }


  const int MAX_LINE_LENGTH = 2048;
//  int lines = 0;
  while( true ) {
    char line[MAX_LINE_LENGTH];
    infs.getline( line, MAX_LINE_LENGTH );

    if( !infs.good() )
      break;

    string line_str( line );
    int pos = 0;

    while( true ) {
      size_t find_pos = line_str.find_first_of( '@', pos );
      if( find_pos == string::npos ) {
        outfs << line_str.substr( pos, string::npos );
        break;
      }

      bool replaced = false;
      size_t end_marker = line_str.find_first_of( "@[", find_pos+1 );
      if( end_marker != string::npos ) {

        for( int k = 0; pattern_list[k].pattern != NULL; k++ )
        {
          if( line_str.compare( find_pos+1, end_marker-find_pos-1, pattern_list[k].pattern ) == 0 ) {
            outfs << line_str.substr( pos, find_pos-pos );

            string parameter;
            if( line_str[end_marker] == '[' ) {
              size_t param_endmarker = line_str.find_first_of( ']', end_marker+1 );
              if( param_endmarker == string::npos )
                throw pfs::Exception( "Non-closed bracker in the replacement keyword" );
              parameter = line_str.substr( end_marker+1, param_endmarker-end_marker-1 );
              end_marker = param_endmarker+1;
            }

            pattern_list[k].write_replacement( outfs, parameter.empty() ? NULL : parameter.c_str() );
            pos = end_marker + 1;
            replaced = true;
            break;
          }
        }

      }
      if( !replaced ) {
        outfs << line_str.substr( pos, find_pos-pos+1 );
        pos = find_pos+1;
      }

    }


    outfs << "\n";

  }

}

void create_from_template( const char *output_file_name, const char *template_file_name,
  ReplacePattern *pattern_list )
{
  ofstream outfs( output_file_name );
  if( !outfs.good() ) {
    ostringstream error_message;
    error_message << "Cannot open '" << output_file_name << "' for writing";
    throw pfs::Exception( error_message.str().c_str() );
  }
  try {
    create_from_template( outfs, template_file_name, pattern_list );
  }
  catch( pfs::Exception &e) {
    throw;
  }
}




// ================================================
//            Read and parse CVS files
// ================================================


class CSVTable
{
public:

  float **data;
  int columns, rows;

  CSVTable() : data( NULL ), columns( 0), rows( 0 )
  {
  }


  ~CSVTable()
  {
    free();
  }

  void free()
  {
    if( data == NULL )
      return;

    for( int k = 0; k < columns; k++ )
      delete [] data[k];

    delete []data;

    data = NULL;
  }


  void read( const char *file_name, int columns )
  {
    free();

    this->columns = columns;

    ifstream ifs( file_name );

    if( !ifs.is_open() ) {
      string full_message( "Cannot open file: " );
      full_message += file_name;
      throw pfs::Exception( full_message.c_str() );
    }

    list<float> value_list;

    const int MAX_LINE_LENGTH = 1024;
    int lines = 0;
    while( 1 ) {
      char line[MAX_LINE_LENGTH];
      ifs.getline( line, MAX_LINE_LENGTH );

      if( !ifs.good() )
        break;

      string line_str( line );
      int pos = 0;
      for( int k=0; k < columns; k++ ) {
        // Skip white spaces
        while( line_str[pos] == ' ' || line_str[pos] == '\t' ) pos++;
        size_t new_pos = line_str.find_first_of( ',', pos );
        size_t len;
        if( new_pos == string::npos ) {
          if( k != columns-1 ) {
            string full_message( "Missing column data in the file: " );
            full_message += file_name;
            throw pfs::Exception( full_message.c_str() );
          }
          len = string::npos;
        } else
          len = new_pos-pos;

        float value;
        if( len == 0 ) {
          value = numeric_limits<float>::quiet_NaN();
        } else {
          string token = line_str.substr( pos, len );
          const char *str_beg = token.c_str();
          char *str_end;
          //cerr << "token: " << str_beg << "\n";
          value = strtof( str_beg, &str_end );
          if( str_beg == str_end ) {
            ostringstream error_message;
            error_message << "Error parsing line " << lines+1 << " of " << file_name << "\n";
            throw pfs::Exception( error_message.str().c_str() );
          }
        }


        value_list.push_back( value );

        pos = new_pos+1;
      }

      lines++;
    }

    float **table = new float*[columns];
    for( int c=0; c < columns; c++ )
      table[c] = new float[lines];

    for( int l=0; l < lines; l++ )
      for( int c=0; c < columns; c++ ) {
        table[c][l] = value_list.front();
        value_list.pop_front();
      }

    data = table;
    this->rows = lines;
  }

};


// ================================================
//                 HDR HTML code
// ================================================


void HDRHTMLSet::add_image( int width, int height, float *R, float *G, float *B,
  float *Y,
  const char *base_name, const char *out_dir, int quality, bool verbose )
{

  //THIS CAUSED ME A BIG HEADACHE!!!
  string user_locale = locale("").name();
  locale::global(locale::classic());

  const int pixels = width*height;
  const int basis_no = quality;

  // Load LUT for the basis tone-curves
  ostringstream lut_filename;
#if defined (WIN32) || defined (__APPLE__)
  QString h_t_b = HDRHTMLDIR;
  h_t_b.append("\\hdrhtml_t_b");
  lut_filename << h_t_b.toStdString().c_str() << basis_no+1 << ".csv";
#else
  lut_filename << HDRHTMLDIR "/hdrhtml_t_b" << basis_no+1 << ".csv";
#endif
  CSVTable basis_table;
  try {
    basis_table.read( lut_filename.str().c_str(), basis_no+1 );
  }
  catch (pfs::Exception &e) {
      throw;
  }
  // Transform the first row (luminance factors) to the log domain
  for( int k = 0; k < basis_table.rows; k++ ) {
    basis_table.data[0][k] = log2f( basis_table.data[0][k] );
  }

// Fix zero and negative values in the image, convert to log2 space, find min and max values
  float img_min = numeric_limits<float>::max();
  float img_max = numeric_limits<float>::min();
  {
    float *arrays[] = { R, G, B, Y };

    int k;

    for( k = 0; k < 4; k++ ) {
      float *x = arrays[k];
      float min_val = numeric_limits<float>::max(), max_val = numeric_limits<float>::min();
      for( int i=0; i < pixels; i++ ) {
        if( x[i] < min_val && x[i] > 0)
          min_val = x[i];
        if( x[i] > max_val )
          max_val = x[i];
      }
      img_max = max( img_max, log2f(max_val) );
      img_min = min( img_min, log2f(min_val) );

      for( int i=0; i < pixels; i++ ) {
        if( x[i] < min_val )
          x[i] = log2f(min_val);
        else
          x[i] = log2f(x[i]);
      }
    }
  }

  Percentiles<float> prc( Y, pixels );
  img_min = prc.prctile( 0.1 );
  img_max = prc.prctile( 99.9 );

  img_min -= 4;  // give extra room for brightenning
  // how many 8-fstop segments we need to cover the DR
  int f8_stops = ceil((img_max-img_min)/8);

  // start with this f-stop
  float l_start = img_min + (img_max-img_min-f8_stops*8)/2;

  float l_med = prc.prctile( 50 );
  float best_exp = round(l_med-l_start-4);

// pix_per_fstop = 25;

// % generate image histogram

  const int hist_height = 36;
  int hist_width = width;
//  float hist_img_sz = [36 size(img,2)];
  float hist_fstops = (float)hist_width / (float)pix_per_fstop;
  float hist_start = (img_max-img_min-hist_fstops)/2;
  {

    Histogram<float> hist;
    hist.compute( Y, pixels, hist_width, img_min+hist_start, img_min+hist_start+hist_fstops );

    unsigned short *hist_buffer = new unsigned short[hist_width*hist_height*3];
    float hist_n_max = -1;
    for( int k = 0; k < hist_width; k++ )
      hist_n_max = max( hist_n_max, (float)hist.n[k] );

    for( int k = 0; k < hist_width; k++ ) {
      float top = hist_height - round((float)hist.n[k]/hist_n_max * hist_height);
      for( int r = 0; r < hist_height; r++ ) {
        hist_buffer[(r*hist_width+k)*3+0] = 0;
        hist_buffer[(r*hist_width+k)*3+1] = (r>=top ? (1<<16) -1 : 0);
        hist_buffer[(r*hist_width+k)*3+2] = 0;
      }
    }

    unsigned char *hist_buffer_c = new unsigned char[hist_width*hist_height*3];
    for( int h = 0; h < hist_width*hist_height*3; h++ ) {
        float v = 255.f * (hist_buffer[h]/65535.f);
        hist_buffer_c[h] = (unsigned char)v;
    }

// tick_fstops = (floor(hist_l(end))-ceil(hist_l(1)));
// ticks = round((ceil(hist_l(1))-hist_l(1))*pix_per_fstop) + (1:tick_fstops)*pix_per_fstop;
// hist_img(1:5,ticks,1:2) = 0.5;
// hist_img(end-4:end,ticks,1:2) = 0.5;
// plot_name = sprintf( '%s_hist.png', base_name );
// imwrite( hist_img, plot_name );

    QImage hist_image(hist_buffer_c, hist_width, hist_height, QImage::Format_RGB888);
    ostringstream img_filename;
    if( out_dir != NULL )
      img_filename << out_dir << "/";
    if( image_dir != NULL )
      img_filename << image_dir << "/";
    img_filename << base_name << "_hist.png";
    if (verbose)
        cout << QObject::tr("Writing: ").toStdString() << img_filename.str() << endl;
    hist_image.save( QString::fromStdString(img_filename.str()));

    delete []hist_buffer;
  }

  // generate basis images

  //unsigned short *imgBuffer =
    //new unsigned short[pixels*3];
  vector<float> imgBuffer(pixels*3);
  vector<unsigned char> imgBuffer_c(pixels*3);
  for( int k=1; k <= f8_stops+1; k++ ) {


    float max_value  = (float)numeric_limits<unsigned char>::max(); //(1<<16) -1;

    float exp_multip = log2f(1/powf( 2, l_start + k*8 ));

    int max_basis = basis_no;
    if( k == f8_stops+1 )     // Do only one shared basis for the last 8-fstop segment
      max_basis = 1;

    for( int b=0; b < max_basis; b++ ) {
      UniformArrayLUT basis_lut( basis_table.rows, basis_table.data[0], basis_table.data[b+1] );

      int i = 0;
      for( int pix = 0; pix < pixels; pix++ ) {

        float rgb[3];
        rgb[0] = R[pix];
        rgb[1] = G[pix];
        rgb[2] = B[pix];

        for( int c=0; c < 3; c++ ) {
          float exposure_comp_v = rgb[c] + exp_multip;
          float v = (basis_lut.interp(exposure_comp_v)*max_value);
          imgBuffer[i++] = v;
        }
      }
      for( int pix = 0; pix < pixels*3; pix++ ) {
          float r = imgBuffer[pix];
          imgBuffer_c[pix] = (unsigned char)r;
      }
      QImage imImage(imgBuffer_c.data(), width, height, QImage::Format_RGB888);

      ostringstream img_filename;
      if( out_dir != NULL )
        img_filename << out_dir << "/";
      if( image_dir != NULL )
        img_filename << image_dir << "/";
      img_filename << base_name << '_' << k-1 << '_' << b+1 << ".jpg";
      if (verbose)
        cout << QObject::tr("Writing: ").toStdString() << img_filename.str() << endl;
      imImage.save( QString::fromStdString(img_filename.str()));
    }

  }

  HDRHTMLImage new_image( base_name, width, height );

  new_image.hist_width = hist_width;
  new_image.f8_stops = f8_stops;
  new_image.f_step_res = f_step_res;
  new_image.basis = basis_no;
  new_image.shared_basis = 1;
  new_image.pix_per_fstop = pix_per_fstop;
  new_image.hist_start = hist_start;
  new_image.hist_width = hist_width;
  new_image.best_exp = best_exp;

  image_list.push_back( new_image );

  locale::global(locale(user_locale.c_str()));
}

void print_image_objects( ostream &out, void *user_data, const char *parameter );
void print_cf_table( ostream &out, void *user_data, const char *parameter );
void print_image_htmlcode( ostream &out, void *user_data, const char *parameter );

void HDRHTMLSet::generate_webpage( const char *page_template, const char *image_template,
  const char *out_dir, const char *object_output, const char *html_output, bool verbose)
{
  if( image_list.empty() )
    return;

  ostringstream out_file_name;
  if (out_dir != NULL)
      out_file_name << out_dir << "/";
  if( page_name == NULL )
    out_file_name << image_list.front().base_name << ".html";
  else
    out_file_name << page_name;

  // Load the table of the opacity coeffcients
  ostringstream lut_filename;
#if defined (WIN32) || defined (__APPLE__)
  QString h_c_b = HDRHTMLDIR;
  h_c_b.append("\\hdrhtml_c_b");
  lut_filename << h_c_b.toStdString().c_str() << image_list.front().basis+1 << ".csv";
#else
  lut_filename << HDRHTMLDIR "/hdrhtml_c_b" << image_list.front().basis+1 << ".csv";
#endif
  CSVTable coeff_table;
  coeff_table.read( lut_filename.str().c_str(), image_list.front().basis+1 );

  ReplacePattern replace_list[] = {
    ReplacePattern( "cf_array_def", print_cf_table, &coeff_table ),
    ReplacePattern( "hdr_img_def", print_image_objects, this ),
    ReplacePattern( "image_htmlcode", print_image_htmlcode, this ),
    ReplacePattern( "title", page_name == NULL ? "HDRHTML viewer" : page_name ),
    ReplacePattern( "version", hdrhtml_version ),
    ReplacePattern()
  };

  this->image_template = image_template;
  try {
    create_from_template( out_file_name.str().c_str(), page_template, replace_list );
  }
  catch( pfs::Exception &e) {
    throw;
  }

  if( object_output != NULL ) {
    ofstream oofs( object_output );
    if( !oofs.good() ) {
      ostringstream error_message;
      error_message << "Cannot open '" << object_output << "' for writing";
      throw pfs::Exception( error_message.str().c_str() );
    }
    print_image_objects( oofs, this, NULL );
  }

  if (verbose)
    cout << QObject::tr("Writing: ").toStdString() << out_file_name.str().c_str() << endl;

  if( html_output != NULL ) {
    ofstream hofs( html_output );
    if( !hofs.good() ) {
      ostringstream error_message;
      error_message << "Cannot open '" << html_output << "' for writing";
      throw pfs::Exception( error_message.str().c_str() );
    }
    try {
      print_image_htmlcode( hofs, this, NULL );
    }
    catch( pfs::Exception &e) {
        throw;
    }
  }

}

void print_image_objects( ostream &out, void *user_data, const char *parameter )
{
  HDRHTMLSet *hdrhtml_set = static_cast<HDRHTMLSet*>(user_data);

  list<HDRHTMLImage>::iterator it;
  for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); ++it ) {
    string obj_name( "hdr_" );
    obj_name.append( it->base_name );

    out << obj_name << " = new Object();\n";
    out << obj_name << ".width = " << it->width << ";\n";
    out << obj_name << ".height = " << it->height << ";\n";
    out << obj_name << ".f8_stops = " << it->f8_stops << ";\n";
    out << obj_name << ".f_step_res = " << it->f_step_res << ";\n";
    out << obj_name << ".base_name = \"" << it->base_name << "\";\n";
    if( hdrhtml_set->image_dir==NULL )
      out << obj_name << ".image_dir = \"\";\n";
    else
      out << obj_name << ".image_dir = \"" << hdrhtml_set->image_dir << "/\";\n";
    out << obj_name << ".basis = " << it->basis << ";\n";
    out << obj_name << ".shared_basis = " << it->shared_basis << ";\n";
    out << obj_name << ".pix_per_fstop = " << it->pix_per_fstop << ";\n";
    out << obj_name << ".hist_start = " << it->hist_start << ";\n";
    out << obj_name << ".hist_width = " << it->hist_width << ";\n";
    out << obj_name << ".exposure = " << it->best_exp << ";\n";
    out << obj_name << ".best_exp = " << it->best_exp << ";\n\n";
  }

}

void print_image_htmlcode( ostream &out, HDRHTMLSet *hdrhtml_set, const HDRHTMLImage &it )
{
    string obj_name( "hdr_" );
    obj_name.append( it.base_name );

    ostringstream img_dir;
    if( hdrhtml_set->image_dir != NULL )
      img_dir << hdrhtml_set->image_dir << "/";

    ReplacePattern replace_list[] = {
      ReplacePattern( "hdr_img_width", it.width ),
      ReplacePattern( "hdr_img_height", it.height ),
      ReplacePattern( "img_dir", img_dir.str() ),
      ReplacePattern( "hist_width", it.hist_width ),
      ReplacePattern( "base_name", it.base_name ),
      ReplacePattern( "help_mark_pos", it.hist_width-12 ),
      ReplacePattern( "hdr_img_object", obj_name ),
      ReplacePattern( "version", hdrhtml_version ),
      ReplacePattern()
    };

    try {
        create_from_template( out, hdrhtml_set->image_template, replace_list );
    }
    catch( pfs::Exception &e) {
        throw;
    }
}

void print_image_htmlcode( ostream &out, void *user_data, const char *parameter )
{
  HDRHTMLSet *hdrhtml_set = static_cast<HDRHTMLSet*>(user_data);

  if( parameter != NULL ) {

    list<HDRHTMLImage>::iterator it;
    for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); ++it ) {
      if( it->base_name.compare( parameter ) == 0 )
        break;
    }
    if( it == hdrhtml_set->image_list.end() )
    {
      cerr << "Warning: image '" << parameter << "' not found\n";
      throw;
    }
    try {
      print_image_htmlcode( out, hdrhtml_set, *it );
    }
    catch( pfs::Exception &e) {
        throw;
    }

  } else {

    list<HDRHTMLImage>::iterator it;
    for( it = hdrhtml_set->image_list.begin(); it != hdrhtml_set->image_list.end(); ++it ) {

      try {
        print_image_htmlcode( out, hdrhtml_set, *it );
      }
      catch( pfs::Exception &e) {
        throw;
      }

    }
  }

}

void print_cf_table( ostream &out, void *user_data, const char *parameter )
{
  CSVTable *cf = static_cast<CSVTable*>(user_data);

  out << "var cf = new Array(\n";
  for( int b=0; b < cf->rows; b++ ) {
    out << "   new Array(";
    for( int ex=0; ex < cf->columns; ex++ ) {
      out << ' ' << cf->data[ex][b];
      if( ex != cf->columns-1 )
        out << ',';
    }
    out << ')';
    if( b != cf->rows-1 )
      out << ',';
    out << "\n";
  }
  out << ");\n";

}

}
