/**************************************************************************/
/*  Copyright 2012 Tim Day                                                */
/*                                                                        */
/*  This file is part of Evolvotron                                       */
/*                                                                        */
/*  Evolvotron 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 3 of the License, or     */
/*  (at your option) any later version.                                   */
/*                                                                        */
/*  Evolvotron 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 Evolvotron.  If not, see <http://www.gnu.org/licenses/>.   */
/**************************************************************************/

/*! \file
  \brief Standalone renderer for evolvotron function files.
*/

#include "function_registry.h"
#include "mutatable_image.h"
#include "random.h"

#include <boost/program_options.hpp>

//! Application code
int main(int argc,char* argv[])
{
  {
    uint frames;
    bool help;
    bool jitter;
    int multisample;
    std::string output_filename;
    std::string size;
    bool verbose;
    
    boost::program_options::options_description options_desc("Options");
    boost::program_options::positional_options_description pos_options_desc;
    {
      using namespace boost::program_options;
      options_desc.add_options()
	("frames,f"     ,value<uint>(&frames)->default_value(1)    ,"Frames in an animation")
	("help,h"       ,bool_switch(&help)                        ,"Print command-line options help message and exit")
	("jitter,j"     ,bool_switch(&jitter)                      ,"Enable rendering jitter")
	("multisample,m",value<int>(&multisample)->default_value(1),"Multisampling grid (NxN)")
	("output,o"     ,value<std::string>(&output_filename)      ,"Output filename (.png or .ppm suffix).  (Or use first positional argument.)")
	("size,s"       ,value<std::string>(&size)->default_value("512x515"),"Generated image size")
	("verbose,v"    ,bool_switch(&verbose)                     ,"Log some details to stderr")
	;
      pos_options_desc.add("output",1);
    }

    boost::program_options::variables_map options;
    boost::program_options::store
      (
       boost::program_options::command_line_parser(argc,argv)
       .options(options_desc).positional(pos_options_desc).run()
       ,options
       );
    boost::program_options::notify(options);
    
    if (help)
      {
	std::cerr << options_desc;
	return 0;
      }

    if (verbose)
      std::clog.rdbuf(std::cerr.rdbuf());
    else
      std::clog.rdbuf(sink_ostream.rdbuf());

    //! \todo Could be done better maybe (set 'x' as input separator)
    const std::string::size_type p=size.find("x");
    if (p==std::string::npos || p==0 || p==size.size()-1)
      {
	std::cerr << "--size option argument isn't in <width>x<height> format\n";
	return 1;
      }
    else
      {
	size[p]=' ';
      }
    int width=512;
    int height=512;
    std::stringstream(size) >> width >> height;
    
    if (frames<1)
      {
	std::cerr << "Must specify at least 1 frame (option: -f <frames>)\n";
	return 1;
      }

    if (output_filename.empty())
      {
	std::cerr << "Must specify an output filename\n";
	return 1;
      }
    
    FunctionRegistry function_registry;
    
    std::string report;
    const boost::shared_ptr<const MutatableImage> imagefn(MutatableImage::load_function(function_registry,std::cin,report));

    if (imagefn.get()==0)
      {
	std::cerr << "evolvotron_render: Error: Function not loaded due to errors:\n" << report;
	return 1;
      }
    else if (!report.empty())
      {
	std::cerr << "evolvotron_render: Warning: Function loaded with warnings:\n" << report;
      }

    // Seed value pretty unimportant; only used for sample jitter.
    Random01 r01(23);

    for (uint frame=0;frame<frames;frame++)
      {
	std::vector<uint> image_data;
	image_data.reserve(width*height);
  
	uint pixels=0;
	uint report=1;
	const uint reports=20;
	for (int row=0;row<height;row++)
	  for (int col=0;col<width;col++)
	    {
	      const XYZ v(imagefn->sampling_coordinate(col,row,frame,width,height,frames));
	    
	      const XYZ colour(imagefn->get_rgb(col,row,frame,width,height,frames,(jitter ? &r01 : 0),multisample));
	    
	      const uint col0=lrint(clamped(colour.x(),0.0,255.0));
	      const uint col1=lrint(clamped(colour.y(),0.0,255.0));
	      const uint col2=lrint(clamped(colour.z(),0.0,255.0));

	      image_data.push_back(((col0<<16)|(col1<<8)|(col2)));

	      pixels++;
	      if (pixels>=(report*width*height)/reports)
		{
		  std::clog << "[" << (100*report)/reports << "%]";
		  report++;
		}
	    }
	std::clog << "\n";

	{
	  //! \todo If filename is "-", write PPM to stdout (QImage save only supports write-to-a-filenames though)
	  QString save_filename(QString::fromLocal8Bit(output_filename.c_str()));

	  const char* save_format="PPM";
	  if (save_filename.toUpper().endsWith(".PPM"))
	    {
	      save_format="PPM";
	    }
	  else if (save_filename.toUpper().endsWith(".PNG"))
	    {
	      save_format="PNG";
	    }
	  else
	    {
	      std::cerr 
		<< "evolvotron_render: Warning: Unrecognised file suffix.  File will be written in "
		<< save_format
		<< " format.\n";
	    }

	  if (frames>1)
	    {
	      QString frame_component = QString::asprintf(".f%06d",frame);
	      int insert_point=save_filename.lastIndexOf(QString("."));
	      if (insert_point==-1)
		{
		  save_filename.append(frame_component);
		}
	      else
		{
		  save_filename.insert(insert_point,frame_component);
		}
	    }
    
	  QImage image
	    (
	     reinterpret_cast<uchar*>(&(image_data[0])),
	     width,
	     height,
	     QImage::Format_RGB32
	     );

	  if (!image.save(save_filename,save_format))
	    {
	      std::cerr 
		<< "evolvotron_render: Error: Couldn't save file "
		<< save_filename.toLocal8Bit().data()
		<< "\n";
	      return 1;
	    }
	
	  std::clog
	    << "Wrote file " 
	    << save_filename.toLocal8Bit().data()
	    << "\n";
	}
      }
  }
  
  return 0;
}
