// K-3D
// Copyright (c) 1995-2004, Timothy M. Shead
//
// Contact: tshead@k-3d.com
//
// 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

/** \file
		\brief Implements the NetPBMWriter K-3D object, which writes bitmap images using the NetPBM tools
		\author Tim Shead (tshead@k-3d.com)
*/

#include <k3dsdk/application.h>
#include <k3dsdk/ibitmap_write_format.h>
#include <k3dsdk/ideletable.h>
#include <k3dsdk/ifile_format.h>
#include <k3dsdk/module.h>
#include <k3dsdk/string_modifiers.h>
#include <k3dsdk/system_functions.h>

#include <sdpxml/sdpxml.h>

#include <boost/filesystem/fstream.hpp>
#include <boost/filesystem/path.hpp>
#include <boost/regex.hpp>

#include <stdio.h>
#include <fstream>

namespace
{

/////////////////////////////////////////////////////////////////////////////
// netpbm_writer_implementation

class netpbm_writer_implementation :
	public k3d::ifile_format,
	public k3d::ibitmap_write_format,
	public k3d::ideletable
{
public:
	netpbm_writer_implementation() :
		m_Formats("netpbm")
	{
	}

	unsigned long priority()
	{
		return 0;
	}
	
	bool query_can_handle(const boost::filesystem::path& FilePath)
	{
		return GetWriter(FilePath);
	}

	bool write_file(const boost::filesystem::path& FilePath, const k3d::bitmap& Bitmap)
	{
		std::cerr << info << "Write " << FilePath.native_file_string() << " using NetPBMWriter" << std::endl;

		sdpxml::ElementPointer writer = GetWriter(FilePath);
		return_val_if_fail(writer, false);

		// Save the image as a PPM file ...
		const boost::filesystem::path ppmpath(k3d::system::get_temp_directory() / "netpbmwriter.ppm");
		return_val_if_fail(SaveAsPPM(ppmpath, Bitmap), false);

		// Convert the PPM file to the desired end file format ...
		//std::string command = sdpxml::GetAttribute<std::string>(*writer, "command", "");
		std::string command = sdpxml::GetAttribute<std::string>(*writer, "command", "");
		return_val_if_fail(command.size(), false);

		k3d::formatted_replace(command, '%', "p", ppmpath.native_file_string());

		// Redirect command to FilePath
		command += " > " + FilePath.native_file_string();

		return_val_if_fail(k3d::system::run_process(command, std::cerr), false);

		return true;
	}

	k3d::iplugin_factory& factory()
	{
		return get_factory();
	}

	static k3d::iplugin_factory& get_factory()
	{
		static k3d::plugin_factory<
			k3d::application_plugin<netpbm_writer_implementation>,
			k3d::interface_list<k3d::ibitmap_write_format> > factory(
				k3d::uuid(0x2247eb1c, 0x9f37414d, 0xb8b98d4a, 0xad0667eb),
				"NetPBMWriter",
				"NetPBM Formats ( many )",
				"Bitmap File Formats");

		return factory;
	}

private:
	/// Loads the netpbm.conf XML file, which describes how to identify and convert filetypes supported by NetPBM
	bool LoadFormats();

	/// Returns a filter description from the netpbm.conf XML file, corresponding to the requested file format
	sdpxml::ElementPointer GetWriter(const boost::filesystem::path& FilePath);

	/// Saves a K-3D bitmap in PPM format
	bool SaveAsPPM(const boost::filesystem::path& FilePath, const k3d::bitmap& Bitmap);
	/// Saves image data in PPM format
	bool SaveAsPPM(const k3d::pixel* Data, const k3d::pixel_size_t Width, const k3d::pixel_size_t Height, std::ostream& Stream);

	sdpxml::Document m_Formats;
};

/////////////////////////////////////////////////////////////////////////////
// Helper functions

bool netpbm_writer_implementation::LoadFormats()
{
	if(m_Formats.Children().size())
		return true;

	const boost::filesystem::path format_file(k3d::application().share_path() / "netpbm.conf");
	boost::filesystem::ifstream stream(format_file);
	if(!m_Formats.Load(stream, format_file.native_file_string()))
		{
			std::cerr << __PRETTY_FUNCTION__ << ": Error loading " << format_file.native_file_string() << " configuration file" << std::endl;
			return false;
		}

	return true;
}

sdpxml::ElementPointer netpbm_writer_implementation::GetWriter(const boost::filesystem::path& FilePath)
{
	// Load our external format description file ...
	return_val_if_fail(LoadFormats(), 0);

	// Make sure we've got something ...
	return_val_if_fail(m_Formats.Children().size(), 0);
	return_val_if_fail(m_Formats.Name() == "netpbm", 0);

	sdpxml::ElementPointer writers = sdpxml::FindElement(m_Formats, sdpxml::SameName("writers"));
	return_val_if_fail(writers, 0);

	for(sdpxml::ElementCollection::iterator writer = writers->Children().begin(); writer != writers->Children().end(); writer++)
		{
			// See if it has a "filename" regular expression ...
			const std::string filename = sdpxml::GetAttribute<std::string>(*writer, "filename", "");
			if(filename.empty())
				continue;

			// See if the filename regular expression has any matches against the input filename ...
			boost::regex expression(filename);
			boost::smatch match_results;
			if(boost::regex_search(FilePath.native_file_string(), match_results, expression))
				return &(*writer);
		}

	return 0;
}

bool netpbm_writer_implementation::SaveAsPPM(const boost::filesystem::path& FilePath, const k3d::bitmap& Bitmap)
{
	// Get data from the bitmap ...
	const k3d::pixel* data = Bitmap.data();
	const k3d::pixel_size_t width = Bitmap.width();
	const k3d::pixel_size_t height = Bitmap.height();

	return_val_if_fail(data, false);

	// Create the output stream ... (prefer binary mode, due to Win32 endl)
	boost::filesystem::ofstream file(FilePath, std::ios::out | std::ios::binary);
	return_val_if_fail(file.good(), false);

	return SaveAsPPM(data, width, height, file);
}

bool netpbm_writer_implementation::SaveAsPPM(const k3d::pixel* Data, const k3d::pixel_size_t Width, const k3d::pixel_size_t Height, std::ostream& Stream)
{
	// Write our magic headers ...
	Stream << "P6" << std::endl;
	Stream << Width << " " << Height << std::endl;
	Stream << "255" << std::endl;

	// Write data ...
	for(k3d::pixel_size_t y = 0; y < Height; y++)
		{
			for(k3d::pixel_size_t x = 0; x < Width; x++)
				{
					Stream << Data->red << Data->green << Data->blue;
					++Data;
				}
		}

	return true;
}

} // namespace

namespace libk3dbitmap
{

/////////////////////////////////////////////////////////////////////////////
// netpbm_reader_factory

k3d::iplugin_factory& netpbm_writer_factory()
{
	return netpbm_writer_implementation::get_factory();
}

} // namespace libk3dbitmap


