#include <iostream>
#include <fstream>

#include "voronotalt/parallelization_configuration.h"

#ifdef VORONOTALT_OPENMP
#include <omp.h>
#endif

#ifdef VORONOTALT_WITH_TEST_MODES
#include <random>
#endif

#include "voronotalt/voronotalt.h"

#include "voronotalt_cli/voronotalt_cli.h"

namespace
{

void print_help(const bool full, std::ostream& output) noexcept
{
	output << R"(
Voronota-LT version 0.9.5

'voronota-lt' executable constructs a radical Voronoi tessellation (also known as a Laguerre-Voronoi diagram or a power diagram)
of atomic balls of van der Waals radii constrained inside a solvent-accessible surface defined by a rolling probe.
The software computes inter-atom contact areas, per-cell solvent accessible surface areas, per-cell constrained volumes.
'voronota-lt' is very fast when used on molecular data with a not large rolling probe radius (less than 2.0 angstroms, 1.4 is recommended)
and can be made even faster by running it using multiple processors.

Options:
    --probe                                          number     rolling probe radius, default is 1.4)";

	if(voronotalt::openmp_enabled())
	{
		output << R"(
    --processors                                     number     maximum number of OpenMP threads to use, default is 2)";
	}

	output << R"(
    --compute-only-inter-residue-contacts                       flag to only compute inter-residue contacts, turns off per-cell summaries
    --compute-only-inter-chain-contacts                         flag to only compute inter-chain contacts, turns off per-cell summaries
    --run-in-aw-diagram-regime                                  flag to run construct a simplified additively weighted Voronoi diagram, turns off per-cell summaries
    --input | -i                                     string     input file path to use instead of standard input, or '_stdin' to still use standard input
    --periodic-box-directions                        numbers    coordinates of three vectors (x1 y1 z1 x2 y2 z2 x3 y3 z3) to define and use a periodic box
    --periodic-box-corners                           numbers    coordinates of two corners (x1 y1 z1 x2 y2 z2) to define and use a periodic box
    --pdb-or-mmcif-heteroatoms                                  flag to include heteroatoms when reading input in PDB or mmCIF format
    --pdb-or-mmcif-hydrogens                                    flag to include hydrogen atoms when reading input in PDB or mmCIF format
    --pdb-or-mmcif-join-models                                  flag to join multiple models into an assembly when reading input in PDB or mmCIF format
    --print-contacts                                            flag to print table of contacts to stdout
    --print-contacts-residue-level                              flag to print residue-level grouped contacts to stdout
    --print-contacts-chain-level                                flag to print chain-level grouped contacts to stdout
    --print-cells                                               flag to print table of per-cell summaries to stdout
    --print-cells-residue-level                                 flag to print residue-level grouped per-cell summaries to stdout
    --print-cells-chain-level                                   flag to print chain-level grouped per-cell summaries to stdout
    --print-everything                                          flag to print everything to stdout, terminate if printing everything is not possible
    --write-input-balls-to-file                                 output file path to write input balls to file
    --write-contacts-to-file                         string     output file path to write table of contacts
    --write-contacts-residue-level-to-file           string     output file path to write residue-level grouped contacts
    --write-contacts-chain-level-to-file             string     output file path to write chain-level grouped contacts
    --write-cells-to-file                            string     output file path to write of per-cell summaries
    --write-cells-residue-level-to-file              string     output file path to write residue-level grouped per-cell summaries
    --write-cells-chain-level-to-file                string     output file path to write chain-level grouped per-cell summaries
    --write-tessellation-edges-to-file               string     output file path to write generating IDs and lengths of SAS-constrained tessellation edges
    --write-tessellation-vertices-to-file            string     output file path to write generating IDs and positions of SAS-constrained tessellation vertices
    --graphics-output-file                           string     output file path to write contacts drawing .py script to run in PyMol)";

	if(full)
	{
		output << R"(
    --graphics-title                                 string     title to use for the graphics objects generated by the contacts drawing script
    --graphics-restrict-representations              strings    space-separated list of representations to output, e.g.: balls faces wireframe xspheres lattice
    --graphics-restrict-chains                       strings    space-separated list of chain IDs to include in the output, e.g.: A B
    --graphics-restrict-chain-pairs                  strings    space-separated list of pairs of chain IDs to include in the output, e.g.: A B A C B C
    --graphics-color-balls                           string     hex-coded color for balls, default is '0x00FFFF'
    --graphics-color-faces                           string     hex-coded color for faces, default is '0xFFFF00'
    --graphics-color-wireframe                       string     hex-coded color for wireframe, default is '0x808080'
    --graphics-color-xspheres                        string     hex-coded color for xspheres (expanded spheres), default is '0x00FF00'
    --graphics-color-lattice                         string     hex-coded color for lattice (periodic boundaries), default is '0x00FF00')";
	}
	else
	{
		output << R"(
    --graphics-*                                     various    other graphics output configuration options, can be listed with '--help-full' flag)";
	}

	output << R"(
    --mesh-output-obj-file                           string     output file path to write contacts surfaces mesh .obj file
    --mesh-print-topology-summary                               flag to print mesh topology summary)";

	output << R"(
    --measure-running-time                                      flag to measure and output running times
    --write-log-to-file                              string     output file path to write global log, does not turn off printing log to stderr
    --help-full                                                 flag to print full help (for all options) to stderr and exit
    --help | -h                                                 flag to print help (for basic options) to stderr and exit

Standard input stream:
    Several input formats are supported:
      a) Space-separated or tab-separated header-less table of balls, one of the following line formats possible:
             x y z radius
             chainID x y z radius
             chainID residueID x y z radius
             chainID residueID atomName x y z radius
      b) Output of 'voronota get-balls-from-atoms-file' is acceptable, where line format is:
             x y z radius # atomSerial chainID resSeq resName atomName altLoc iCode
      c) PDB file
      d) mmCIF file

Standard output stream:
    Requested tables with headers, with column values tab-separated

Standard error output stream:
    Log (a name-value pair line), error messages

Usage examples:

    cat ./2zsk.pdb | voronota-lt --print-contacts

    voronota-lt -i ./2zsk.pdb --print-contacts

    voronota-lt --input ./2zsk.pdb --print-contacts-residue-level --compute-only-inter-residue-contacts

    voronota-lt --input ./balls.xyzr --processors 8 --write-contacts-to-file ./contacts.tsv --write-cells-to-file ./cells.tsv

    voronota-lt -i ./balls.xyzr --probe 2 --periodic-box-corners 0 0 0 100 100 300 --processors 8 --write-cells-to-file ./cells.tsv
)";
}

class ApplicationParameters
{
public:
	struct RunningMode
	{
		enum ID
		{
			radical,
			simplified_aw,
#ifdef VORONOTALT_WITH_TEST_MODES
			test_updateable,
			test_updateable_with_backup,
			test_updateable_extensively,
			test_maskable,
			test_second_order_cell_volumes_calculation,
			test_raw_collisions,
#endif
		};
	};

	unsigned int max_number_of_processors;
	voronotalt::Float probe;
	bool compute_only_inter_residue_contacts;
	bool compute_only_inter_chain_contacts;
	bool pdb_or_mmcif_heteroatoms;
	bool pdb_or_mmcif_hydrogens;
	bool pdb_or_mmcif_as_assembly;
	bool measure_running_time;
	bool print_contacts;
	bool print_contacts_residue_level;
	bool print_contacts_chain_level;
	bool print_cells;
	bool print_cells_residue_level;
	bool print_cells_chain_level;
	bool need_summaries_on_residue_level;
	bool need_summaries_on_chain_level;
	RunningMode::ID running_mode;
	unsigned int graphics_color_balls;
	unsigned int graphics_color_faces;
	unsigned int graphics_color_wireframe;
	unsigned int graphics_color_xspheres;
	unsigned int graphics_color_lattice;
	long mesh_extract_connected_component;
	bool mesh_print_topology_summary;
	bool read_successfuly;
	std::string input_from_file;
	std::vector<voronotalt::SimplePoint> periodic_box_directions;
	std::vector<voronotalt::SimplePoint> periodic_box_corners;
	std::string write_input_balls_to_file;
	std::string write_contacts_to_file;
	std::string write_contacts_residue_level_to_file;
	std::string write_contacts_chain_level_to_file;
	std::string write_cells_to_file;
	std::string write_cells_residue_level_to_file;
	std::string write_cells_chain_level_to_file;
	std::string graphics_output_file;
	std::string graphics_title;
	std::set<std::string> graphics_restrict_representations;
	std::set<std::string> graphics_restrict_chains;
	std::set< std::pair<std::string, std::string> > graphics_restrict_chain_pairs;
	std::string mesh_output_obj_file;
	std::string write_tessellation_edges_to_file;
	std::string write_tessellation_vertices_to_file;
	std::string write_log_to_file;
	std::ostringstream error_log_for_options_parsing;

	ApplicationParameters() noexcept :
		max_number_of_processors(voronotalt::openmp_enabled() ? 2 : 1),
		probe(1.4),
		compute_only_inter_residue_contacts(false),
		compute_only_inter_chain_contacts(false),
		pdb_or_mmcif_heteroatoms(false),
		pdb_or_mmcif_hydrogens(false),
		pdb_or_mmcif_as_assembly(false),
		measure_running_time(false),
		print_contacts(false),
		print_contacts_residue_level(false),
		print_contacts_chain_level(false),
		print_cells(false),
		print_cells_residue_level(false),
		print_cells_chain_level(false),
		need_summaries_on_residue_level(false),
		need_summaries_on_chain_level(false),
		running_mode(RunningMode::radical),
		graphics_color_balls(0x00FFFF),
		graphics_color_faces(0xFFFF00),
		graphics_color_wireframe(0x808080),
		graphics_color_xspheres(0x00FF00),
		graphics_color_lattice(0x00FF00),
		mesh_extract_connected_component(0),
		mesh_print_topology_summary(false),
		read_successfuly(false)
	{
	}

	bool read_from_command_line_args(const int argc, const char** argv) noexcept
	{
		read_successfuly=false;

		{
			const std::vector<voronotalt::CLOParser::Option> cloptions=voronotalt::CLOParser::read_options(argc, argv);

			for(std::size_t i=0;i<cloptions.size();i++)
			{
				const voronotalt::CLOParser::Option& opt=cloptions[i];
				if((opt.name=="help" || opt.name=="h") && opt.is_flag_and_true())
				{
					print_help(false, error_log_for_options_parsing);
					return false;
				}
				else if(opt.name=="help-full" && opt.is_flag_and_true())
				{
					print_help(true, error_log_for_options_parsing);
					return false;
				}
			}

			for(std::size_t i=0;i<cloptions.size();i++)
			{
				const voronotalt::CLOParser::Option& opt=cloptions[i];
				if(opt.name=="probe" && opt.args_doubles.size()==1)
				{
					probe=static_cast<voronotalt::Float>(opt.args_doubles.front());
					if(!(probe>=0.0 && probe<=30.0))
					{
						error_log_for_options_parsing << "Error: invalid command line argument for the rolling probe radius, must be a value from 0.0 to 30.0.\n";
					}
				}
				else if(voronotalt::openmp_enabled() && opt.name=="processors" && opt.args_ints.size()==1)
				{
					max_number_of_processors=static_cast<unsigned int>(opt.args_ints.front());
					if(!(max_number_of_processors>=1 && max_number_of_processors<=1000))
					{
						error_log_for_options_parsing << "Error: invalid command line argument for the maximum number of processors, must be an integer from 1 to 1000.\n";
					}
				}
				else if((opt.name=="input" || opt.name=="i")  && opt.args_strings.size()==1)
				{
					input_from_file=opt.args_strings.front();
				}
				else if(opt.name=="periodic-box-directions" && opt.args_doubles.size()==9)
				{
					periodic_box_directions.resize(3);
					periodic_box_directions[0].x=opt.args_doubles[0];
					periodic_box_directions[0].y=opt.args_doubles[1];
					periodic_box_directions[0].z=opt.args_doubles[2];
					periodic_box_directions[1].x=opt.args_doubles[3];
					periodic_box_directions[1].y=opt.args_doubles[4];
					periodic_box_directions[1].z=opt.args_doubles[5];
					periodic_box_directions[2].x=opt.args_doubles[6];
					periodic_box_directions[2].y=opt.args_doubles[7];
					periodic_box_directions[2].z=opt.args_doubles[8];
				}
				else if(opt.name=="periodic-box-corners" && opt.args_doubles.size()==6)
				{
					periodic_box_corners.resize(2);
					periodic_box_corners[0].x=opt.args_doubles[0];
					periodic_box_corners[0].y=opt.args_doubles[1];
					periodic_box_corners[0].z=opt.args_doubles[2];
					periodic_box_corners[1].x=opt.args_doubles[3];
					periodic_box_corners[1].y=opt.args_doubles[4];
					periodic_box_corners[1].z=opt.args_doubles[5];
				}
				else if(opt.name=="compute-only-inter-residue-contacts" && opt.is_flag())
				{
					compute_only_inter_residue_contacts=opt.is_flag_and_true();
				}
				else if(opt.name=="compute-only-inter-chain-contacts" && opt.is_flag())
				{
					compute_only_inter_chain_contacts=opt.is_flag_and_true();
				}
				else if(opt.name=="run-in-aw-diagram-regime" && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						running_mode=RunningMode::simplified_aw;
					}
				}
				else if(opt.name=="pdb-or-mmcif-heteroatoms" && opt.is_flag())
				{
					pdb_or_mmcif_heteroatoms=opt.is_flag_and_true();
				}
				else if(opt.name=="pdb-or-mmcif-hydrogens" && opt.is_flag())
				{
					pdb_or_mmcif_hydrogens=opt.is_flag_and_true();
				}
				else if(opt.name=="pdb-or-mmcif-join-models" && opt.is_flag())
				{
					pdb_or_mmcif_as_assembly=opt.is_flag_and_true();
				}
				else if(opt.name=="measure-running-time" && opt.is_flag())
				{
					measure_running_time=opt.is_flag_and_true();
				}
				else if(opt.name=="print-contacts" && opt.is_flag())
				{
					print_contacts=opt.is_flag_and_true();
				}
				else if(opt.name=="print-contacts-residue-level" && opt.is_flag())
				{
					print_contacts_residue_level=opt.is_flag_and_true();
				}
				else if(opt.name=="print-contacts-chain-level" && opt.is_flag())
				{
					print_contacts_chain_level=opt.is_flag_and_true();
				}
				else if(opt.name=="print-cells" && opt.is_flag())
				{
					print_cells=opt.is_flag_and_true();
				}
				else if(opt.name=="print-cells-residue-level" && opt.is_flag())
				{
					print_cells_residue_level=opt.is_flag_and_true();
				}
				else if(opt.name=="print-cells-chain-level" && opt.is_flag())
				{
					print_cells_chain_level=opt.is_flag_and_true();
				}
				else if(opt.name=="print-everything" && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						print_contacts=true;
						print_contacts_residue_level=true;
						print_contacts_chain_level=true;
						print_cells=true;
						print_cells_residue_level=true;
						print_cells_chain_level=true;
					}
				}
				else if(opt.name=="write-input-balls-to-file" && opt.args_strings.size()==1)
				{
					write_input_balls_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-contacts-to-file" && opt.args_strings.size()==1)
				{
					write_contacts_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-contacts-residue-level-to-file" && opt.args_strings.size()==1)
				{
					write_contacts_residue_level_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-contacts-chain-level-to-file" && opt.args_strings.size()==1)
				{
					write_contacts_chain_level_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-cells-to-file" && opt.args_strings.size()==1)
				{
					write_cells_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-cells-residue-level-to-file" && opt.args_strings.size()==1)
				{
					write_cells_residue_level_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-cells-chain-level-to-file" && opt.args_strings.size()==1)
				{
					write_cells_chain_level_to_file=opt.args_strings.front();
				}
				else if(opt.name=="graphics-output-file" && opt.args_strings.size()==1)
				{
					graphics_output_file=opt.args_strings.front();
				}
				else if(opt.name=="graphics-title" && opt.args_strings.size()==1)
				{
					graphics_title=opt.args_strings.front();
				}
				else if(opt.name=="graphics-restrict-representations" && !opt.args_strings.empty())
				{
					graphics_restrict_representations=std::set<std::string>(opt.args_strings.begin(), opt.args_strings.end());
				}
				else if(opt.name=="graphics-restrict-chains" && !opt.args_strings.empty())
				{
					graphics_restrict_chains=std::set<std::string>(opt.args_strings.begin(), opt.args_strings.end());
				}
				else if(opt.name=="graphics-restrict-chain-pairs" && !opt.args_strings.empty() && opt.args_strings.size()%2==0)
				{
					for(std::size_t j=0;j<opt.args_strings.size();j+=2)
					{
						graphics_restrict_chain_pairs.insert(std::make_pair(opt.args_strings[j], opt.args_strings[j+1]));
						graphics_restrict_chain_pairs.insert(std::make_pair(opt.args_strings[j+1], opt.args_strings[j]));
					}
				}
				else if(opt.name=="graphics-color-balls" && opt.args_hexints.size()==1)
				{
					graphics_color_balls=opt.args_hexints.front();
				}
				else if(opt.name=="graphics-color-faces" && opt.args_hexints.size()==1)
				{
					graphics_color_faces=opt.args_hexints.front();
				}
				else if(opt.name=="graphics-color-wireframe" && opt.args_hexints.size()==1)
				{
					graphics_color_wireframe=opt.args_hexints.front();
				}
				else if(opt.name=="graphics-color-xspheres" && opt.args_hexints.size()==1)
				{
					graphics_color_xspheres=opt.args_hexints.front();
				}
				else if(opt.name=="graphics-color-lattice" && opt.args_hexints.size()==1)
				{
					graphics_color_lattice=opt.args_hexints.front();
				}
				else if(opt.name=="mesh-output-obj-file" && opt.args_strings.size()==1)
				{
					mesh_output_obj_file=opt.args_strings.front();
				}
				else if(opt.name=="mesh-print-topology-summary" && opt.is_flag())
				{
					mesh_print_topology_summary=opt.is_flag_and_true();
				}
				else if(opt.name=="mesh-extract-connected-component" && opt.args_ints.size()==1)
				{
					mesh_extract_connected_component=static_cast<long>(opt.args_ints.front());
				}
				else if(opt.name=="write-tessellation-edges-to-file" && opt.args_strings.size()==1)
				{
					write_tessellation_edges_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-tessellation-vertices-to-file" && opt.args_strings.size()==1)
				{
					write_tessellation_vertices_to_file=opt.args_strings.front();
				}
				else if(opt.name=="write-log-to-file" && opt.args_strings.size()==1)
				{
					write_log_to_file=opt.args_strings.front();
				}
#ifdef VORONOTALT_WITH_TEST_MODES
				else if((opt.name=="test-updateable-tessellation") && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						running_mode=RunningMode::test_updateable;
					}
				}
				else if((opt.name=="test-updateable-tessellation-with-backup") && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						running_mode=RunningMode::test_updateable_with_backup;
					}
				}
				else if((opt.name=="test-updateable-tessellation-extensively") && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						running_mode=RunningMode::test_updateable_extensively;
					}
				}
				else if((opt.name=="test-maskable-tessellation") && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						running_mode=RunningMode::test_maskable;
					}
				}
				else if((opt.name=="test-second-order-cell-volumes-calculation") && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						running_mode=RunningMode::test_second_order_cell_volumes_calculation;
					}
				}
				else if((opt.name=="test-raw-collisions") && opt.is_flag())
				{
					if(opt.is_flag_and_true())
					{
						running_mode=RunningMode::test_raw_collisions;
					}
				}
#endif /* VORONOTALT_WITH_TEST_MODES */
				else if(opt.name.empty())
				{
					error_log_for_options_parsing << "Error: unnamed command line arguments detected.\n";
				}
				else
				{
					error_log_for_options_parsing << "Error: invalid command line option '" << opt.name << "'.\n";
				}
			}
		}

		if((input_from_file.empty() || input_from_file=="_stdin") && voronotalt::is_stdin_from_terminal())
		{
			error_log_for_options_parsing << "Error: no input provided to stdin or from a file, please provide input or run with an -h or --help flag to see documentation and examples.\n";
		}

		if(running_mode==RunningMode::simplified_aw && !(periodic_box_directions.empty() && periodic_box_corners.empty()))
		{
			error_log_for_options_parsing << "Error: in this version a periodic box cannot be used in the simplified additively weighted Voronoi diagram regime.\n";
		}

		if(running_mode==RunningMode::simplified_aw && (!mesh_output_obj_file.empty() || mesh_print_topology_summary))
		{
			error_log_for_options_parsing << "Error: in this version mesh output and analysis is disabled for the simplified additively weighted Voronoi diagram regime.\n";
		}

		if(running_mode==RunningMode::simplified_aw && !(write_tessellation_edges_to_file.empty() && write_tessellation_vertices_to_file.empty()))
		{
			error_log_for_options_parsing << "Error: in this version tessellation edges and vertices output is disabled for the simplified additively weighted Voronoi diagram regime.\n";
		}

		if(!periodic_box_directions.empty() && !periodic_box_corners.empty())
		{
			error_log_for_options_parsing << "Error: cannot use both the periodic box directions and the periodic box corners.\n";
		}

		if(!periodic_box_directions.empty() && periodic_box_directions.size()!=3)
		{
			error_log_for_options_parsing << "Error: not exactly three periodic box directions provided.\n";
		}

		if(!periodic_box_corners.empty() && periodic_box_corners.size()!=2)
		{
			error_log_for_options_parsing << "Error: not exactly two periodic box corners provided.\n";
		}

		if(!graphics_output_file.empty() && graphics_restrict_representations.empty())
		{
			graphics_restrict_representations.insert("balls");
			graphics_restrict_representations.insert("faces");
			graphics_restrict_representations.insert("wireframe");
			if(!periodic_box_directions.empty() || !periodic_box_corners.empty())
			{
				graphics_restrict_representations.insert("lattice");
			}
		}

		read_successfuly=error_log_for_options_parsing.str().empty();

		if(read_successfuly)
		{
			need_summaries_on_residue_level=(print_contacts_residue_level || print_cells_residue_level || !write_contacts_residue_level_to_file.empty() || !write_cells_residue_level_to_file.empty());
			need_summaries_on_chain_level=(print_contacts_chain_level || print_cells_chain_level || !write_contacts_chain_level_to_file.empty() || !write_cells_chain_level_to_file.empty());
		}

		return read_successfuly;
	}
};

class ApplicationLogRecorders
{
public:
	voronotalt::TimeRecorderChrono time_recoder_for_all;
	voronotalt::TimeRecorderChrono time_recoder_for_input;
	voronotalt::TimeRecorderChrono time_recoder_for_tessellation;
	voronotalt::TimeRecorderChrono time_recoder_for_output;
	std::ostringstream log_output;

	explicit ApplicationLogRecorders(const ApplicationParameters& app_params) noexcept :
		time_recoder_for_all(app_params.measure_running_time),
		time_recoder_for_input(app_params.measure_running_time),
		time_recoder_for_tessellation(app_params.measure_running_time),
		time_recoder_for_output(app_params.measure_running_time)
	{
	}

	void finalize_and_output(const ApplicationParameters& app_params) noexcept
	{
		if(app_params.measure_running_time)
		{
#ifdef VORONOTALT_OPENMP
			log_output << "log_openmp_threads\t" << omp_get_max_threads() << "\n";
#endif
			time_recoder_for_input.print_recordings(log_output, "log time input stage", true);
			time_recoder_for_tessellation.print_recordings(log_output, "log time tessellation stage", true);
			time_recoder_for_output.print_recordings(log_output, "log time output stage", true);
			time_recoder_for_all.print_elapsed_time(log_output, "log time full program");
		}

		std::cerr << log_output.str();

		if(!app_params.write_log_to_file.empty())
		{
			std::ofstream foutput(app_params.write_log_to_file.c_str(), std::ios::out);
			if(foutput.good())
			{
				foutput << log_output.str();
			}
			else
			{
				std::cerr << "Error (non-terminating): failed to write log to file '" << app_params.write_log_to_file << "'\n";
			}
		}
	}
};

class ApplicationGraphicsRecorder
{
public:
	static inline bool allow_representation(const std::set<std::string>& restrict_representations, const std::string& representation) noexcept
	{
		return (restrict_representations.empty() || restrict_representations.count(representation)>0);
	}

	static inline bool allow_ball_group(const std::set<std::string>& restrict_chains, const voronotalt::SpheresInput::Result& spheres_input_result, const std::size_t index) noexcept
	{
		if(!spheres_input_result.sphere_labels.empty() && !restrict_chains.empty())
		{
			const voronotalt::UnsignedInt N=spheres_input_result.sphere_labels.size();
			return (restrict_chains.count(spheres_input_result.sphere_labels[index%N].chain_id)>0);
		}
		return true;
	}

	static inline bool allow_contact_group(const std::set<std::string>& restrict_chains, const std::set< std::pair<std::string, std::string> >& restrict_chain_pairs, const voronotalt::SpheresInput::Result& spheres_input_result, const std::size_t index1, const std::size_t index2)
	{
		if(!spheres_input_result.sphere_labels.empty() && (!restrict_chains.empty() || !restrict_chain_pairs.empty()))
		{
			const voronotalt::UnsignedInt N=spheres_input_result.sphere_labels.size();
			if(restrict_chain_pairs.empty())
			{
				return (restrict_chains.count(spheres_input_result.sphere_labels[index1%N].chain_id)>0 || restrict_chains.count(spheres_input_result.sphere_labels[index2%N].chain_id)>0);
			}
			else
			{
				return (restrict_chain_pairs.count(std::make_pair(spheres_input_result.sphere_labels[index1%N].chain_id, spheres_input_result.sphere_labels[index2%N].chain_id))>0);
			}
		}
		return true;
	}

	static inline std::string name_ball_group(const std::string& prefix, const voronotalt::SpheresInput::Result& spheres_input_result, const std::size_t index) noexcept
	{
		std::ostringstream output;
		output << prefix;
		if(!spheres_input_result.sphere_labels.empty())
		{
			const voronotalt::UnsignedInt N=spheres_input_result.sphere_labels.size();
			output << "_" << spheres_input_result.sphere_labels[index%N].chain_id;
		}
		return output.str();
	}

	static inline std::string name_contact_group(const std::string& prefix, const voronotalt::SpheresInput::Result& spheres_input_result, const std::size_t index1, const std::size_t index2) noexcept
	{
		std::ostringstream output;
		output << prefix;
		if(!spheres_input_result.sphere_labels.empty())
		{
			const voronotalt::UnsignedInt N=spheres_input_result.sphere_labels.size();
			const bool need_to_swap=(spheres_input_result.sphere_labels[index2%N].chain_id<spheres_input_result.sphere_labels[index1%N].chain_id);
			output << "_" << spheres_input_result.sphere_labels[(need_to_swap? index2 : index1)%N].chain_id;
			output << "_" << spheres_input_result.sphere_labels[(need_to_swap? index1 : index2)%N].chain_id;
		}
		return output.str();
	}

	voronotalt::GraphicsWriter graphics_writer;

	explicit ApplicationGraphicsRecorder(const ApplicationParameters& app_params) noexcept : graphics_writer(!app_params.graphics_output_file.empty())
	{
	}

	void finalize_and_output(const ApplicationParameters& app_params, ApplicationLogRecorders& app_log_recorders) const noexcept
	{
		if(graphics_writer.enabled())
		{
			app_log_recorders.time_recoder_for_output.reset();
			if(!graphics_writer.write_to_file(app_params.graphics_title, app_params.graphics_output_file))
			{
				std::cerr << "Error (non-terminating): failed to write graphics to file '" << app_params.graphics_output_file << "'\n";
			}
			app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("write printed graphics to file");
		}
	}
};

class ApplicationMeshRecorder
{
public:
	voronotalt::MeshWriter mesh_writer;

	explicit ApplicationMeshRecorder(const ApplicationParameters& app_params) noexcept : mesh_writer(!app_params.mesh_output_obj_file.empty() || app_params.mesh_print_topology_summary)
	{
	}

	void finalize_and_output(const ApplicationParameters& app_params, ApplicationLogRecorders& app_log_recorders) noexcept
	{
		if(mesh_writer.enabled())
		{
			app_log_recorders.time_recoder_for_output.reset();
			if(!app_params.mesh_output_obj_file.empty())
			{
				if(!mesh_writer.write_to_obj_file(app_params.mesh_output_obj_file))
				{
					std::cerr << "Error (non-terminating): failed to write mesh to file '" << app_params.mesh_output_obj_file << "'\n";
				}
			}
			if(app_params.mesh_print_topology_summary)
			{
				std::cout << "meshinfo_header\tgenus\tconnected_components\tboundary_components\teuler_characteristic\n"
						<< "meshinfo\t"
						<< mesh_writer.calculate_genus() << "\t"
						<< mesh_writer.get_number_of_connected_components() << "\t"
						<< mesh_writer.get_number_of_boundary_components() << "\t"
						<< mesh_writer.get_euler_characteristic() << "\n";
			}
			app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("write mesh obj to file");
		}
	}
};

void run_mode_radical(
		const ApplicationParameters& app_params,
		const voronotalt::SpheresInput::Result& spheres_input_result,
		ApplicationLogRecorders& app_log_recorders,
		ApplicationGraphicsRecorder& app_graphics_recorder,
		ApplicationMeshRecorder& app_mesh_recorder) noexcept
{
	app_log_recorders.time_recoder_for_tessellation.reset();

	const voronotalt::PeriodicBox periodic_box=voronotalt::PeriodicBox::create_periodic_box_from_shift_directions_or_from_corners(app_params.periodic_box_directions, app_params.periodic_box_corners);

	voronotalt::SpheresContainer spheres_container;

	spheres_container.init(spheres_input_result.spheres, periodic_box, app_log_recorders.time_recoder_for_tessellation);

	voronotalt::RadicalTessellation::Result result;
	voronotalt::RadicalTessellation::ResultGraphics result_graphics;

	{
		const std::vector<int> null_grouping;
		const std::vector<int>& grouping_for_filtering=(app_params.compute_only_inter_chain_contacts ? spheres_input_result.grouping_by_chain : (app_params.compute_only_inter_residue_contacts ? spheres_input_result.grouping_by_residue : null_grouping));
		const bool summarize_cells=grouping_for_filtering.empty();

		voronotalt::RadicalTessellation::construct_full_tessellation(
				spheres_container,
				grouping_for_filtering,
				!(app_params.write_tessellation_edges_to_file.empty() && app_params.write_tessellation_vertices_to_file.empty()),
				(app_graphics_recorder.graphics_writer.enabled() || app_mesh_recorder.mesh_writer.enabled()),
				summarize_cells,
				result,
				result_graphics,
				app_log_recorders.time_recoder_for_tessellation);
	}

	voronotalt::RadicalTessellation::GroupedResult result_grouped_by_residue;
	if(app_params.need_summaries_on_residue_level)
	{
		voronotalt::RadicalTessellation::group_results(result, spheres_input_result.grouping_by_residue, result_grouped_by_residue, app_log_recorders.time_recoder_for_tessellation);
	}

	voronotalt::RadicalTessellation::GroupedResult result_grouped_by_chain;
	if(app_params.need_summaries_on_chain_level)
	{
		voronotalt::RadicalTessellation::group_results(result, spheres_input_result.grouping_by_chain, result_grouped_by_chain, app_log_recorders.time_recoder_for_tessellation);
	}

	app_log_recorders.time_recoder_for_output.reset();

	voronotalt::PrintingCustomTypes::print_tessellation_full_construction_result_log_basic(result, result_grouped_by_residue, result_grouped_by_chain, app_log_recorders.log_output);
	voronotalt::PrintingCustomTypes::print_tessellation_full_construction_result_log_about_cells(result, result_grouped_by_residue, result_grouped_by_chain, app_log_recorders.log_output);

	app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("print total numbers");

	if(app_params.print_contacts)
	{
		voronotalt::PrintingCustomTypes::print_contacts_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, true, std::cout);
	}

	if(!app_params.write_contacts_to_file.empty())
	{
		std::ofstream foutput(app_params.write_contacts_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_contacts_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, true, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write contacts to file '" << app_params.write_contacts_to_file << "'\n";
		}
	}

	if(app_params.print_contacts_residue_level)
	{
		voronotalt::PrintingCustomTypes::print_contacts_residue_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_residue.grouped_contacts_representative_ids, result_grouped_by_residue.grouped_contacts_summaries, std::cout);
	}

	if(!app_params.write_contacts_residue_level_to_file.empty())
	{
		std::ofstream foutput(app_params.write_contacts_residue_level_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_contacts_residue_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_residue.grouped_contacts_representative_ids, result_grouped_by_residue.grouped_contacts_summaries, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write contacts on residue level to file '" << app_params.write_contacts_residue_level_to_file << "'\n";
		}
	}

	if(app_params.print_contacts_chain_level)
	{
		voronotalt::PrintingCustomTypes::print_contacts_chain_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_chain.grouped_contacts_representative_ids, result_grouped_by_chain.grouped_contacts_summaries, std::cout);
	}

	if(!app_params.write_contacts_chain_level_to_file.empty())
	{
		std::ofstream foutput(app_params.write_contacts_chain_level_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_contacts_chain_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_chain.grouped_contacts_representative_ids, result_grouped_by_chain.grouped_contacts_summaries, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write contacts on chain level to file '" << app_params.write_contacts_chain_level_to_file << "'\n";
		}
	}

	app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("print result contacts");

	if(app_params.print_cells)
	{
		voronotalt::PrintingCustomTypes::print_cells_to_stream(result.cells_summaries, spheres_input_result.sphere_labels, true, std::cout);
	}

	if(!app_params.write_cells_to_file.empty())
	{
		std::ofstream foutput(app_params.write_cells_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_cells_to_stream(result.cells_summaries, spheres_input_result.sphere_labels, true, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write cells to file '" << app_params.write_cells_to_file << "'\n";
		}
	}

	if(app_params.print_cells_residue_level)
	{
		voronotalt::PrintingCustomTypes::print_cells_residue_level_to_stream(result.cells_summaries, spheres_input_result.sphere_labels, result_grouped_by_residue.grouped_cells_representative_ids, result_grouped_by_residue.grouped_cells_summaries, std::cout);
	}

	if(!app_params.write_cells_residue_level_to_file.empty())
	{
		std::ofstream foutput(app_params.write_cells_residue_level_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_cells_residue_level_to_stream(result.cells_summaries, spheres_input_result.sphere_labels, result_grouped_by_residue.grouped_cells_representative_ids, result_grouped_by_residue.grouped_cells_summaries, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write cells on residue level to file '" << app_params.write_cells_residue_level_to_file << "'\n";
		}
	}

	if(app_params.print_cells_chain_level)
	{
		voronotalt::PrintingCustomTypes::print_cells_chain_level_to_stream(result.cells_summaries, spheres_input_result.sphere_labels, result_grouped_by_chain.grouped_cells_representative_ids, result_grouped_by_chain.grouped_cells_summaries, std::cout);
	}

	if(!app_params.write_cells_chain_level_to_file.empty())
	{
		std::ofstream foutput(app_params.write_cells_chain_level_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_cells_chain_level_to_stream(result.cells_summaries, spheres_input_result.sphere_labels, result_grouped_by_chain.grouped_cells_representative_ids, result_grouped_by_chain.grouped_cells_summaries, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write cells on chain level to file '" << app_params.write_cells_chain_level_to_file << "'\n";
		}
	}

	app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("print result sas and volumes");

	if(!app_params.write_tessellation_edges_to_file.empty())
	{
		std::ofstream foutput(app_params.write_tessellation_edges_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_sequential_container_simply(result.tessellation_net.tes_edges, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write tessellation edges to file '" << app_params.write_tessellation_edges_to_file << "'\n";
		}
		app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("write tessellation edges");
	}

	if(!app_params.write_tessellation_vertices_to_file.empty())
	{
		std::ofstream foutput(app_params.write_tessellation_vertices_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_sequential_container_simply(result.tessellation_net.tes_vertices, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write tessellation vertices to file '" << app_params.write_tessellation_vertices_to_file << "'\n";
		}
		app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("write tessellation vertices");
	}

	if(app_graphics_recorder.graphics_writer.enabled())
	{
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "balls"))
		{
			app_graphics_recorder.graphics_writer.add_color("balls", "", app_params.graphics_color_balls);
			for(std::size_t i=0;i<spheres_input_result.spheres.size();i++)
			{
				if(ApplicationGraphicsRecorder::allow_ball_group(app_params.graphics_restrict_chains, spheres_input_result, i))
				{
					const std::string group_name=ApplicationGraphicsRecorder::name_ball_group("atoms", spheres_input_result, i);
					if(app_params.graphics_color_balls==0)
					{
						app_graphics_recorder.graphics_writer.add_random_color("balls", group_name);
					}
					app_graphics_recorder.graphics_writer.add_sphere("balls", group_name, spheres_input_result.spheres[i], app_params.probe);
				}
			}
		}
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "faces"))
		{
			app_graphics_recorder.graphics_writer.add_color("faces", "", app_params.graphics_color_faces);
			for(std::size_t i=0;i<result_graphics.contacts_graphics.size();i++)
			{
				const voronotalt::RadicalTessellation::ContactDescriptorSummary& pair_summary=(i<result.contacts_summaries_with_redundancy_in_periodic_box.size() ? result.contacts_summaries_with_redundancy_in_periodic_box[i] : result.contacts_summaries[i]);
				if(ApplicationGraphicsRecorder::allow_contact_group(app_params.graphics_restrict_chains, app_params.graphics_restrict_chain_pairs, spheres_input_result, pair_summary.id_a, pair_summary.id_b))
				{
					const std::string group_name=ApplicationGraphicsRecorder::name_contact_group("contacts", spheres_input_result, pair_summary.id_a, pair_summary.id_b);
					if(app_params.graphics_color_faces==0)
					{
						app_graphics_recorder.graphics_writer.add_random_color("faces", group_name);
					}
					const voronotalt::RadicalTessellationContactConstruction::ContactDescriptorGraphics& pair_graphics=result_graphics.contacts_graphics[i];
					app_graphics_recorder.graphics_writer.add_triangle_fan("faces", group_name, pair_graphics.outer_points, pair_graphics.barycenter, pair_graphics.plane_normal);
				}
			}
		}
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "wireframe"))
		{
			app_graphics_recorder.graphics_writer.add_color("wireframe", "", app_params.graphics_color_wireframe);
			for(std::size_t i=0;i<result_graphics.contacts_graphics.size();i++)
			{
				const voronotalt::RadicalTessellation::ContactDescriptorSummary& pair_summary=(i<result.contacts_summaries_with_redundancy_in_periodic_box.size() ? result.contacts_summaries_with_redundancy_in_periodic_box[i] : result.contacts_summaries[i]);
				if(ApplicationGraphicsRecorder::allow_contact_group(app_params.graphics_restrict_chains, app_params.graphics_restrict_chain_pairs, spheres_input_result, pair_summary.id_a, pair_summary.id_b))
				{
					const voronotalt::RadicalTessellationContactConstruction::ContactDescriptorGraphics& pair_graphics=result_graphics.contacts_graphics[i];
					app_graphics_recorder.graphics_writer.add_line_loop("wireframe", ApplicationGraphicsRecorder::name_contact_group("contacts", spheres_input_result, pair_summary.id_a, pair_summary.id_b), pair_graphics.outer_points);
				}
			}
		}
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "xspheres"))
		{
			app_graphics_recorder.graphics_writer.add_alpha("xspheres", "", 0.5);
			app_graphics_recorder.graphics_writer.add_color("xspheres", "", app_params.graphics_color_xspheres);
			for(std::size_t i=0;i<spheres_input_result.spheres.size();i++)
			{
				if(ApplicationGraphicsRecorder::allow_ball_group(app_params.graphics_restrict_chains, spheres_input_result, i))
				{
					app_graphics_recorder.graphics_writer.add_sphere("xspheres", ApplicationGraphicsRecorder::name_ball_group("atoms", spheres_input_result, i), spheres_input_result.spheres[i], 0.0);
				}
			}
		}
		if(periodic_box.enabled() && ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "lattice"))
		{
			voronotalt::SimplePoint origin;
			std::vector<voronotalt::SimplePoint> directions;
			if(app_params.periodic_box_directions.size()==3)
			{
				directions=app_params.periodic_box_directions;
			}
			else if(app_params.periodic_box_corners.size()==2)
			{
				const voronotalt::SimplePoint corner_min(
						std::min(app_params.periodic_box_corners[0].x, app_params.periodic_box_corners[1].x),
						std::min(app_params.periodic_box_corners[0].y, app_params.periodic_box_corners[1].y),
						std::min(app_params.periodic_box_corners[0].z, app_params.periodic_box_corners[1].z));
				const voronotalt::SimplePoint corner_max(
						std::max(app_params.periodic_box_corners[0].x, app_params.periodic_box_corners[1].x),
						std::max(app_params.periodic_box_corners[0].y, app_params.periodic_box_corners[1].y),
						std::max(app_params.periodic_box_corners[0].z, app_params.periodic_box_corners[1].z));
				origin=corner_min;
				directions.resize(3, voronotalt::SimplePoint());
				directions[0].x=corner_max.x-corner_min.x;
				directions[1].y=corner_max.y-corner_min.y;
				directions[2].z=corner_max.z-corner_min.z;
			}
			if(directions.size()==3)
			{
				app_graphics_recorder.graphics_writer.add_color("lattice", "", app_params.graphics_color_lattice);
				for(int i=0;i<3;i++)
				{
					for(int wa=0;wa<=1;wa++)
					{
						for(int wb=0;wb<=1;wb++)
						{
							const int wx=(i==0 ? -1 : wa);
							const int wy=(i==1 ? -1 : (i==0 ? wa : wb));
							const int wz=(i==2 ? -1 : wb);
							voronotalt::SimplePoint o=origin;
							o=voronotalt::sum_of_points(o, voronotalt::point_and_number_product(directions[0], static_cast<voronotalt::Float>(wx)));
							o=voronotalt::sum_of_points(o, voronotalt::point_and_number_product(directions[1], static_cast<voronotalt::Float>(wy)));
							o=voronotalt::sum_of_points(o, voronotalt::point_and_number_product(directions[2], static_cast<voronotalt::Float>(wz)));
							app_graphics_recorder.graphics_writer.add_line("lattice", "borders", o, voronotalt::sum_of_points(o, voronotalt::point_and_number_product(directions[i], FLOATCONST(3.0))));
						}
					}
				}
			}
		}
		app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("print graphics");
	}

	if(app_mesh_recorder.mesh_writer.enabled())
	{
		{
			voronotalt::MeshWriter::ChooserOfBestCoordinateID chooser;
			for(std::size_t i=0;i<result_graphics.contacts_graphics.size();i++)
			{
				chooser.feed(result_graphics.contacts_graphics[i].barycenter);
			}
			const unsigned int chosen_best_coordinate_id=chooser.choose_best_coordinate_id();
			if(chosen_best_coordinate_id>0)
			{
				app_mesh_recorder.mesh_writer=voronotalt::MeshWriter(true, chosen_best_coordinate_id);
			}
		}

		for(std::size_t i=0;i<result_graphics.contacts_graphics.size();i++)
		{
			const voronotalt::RadicalTessellationContactConstruction::ContactDescriptorGraphics& pair_graphics=result_graphics.contacts_graphics[i];
			app_mesh_recorder.mesh_writer.add_triangle_fan(pair_graphics.outer_points, pair_graphics.boundary_mask, pair_graphics.barycenter);
		}

		app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("collect mesh");

		if(app_params.mesh_extract_connected_component>0)
		{
			voronotalt::MeshWriter submesh_writer(false);
			if(app_params.mesh_extract_connected_component>app_mesh_recorder.mesh_writer.get_number_of_connected_components())
			{
				std::cerr << "Error (non-terminating): could not extract mesh connected component " << app_params.mesh_extract_connected_component << " because there are only " << app_mesh_recorder.mesh_writer.get_number_of_connected_components() << " connected components in total\n";
			}
			else
			{
				if(!app_mesh_recorder.mesh_writer.extract_connected_component(app_params.mesh_extract_connected_component, submesh_writer))
				{
					std::cerr << "Error (non-terminating): could not extract mesh connected component " << app_params.mesh_extract_connected_component << "\n";
				}
			}
			app_mesh_recorder.mesh_writer=submesh_writer;
		}

		if(app_mesh_recorder.mesh_writer.enabled())
		{
			app_log_recorders.log_output << "log_mesh_number_of_vertices\t" << app_mesh_recorder.mesh_writer.get_number_of_vertices() << "\n";
			app_log_recorders.log_output << "log_mesh_connected_components\t" << app_mesh_recorder.mesh_writer.get_number_of_connected_components() << "\n";
			app_log_recorders.log_output << "log_mesh_boundary_components\t" << app_mesh_recorder.mesh_writer.get_number_of_boundary_components() << "\n";
			app_log_recorders.log_output << "log_mesh_euler_characteristic\t" << app_mesh_recorder.mesh_writer.get_euler_characteristic() << "\n";
			app_log_recorders.log_output << "log_mesh_genus\t" << app_mesh_recorder.mesh_writer.calculate_genus() << "\n";
		}

		app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("analyze mesh");
	}
}

void run_mode_simplified_aw(
		const ApplicationParameters& app_params,
		const voronotalt::SpheresInput::Result& spheres_input_result,
		ApplicationLogRecorders& app_log_recorders,
		ApplicationGraphicsRecorder& app_graphics_recorder) noexcept
{
	app_log_recorders.time_recoder_for_tessellation.reset();

	voronotalt::SimplifiedAWTessellation::Result result;
	voronotalt::SimplifiedAWTessellation::ResultGraphics result_graphics;

	{
		const std::vector<int> null_grouping;
		const std::vector<int>& grouping_for_filtering=(app_params.compute_only_inter_chain_contacts ? spheres_input_result.grouping_by_chain : (app_params.compute_only_inter_residue_contacts ? spheres_input_result.grouping_by_residue : null_grouping));

		voronotalt::SimplifiedAWTessellation::construct_full_tessellation(spheres_input_result.spheres, grouping_for_filtering, app_graphics_recorder.graphics_writer.enabled(), result, result_graphics, app_log_recorders.time_recoder_for_tessellation);
	}

	voronotalt::SimplifiedAWTessellation::GroupedResult result_grouped_by_residue;
	if(app_params.need_summaries_on_residue_level)
	{
		voronotalt::SimplifiedAWTessellation::group_results(result, spheres_input_result.grouping_by_residue, result_grouped_by_residue, app_log_recorders.time_recoder_for_tessellation);
	}

	voronotalt::SimplifiedAWTessellation::GroupedResult result_grouped_by_chain;
	if(app_params.need_summaries_on_chain_level)
	{
		voronotalt::SimplifiedAWTessellation::group_results(result, spheres_input_result.grouping_by_chain, result_grouped_by_chain, app_log_recorders.time_recoder_for_tessellation);
	}

	app_log_recorders.time_recoder_for_output.reset();

	voronotalt::PrintingCustomTypes::print_tessellation_full_construction_result_log_basic(result, result_grouped_by_residue, result_grouped_by_chain, app_log_recorders.log_output);

	app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("print total numbers");

	if(app_params.print_contacts)
	{
		voronotalt::PrintingCustomTypes::print_contacts_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, true, std::cout);
	}

	if(!app_params.write_contacts_to_file.empty())
	{
		std::ofstream foutput(app_params.write_contacts_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_contacts_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, true, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write contacts to file '" << app_params.write_contacts_to_file << "'\n";
		}
	}

	if(app_params.print_contacts_residue_level)
	{
		voronotalt::PrintingCustomTypes::print_contacts_residue_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_residue.grouped_contacts_representative_ids, result_grouped_by_residue.grouped_contacts_summaries, std::cout);
	}

	if(!app_params.write_contacts_residue_level_to_file.empty())
	{
		std::ofstream foutput(app_params.write_contacts_residue_level_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_contacts_residue_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_residue.grouped_contacts_representative_ids, result_grouped_by_residue.grouped_contacts_summaries, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write contacts on residue level to file '" << app_params.write_contacts_residue_level_to_file << "'\n";
		}
	}

	if(app_params.print_contacts_chain_level)
	{
		voronotalt::PrintingCustomTypes::print_contacts_chain_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_chain.grouped_contacts_representative_ids, result_grouped_by_chain.grouped_contacts_summaries, std::cout);
	}

	if(!app_params.write_contacts_chain_level_to_file.empty())
	{
		std::ofstream foutput(app_params.write_contacts_chain_level_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_contacts_chain_level_to_stream(result.contacts_summaries, spheres_input_result.sphere_labels, result_grouped_by_chain.grouped_contacts_representative_ids, result_grouped_by_chain.grouped_contacts_summaries, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write contacts on chain level to file '" << app_params.write_contacts_chain_level_to_file << "'\n";
		}
	}

	app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("print result contacts");

	if(app_graphics_recorder.graphics_writer.enabled())
	{
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "balls"))
		{
			app_graphics_recorder.graphics_writer.add_color("balls", "", app_params.graphics_color_balls);
			for(std::size_t i=0;i<spheres_input_result.spheres.size();i++)
			{
				if(ApplicationGraphicsRecorder::allow_ball_group(app_params.graphics_restrict_chains, spheres_input_result, i))
				{
					const std::string group_name=ApplicationGraphicsRecorder::name_ball_group("atoms", spheres_input_result, i);
					if(app_params.graphics_color_balls==0)
					{
						app_graphics_recorder.graphics_writer.add_random_color("balls", group_name);
					}
					app_graphics_recorder.graphics_writer.add_sphere("balls", group_name, spheres_input_result.spheres[i], app_params.probe);
				}
			}
		}
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "faces"))
		{
			app_graphics_recorder.graphics_writer.add_color("faces", "", app_params.graphics_color_faces);
			for(std::size_t i=0;i<result_graphics.contacts_graphics.size();i++)
			{
				const voronotalt::SimplifiedAWTessellation::ContactDescriptorSummary& pair_summary=result.contacts_summaries[i];
				if(ApplicationGraphicsRecorder::allow_contact_group(app_params.graphics_restrict_chains, app_params.graphics_restrict_chain_pairs, spheres_input_result, pair_summary.id_a, pair_summary.id_b))
				{
					const std::string group_name=ApplicationGraphicsRecorder::name_contact_group("contacts", spheres_input_result, pair_summary.id_a, pair_summary.id_b);
					if(app_params.graphics_color_faces==0)
					{
						app_graphics_recorder.graphics_writer.add_random_color("faces", group_name);
					}
					const voronotalt::SimplifiedAWTessellationContactConstruction::ContactDescriptorGraphics& pair_graphics=result_graphics.contacts_graphics[i];
					for(std::size_t j=0;j<pair_graphics.contours_graphics.size();j++)
					{
						app_graphics_recorder.graphics_writer.add_triangle_fan("faces", group_name, pair_graphics.contours_graphics[j].outer_points, pair_graphics.contours_graphics[j].barycenter, spheres_input_result.spheres[pair_summary.id_a].p, spheres_input_result.spheres[pair_summary.id_b].p);
					}
				}
			}
		}
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "wireframe"))
		{
			app_graphics_recorder.graphics_writer.add_color("wireframe", "", app_params.graphics_color_wireframe);
			for(std::size_t i=0;i<result_graphics.contacts_graphics.size();i++)
			{
				const voronotalt::SimplifiedAWTessellation::ContactDescriptorSummary& pair_summary=result.contacts_summaries[i];
				if(ApplicationGraphicsRecorder::allow_contact_group(app_params.graphics_restrict_chains, app_params.graphics_restrict_chain_pairs, spheres_input_result, pair_summary.id_a, pair_summary.id_b))
				{
					const std::string group_name=ApplicationGraphicsRecorder::name_contact_group("contacts", spheres_input_result, pair_summary.id_a, pair_summary.id_b);
					const voronotalt::SimplifiedAWTessellationContactConstruction::ContactDescriptorGraphics& pair_graphics=result_graphics.contacts_graphics[i];
					for(std::size_t j=0;j<pair_graphics.contours_graphics.size();j++)
					{
						app_graphics_recorder.graphics_writer.add_line_loop("wireframe", group_name, pair_graphics.contours_graphics[j].outer_points);
					}
				}
			}
		}
		if(ApplicationGraphicsRecorder::allow_representation(app_params.graphics_restrict_representations, "xspheres"))
		{
			app_graphics_recorder.graphics_writer.add_alpha("xspheres", "", 0.5);
			app_graphics_recorder.graphics_writer.add_color("xspheres", "", app_params.graphics_color_xspheres);
			for(std::size_t i=0;i<spheres_input_result.spheres.size();i++)
			{
				if(ApplicationGraphicsRecorder::allow_ball_group(app_params.graphics_restrict_chains, spheres_input_result, i))
				{
					app_graphics_recorder.graphics_writer.add_sphere("xspheres", ApplicationGraphicsRecorder::name_ball_group("atoms", spheres_input_result, i), spheres_input_result.spheres[i], 0.0);
				}
			}
		}
		app_log_recorders.time_recoder_for_output.record_elapsed_miliseconds_and_reset("print graphics");
	}
}

#ifdef VORONOTALT_WITH_TEST_MODES

void run_mode_test_updateable(
		const ApplicationParameters& app_params,
		voronotalt::SpheresInput::Result& spheres_input_result,
		ApplicationLogRecorders& app_log_recorders) noexcept
{
	app_log_recorders.time_recoder_for_tessellation.reset();

	const voronotalt::PeriodicBox periodic_box=voronotalt::PeriodicBox::create_periodic_box_from_shift_directions_or_from_corners(app_params.periodic_box_directions, app_params.periodic_box_corners);

	voronotalt::UpdateableRadicalTessellation urt(app_params.running_mode==ApplicationParameters::RunningMode::test_updateable_with_backup);

	urt.init(spheres_input_result.spheres, periodic_box, app_log_recorders.time_recoder_for_tessellation);

	voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_first;
	voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_last_before_last;
	voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_last;

	{
		voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary=urt.result_summary();
		app_log_recorders.log_output << "log_urt_init_local_update_balls_count\t" << urt.last_update_ids_of_affected_input_spheres().size() << "\n";
		app_log_recorders.log_output << "log_urt_init_total_contacts_area\t" << result_summary.total_contacts_summary.area << "\n";
		app_log_recorders.log_output << "log_urt_init_total_cells_sas_area\t" << result_summary.total_cells_summary.sas_area << "\n\n";
		result_summary_first=result_summary;
	}

	{
		std::vector<voronotalt::UnsignedInt> ids_of_changed_input_spheres;
		for(voronotalt::UnsignedInt i=0;i<25 && i<spheres_input_result.spheres.size();i++)
		{
			ids_of_changed_input_spheres.push_back(i);
		}

		{
			urt.update(spheres_input_result.spheres, ids_of_changed_input_spheres, app_log_recorders.time_recoder_for_tessellation);
			voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary=urt.result_summary();
			app_log_recorders.log_output << "log_urt_upd0_local_update_balls_count\t" << urt.last_update_ids_of_affected_input_spheres().size() << "\n";
			app_log_recorders.log_output << "log_urt_upd0_total_contacts_area\t" << result_summary.total_contacts_summary.area << "\n";
			app_log_recorders.log_output << "log_urt_upd0_total_cells_sas_area\t" << result_summary.total_cells_summary.sas_area << "\n\n";
		}

		for(int a=1;a<=10;a++)
		{
			const double move_direction=(a<=5 ? 1.0 : -1.0);
			const voronotalt::SimplePoint move(move_direction, move_direction, move_direction);
			for(voronotalt::UnsignedInt i=0;i<ids_of_changed_input_spheres.size();i++)
			{
				voronotalt::SimpleSphere& sphere_to_move=spheres_input_result.spheres[ids_of_changed_input_spheres[i]];
				sphere_to_move.p=voronotalt::sum_of_points(sphere_to_move.p, move);
			}
			urt.update(spheres_input_result.spheres, ids_of_changed_input_spheres, app_log_recorders.time_recoder_for_tessellation);
			voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary=urt.result_summary();
			app_log_recorders.log_output << "log_urt_upd" << a << "_local_update_balls_count\t" << urt.last_update_ids_of_affected_input_spheres().size() << "\n";
			app_log_recorders.log_output << "log_urt_upd" << a << "_total_contacts_area\t" << result_summary.total_contacts_summary.area << "\n";
			app_log_recorders.log_output << "log_urt_upd" << a << "_total_cells_sas_area\t" << result_summary.total_cells_summary.sas_area << "\n\n";
			result_summary_last_before_last=result_summary_last;
			result_summary_last=result_summary;
		}
	}

	app_log_recorders.log_output << "log_urt_diff_total_contacts_count\t" << (result_summary_first.total_contacts_summary.count-result_summary_last.total_contacts_summary.count) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_contacts_area\t" << (result_summary_first.total_contacts_summary.area-result_summary_last.total_contacts_summary.area) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_count\t" << (result_summary_first.total_cells_summary.count-result_summary_last.total_cells_summary.count) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_sas_area\t" << (result_summary_first.total_cells_summary.sas_area-result_summary_last.total_cells_summary.sas_area) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_sas_inside_volume\t" << (result_summary_first.total_cells_summary.sas_inside_volume-result_summary_last.total_cells_summary.sas_inside_volume) << "\n\n";

	if(urt.backup_enabled())
	{
		app_log_recorders.log_output << "log_urt_backup_done\t" << urt.restore_from_backup() << "\n";
		voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_after_restoring=urt.result_summary();
		app_log_recorders.log_output << "log_urt_backup_diff_total_contacts_count\t" << (result_summary_last_before_last.total_contacts_summary.count-result_summary_after_restoring.total_contacts_summary.count) << "\n";
		app_log_recorders.log_output << "log_urt_backup_diff_total_contacts_area\t" << (result_summary_last_before_last.total_contacts_summary.area-result_summary_after_restoring.total_contacts_summary.area) << "\n";
		app_log_recorders.log_output << "log_urt_backup_diff_total_cells_count\t" << (result_summary_last_before_last.total_cells_summary.count-result_summary_after_restoring.total_cells_summary.count) << "\n";
		app_log_recorders.log_output << "log_urt_backup_diff_total_cells_sas_area\t" << (result_summary_last_before_last.total_cells_summary.sas_area-result_summary_after_restoring.total_cells_summary.sas_area) << "\n";
		app_log_recorders.log_output << "log_urt_backup_diff_total_cells_sas_inside_volume\t" << (result_summary_last_before_last.total_cells_summary.sas_inside_volume-result_summary_after_restoring.total_cells_summary.sas_inside_volume) << "\n\n";
	}
}

void run_mode_test_updateable_extensively(
		const ApplicationParameters& app_params,
		voronotalt::SpheresInput::Result& spheres_input_result,
		ApplicationLogRecorders& app_log_recorders) noexcept
{
	if(spheres_input_result.grouping_by_residue.size()!=spheres_input_result.spheres.size())
	{
		std::cerr << "Error: no atom-to-residue mapping provided.\n";
		return;
	}

	std::vector< std::vector<voronotalt::UnsignedInt> > list_of_residue_atoms;
	{
		std::map< int, std::vector<voronotalt::UnsignedInt> > map_of_residues_to_atoms;
		for(voronotalt::UnsignedInt i=0;i<spheres_input_result.grouping_by_residue.size();i++)
		{
			map_of_residues_to_atoms[spheres_input_result.grouping_by_residue[i]].push_back(i);
		}

		list_of_residue_atoms.reserve(map_of_residues_to_atoms.size());
		for(std::map< int, std::vector<voronotalt::UnsignedInt> >::const_iterator it=map_of_residues_to_atoms.begin();it!=map_of_residues_to_atoms.end();++it)
		{
			list_of_residue_atoms.push_back(it->second);
		}
	}

	const voronotalt::PeriodicBox periodic_box=voronotalt::PeriodicBox::create_periodic_box_from_shift_directions_or_from_corners(app_params.periodic_box_directions, app_params.periodic_box_corners);

	std::mt19937 random_gen(0);
	std::uniform_real_distribution<voronotalt::Float> random_coord(FLOATCONST(-1.0), FLOATCONST(1.0));
	std::uniform_real_distribution<voronotalt::Float> random_shift(FLOATCONST(1.0), FLOATCONST(5.0));

	app_log_recorders.time_recoder_for_tessellation.reset();

	voronotalt::UpdateableRadicalTessellation urt(false);

	urt.init(spheres_input_result.spheres, periodic_box);

	const double miliseconds_of_init=app_log_recorders.time_recoder_for_tessellation.get_elapsed_miliseconds();

	app_log_recorders.time_recoder_for_tessellation.record_elapsed_miliseconds_and_reset("initialized_tessellation");

	voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_first=urt.result_summary();

	const voronotalt::UnsignedInt number_of_residues=list_of_residue_atoms.size();
	const voronotalt::UnsignedInt number_of_double_updates=500;
	const voronotalt::UnsignedInt number_of_single_updates=number_of_double_updates*2;
	voronotalt::UnsignedInt number_of_moved_atoms=0;

	{
		std::vector<voronotalt::UnsignedInt> ids_of_changed_input_spheres(1, 0);
		for(voronotalt::UnsignedInt j=0;j<number_of_double_updates;j++)
		{
			const std::vector<voronotalt::UnsignedInt>& ids_of_changed_input_spheres=list_of_residue_atoms[j%number_of_residues];

			voronotalt::SimplePoint move(random_coord(random_gen), random_coord(random_gen), random_coord(random_gen));
			if(voronotalt::squared_point_module(move)<=FLOATCONST(0.0))
			{
				move=voronotalt::SimplePoint(FLOATCONST(1.0), FLOATCONST(1.0), FLOATCONST(1.0));
			}
			move=voronotalt::point_and_number_product(voronotalt::unit_point(move), random_shift(random_gen));

			for(voronotalt::UnsignedInt i=0;i<ids_of_changed_input_spheres.size();i++)
			{
				voronotalt::SimpleSphere& s=spheres_input_result.spheres[ids_of_changed_input_spheres[i]];
				s.p=voronotalt::sum_of_points(s.p, move);
			}

			urt.update(spheres_input_result.spheres, ids_of_changed_input_spheres);
			number_of_moved_atoms+=ids_of_changed_input_spheres.size();

			for(voronotalt::UnsignedInt i=0;i<ids_of_changed_input_spheres.size();i++)
			{
				voronotalt::SimpleSphere& s=spheres_input_result.spheres[ids_of_changed_input_spheres[i]];
				s.p=voronotalt::sub_of_points(s.p, move);
			}

			urt.update(spheres_input_result.spheres, ids_of_changed_input_spheres);
			number_of_moved_atoms+=ids_of_changed_input_spheres.size();
		}
	}

	const double miliseconds_of_all_updates=app_log_recorders.time_recoder_for_tessellation.get_elapsed_miliseconds();

	app_log_recorders.time_recoder_for_tessellation.record_elapsed_miliseconds_and_reset("updated_tessellation_multiple_times");

	voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_last=urt.result_summary();

	app_log_recorders.log_output << "log_urt_number_of_atoms\t" << spheres_input_result.spheres.size() << "\n";
	app_log_recorders.log_output << "log_urt_number_of_residues\t" << number_of_residues << "\n";
	app_log_recorders.log_output << "log_urt_number_of_single_updates\t" << number_of_single_updates << "\n";
	app_log_recorders.log_output << "log_urt_number_of_moved_atoms\t" << number_of_moved_atoms << "\n";
	app_log_recorders.log_output << "log_urt_miliseconds_of_init_tessellation\t" << miliseconds_of_init << "\n";
	app_log_recorders.log_output << "log_urt_miliseconds_of_all_updates\t" << miliseconds_of_all_updates << "\n";
	app_log_recorders.log_output << "log_urt_miliseconds_per_single_update\t" << (miliseconds_of_all_updates/static_cast<double>(number_of_single_updates)) << "\n";

	app_log_recorders.log_output << "log_urt_diff_total_contacts_count\t" << (result_summary_first.total_contacts_summary.count-result_summary_last.total_contacts_summary.count) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_contacts_area\t" << (result_summary_first.total_contacts_summary.area-result_summary_last.total_contacts_summary.area) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_count\t" << (result_summary_first.total_cells_summary.count-result_summary_last.total_cells_summary.count) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_sas_area\t" << (result_summary_first.total_cells_summary.sas_area-result_summary_last.total_cells_summary.sas_area) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_sas_inside_volume\t" << (result_summary_first.total_cells_summary.sas_inside_volume-result_summary_last.total_cells_summary.sas_inside_volume) << "\n";
}

void run_mode_test_maskable(
		const ApplicationParameters& app_params,
		const voronotalt::SpheresInput::Result& spheres_input_result,
		ApplicationLogRecorders& app_log_recorders) noexcept
{
	app_log_recorders.time_recoder_for_tessellation.reset();

	const voronotalt::PeriodicBox periodic_box=voronotalt::PeriodicBox::create_periodic_box_from_shift_directions_or_from_corners(app_params.periodic_box_directions, app_params.periodic_box_corners);

	voronotalt::UpdateableRadicalTessellation urt(true);

	urt.init(spheres_input_result.spheres, periodic_box, app_log_recorders.time_recoder_for_tessellation);

	voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_first;

	{
		voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary=urt.result_summary();
		app_log_recorders.log_output << "log_urt_init_local_update_balls_count\t" << urt.last_update_ids_of_affected_input_spheres().size() << "\n";
		app_log_recorders.log_output << "log_urt_init_total_contacts_area\t" << result_summary.total_contacts_summary.area << "\n";
		app_log_recorders.log_output << "log_urt_init_total_cells_sas_area\t" << result_summary.total_cells_summary.sas_area << "\n\n";
		result_summary_first=result_summary;
	}

	for(voronotalt::UnsignedInt i=0;i<10 && i<spheres_input_result.spheres.size();i++)
	{
		if(urt.update_by_setting_exclusion_mask(i, true, app_log_recorders.time_recoder_for_tessellation))
		{
			voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary=urt.result_summary();
			app_log_recorders.log_output << "log_urt_upd" << i << "_local_update_balls_count\t" << urt.last_update_ids_of_affected_input_spheres().size() << "\n";
			app_log_recorders.log_output << "log_urt_upd" << i << "_total_contacts_area\t" << result_summary.total_contacts_summary.area << "\n";
			app_log_recorders.log_output << "log_urt_upd" << i << "_total_cells_sas_area\t" << result_summary.total_cells_summary.sas_area << "\n\n";
			urt.restore_from_backup();
		}
	}

	voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary_last;

	{
		voronotalt::UpdateableRadicalTessellation::ResultSummary result_summary=urt.result_summary();
		app_log_recorders.log_output << "log_urt_final_local_update_balls_count\t" << urt.last_update_ids_of_affected_input_spheres().size() << "\n";
		app_log_recorders.log_output << "log_urt_final_total_contacts_area\t" << result_summary.total_contacts_summary.area << "\n";
		app_log_recorders.log_output << "log_urt_final_total_cells_sas_area\t" << result_summary.total_cells_summary.sas_area << "\n\n";
		result_summary_last=result_summary;
	}

	app_log_recorders.log_output << "log_urt_diff_total_contacts_count\t" << (result_summary_first.total_contacts_summary.count-result_summary_last.total_contacts_summary.count) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_contacts_area\t" << (result_summary_first.total_contacts_summary.area-result_summary_last.total_contacts_summary.area) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_count\t" << (result_summary_first.total_cells_summary.count-result_summary_last.total_cells_summary.count) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_sas_area\t" << (result_summary_first.total_cells_summary.sas_area-result_summary_last.total_cells_summary.sas_area) << "\n";
	app_log_recorders.log_output << "log_urt_diff_total_cells_sas_inside_volume\t" << (result_summary_first.total_cells_summary.sas_inside_volume-result_summary_last.total_cells_summary.sas_inside_volume) << "\n\n";
}

void run_mode_test_second_order_cell_volumes_calculation(
		const ApplicationParameters& app_params,
		const voronotalt::SpheresInput::Result& spheres_input_result,
		ApplicationLogRecorders& app_log_recorders) noexcept
{
	app_log_recorders.time_recoder_for_tessellation.reset();

	const voronotalt::PeriodicBox periodic_box=voronotalt::PeriodicBox::create_periodic_box_from_shift_directions_or_from_corners(app_params.periodic_box_directions, app_params.periodic_box_corners);

	voronotalt::UpdateableRadicalTessellation urt(true);

	urt.init(spheres_input_result.spheres, periodic_box, app_log_recorders.time_recoder_for_tessellation);

	std::vector< std::vector<voronotalt::Float> > all_result_volumes_for_contacts_summaries;

	if(urt.calculate_second_order_cell_volumes(all_result_volumes_for_contacts_summaries))
	{
		std::cout << "socv_header";
		if(!spheres_input_result.sphere_labels.empty())
		{
			std::cout << "\tID1_chain\tID1_residue\tID1_atom\tID2_chain\tID2_residue\tID2_atom";
		}
		std::cout << "\tID1_index\tID2_index\tarea\tarc_legth\tdistance\tsolid_angle_a\tsolid_angle_b\tpyramid_volume_a\tpyramid_volume_b\tsecond_order_cell_volume\n";
		for(voronotalt::UnsignedInt i=0;i<urt.result().contacts_summaries.size();i++)
		{
			for(voronotalt::UnsignedInt j=0;j<urt.result().contacts_summaries[i].size();j++)
			{
				const voronotalt::RadicalTessellation::ContactDescriptorSummary& cds=urt.result().contacts_summaries[i][j];
				if(std::min(cds.id_a, cds.id_b)==i)
				{
					std::cout << "socv";
					if(!spheres_input_result.sphere_labels.empty())
					{
						std::cout << "\t";
						voronotalt::PrintingCustomTypes::print_label(spheres_input_result.sphere_labels[cds.id_a], false, false, std::cout);
						std::cout << "\t";
						voronotalt::PrintingCustomTypes::print_label(spheres_input_result.sphere_labels[cds.id_b], false, false, std::cout);
					}
					std::cout << "\t" << cds.id_a << "\t" << cds.id_b << "\t" << cds.area << "\t" << cds.arc_length << "\t" << cds.distance;
					std::cout << "\t" << cds.solid_angle_a << "\t" << cds.solid_angle_b << "\t" << cds.pyramid_volume_a << "\t" << cds.pyramid_volume_b;
					std::cout << "\t" << ((i<all_result_volumes_for_contacts_summaries.size() && j<all_result_volumes_for_contacts_summaries[i].size()) ? all_result_volumes_for_contacts_summaries[i][j] : FLOATCONST(0.0));
					std::cout << "\n";
				}
			}
		}
	}
}

void run_mode_test_raw_collisions(
		const ApplicationParameters& app_params,
		const voronotalt::SpheresInput::Result& spheres_input_result,
		ApplicationLogRecorders& app_log_recorders,
		ApplicationGraphicsRecorder& app_graphics_recorder) noexcept
{
	app_log_recorders.time_recoder_for_tessellation.reset();

	voronotalt::SpheresContainer spheres_container;
	spheres_container.init(spheres_input_result.spheres, app_log_recorders.time_recoder_for_tessellation);

	voronotalt::SpheresContainer::ResultOfPreparationForTessellation preparation_result;

	voronotalt::RadicalTessellation::Result tessellation_result;

	{
		const std::vector<int> null_grouping;
		const std::vector<int>& grouping_for_filtering=(app_params.compute_only_inter_chain_contacts ? spheres_input_result.grouping_by_chain : (app_params.compute_only_inter_residue_contacts ? spheres_input_result.grouping_by_residue : null_grouping));

		{
			spheres_container.prepare_for_tessellation(std::vector<int>(), grouping_for_filtering, preparation_result, app_log_recorders.time_recoder_for_tessellation);
		}

		{
			voronotalt::RadicalTessellation::ResultGraphics result_graphics;

			voronotalt::RadicalTessellation::construct_full_tessellation(spheres_container, grouping_for_filtering, false, false, false, tessellation_result, result_graphics, app_log_recorders.time_recoder_for_tessellation);
		}
	}

	std::map< std::pair<voronotalt::UnsignedInt, voronotalt::UnsignedInt>, std::pair<bool, voronotalt::UnsignedInt> > map_of_collisions_to_contacts;

	for(voronotalt::UnsignedInt i=0;i<preparation_result.relevant_collision_ids.size();i++)
	{
		std::pair<voronotalt::UnsignedInt, voronotalt::UnsignedInt> collision_id=preparation_result.relevant_collision_ids[i];
		if(collision_id.first>collision_id.second)
		{
			std::swap(collision_id.first, collision_id.second);
		}
		std::pair<bool, voronotalt::UnsignedInt>& contact_id=map_of_collisions_to_contacts[collision_id];
		contact_id.first=false;
		contact_id.second=0;
	}

	for(voronotalt::UnsignedInt i=0;i<tessellation_result.contacts_summaries.size();i++)
	{
		const voronotalt::RadicalTessellation::ContactDescriptorSummary& cd=tessellation_result.contacts_summaries[i];
		std::pair<voronotalt::UnsignedInt, voronotalt::UnsignedInt> collision_id(cd.id_a, cd.id_b);
		if(collision_id.first>collision_id.second)
		{
			std::swap(collision_id.first, collision_id.second);
		}
		std::pair<bool, voronotalt::UnsignedInt>& contact_id=map_of_collisions_to_contacts[collision_id];
		contact_id.first=true;
		contact_id.second=i;
	}

	std::cout << "trc_header";
	if(!spheres_input_result.sphere_labels.empty())
	{
		std::cout << "\tID1_chain\tID1_residue\tID1_atom\tID2_chain\tID2_residue\tID2_atom";
	}
	std::cout << "\tID1_index\tID2_index\tdistance\tdistance_vdw\tarea\tarc_length\n";

	for(std::map< std::pair<voronotalt::UnsignedInt, voronotalt::UnsignedInt>, std::pair<bool, voronotalt::UnsignedInt> >::const_iterator it=map_of_collisions_to_contacts.begin();it!=map_of_collisions_to_contacts.end();++it)
	{
		const voronotalt::UnsignedInt a=it->first.first;
		const voronotalt::UnsignedInt b=it->first.second;
		const voronotalt::Float distance=voronotalt::distance_from_point_to_point(spheres_input_result.spheres[a].p, spheres_input_result.spheres[b].p);
		const voronotalt::Float distance_vdw=distance-(spheres_input_result.spheres[a].r-app_params.probe)-(spheres_input_result.spheres[b].r-app_params.probe);
		std::cout << "trc";
		if(!spheres_input_result.sphere_labels.empty())
		{
			std::cout << "\t";
			voronotalt::PrintingCustomTypes::print_label(spheres_input_result.sphere_labels[a], false, false, std::cout);
			std::cout << "\t";
			voronotalt::PrintingCustomTypes::print_label(spheres_input_result.sphere_labels[b], false, false, std::cout);
		}
		std::cout << "\t" << a << "\t" << b << "\t" << distance << "\t" << distance_vdw;
		const std::pair<bool, voronotalt::UnsignedInt>& contact_id=it->second;
		if(contact_id.first)
		{
			const voronotalt::RadicalTessellation::ContactDescriptorSummary& cd=tessellation_result.contacts_summaries[contact_id.second];
			std::cout << "\t" << cd.area << "\t" << cd.arc_length;
		}
		else
		{
			std::cout << "\t0\t0";
		}
		std::cout << "\n";
	}

	if(app_graphics_recorder.graphics_writer.enabled())
	{
		std::map< std::string, std::set<voronotalt::UnsignedInt> > atom_ids[3];
		for(int v=0;v<3;v++)
		{
			for(std::map< std::pair<voronotalt::UnsignedInt, voronotalt::UnsignedInt>, std::pair<bool, voronotalt::UnsignedInt> >::const_iterator it=map_of_collisions_to_contacts.begin();it!=map_of_collisions_to_contacts.end();++it)
			{
				const voronotalt::UnsignedInt a=it->first.first;
				const voronotalt::UnsignedInt b=it->first.second;
				if(v==0 || (v==1 && !it->second.first) || (v==2 && it->second.first))
				{
					atom_ids[v][spheres_input_result.sphere_labels[a].chain_id].insert(a);
					atom_ids[v][spheres_input_result.sphere_labels[b].chain_id].insert(b);
				}
			}
		}

		std::map<std::string, unsigned int> chain_colors[3];
		for(int v=0;v<3;v++)
		{
			for(std::map< std::string, std::set<voronotalt::UnsignedInt> >::const_iterator it=atom_ids[v].begin();it!=atom_ids[v].end();++it)
			{
				chain_colors[v][it->first]=0x222222;
			}
			int i=0;
			for(std::map<std::string, unsigned int>::iterator it=chain_colors[v].begin();it!=chain_colors[v].end();++it)
			{
				if(i==0)
				{
					it->second=(v==0 ? 0xFF00FF : (v==1 ? 0xFF01FF : 0xFF02FF));
				}
				else if(i==1)
				{
					it->second=(v==0 ? 0x00FFFF : (v==1 ? 0x01FFFF : 0x02FFFF));
				}
				i++;
			}
		}

		for(int v=0;v<3;v++)
		{
			app_graphics_recorder.graphics_writer.add_color("lines", "", v==0 ? 0xFFFF00 : (v==1 ? 0xFFFF01 : 0xFFFF02));
			for(std::map< std::pair<voronotalt::UnsignedInt, voronotalt::UnsignedInt>, std::pair<bool, voronotalt::UnsignedInt> >::const_iterator it=map_of_collisions_to_contacts.begin();it!=map_of_collisions_to_contacts.end();++it)
			{
				const voronotalt::UnsignedInt a=it->first.first;
				const voronotalt::UnsignedInt b=it->first.second;
				if(v==0 || (v==1 && !it->second.first) || (v==2 && it->second.first))
				{
					app_graphics_recorder.graphics_writer.add_line("lines", (v==0 ? "collisions0" : (v==1 ? "collisions1" : "collisions2")), spheres_input_result.spheres[a].p, spheres_input_result.spheres[b].p);
				}
			}
			for(std::map< std::string, std::set<voronotalt::UnsignedInt> >::const_iterator it=atom_ids[v].begin();it!=atom_ids[v].end();++it)
			{
				app_graphics_recorder.graphics_writer.add_color("balls", "", chain_colors[v][it->first]);
				const std::string group_name=std::string(v==0 ? "atoms0" : (v==1 ? "atoms1" : "atoms2"))+"_chain_"+it->first;
				const double r=(v==0 ? 0.48 : (v==1 ? 0.49 : 0.50));
				for(std::set<voronotalt::UnsignedInt>::const_iterator jt=it->second.begin();jt!=it->second.end();++jt)
				{
					app_graphics_recorder.graphics_writer.add_sphere("balls", group_name, voronotalt::SimpleSphere(spheres_input_result.spheres[*jt].p, r), 0.0);
				}
			}
		}
	}
}

#endif /* VORONOTALT_WITH_TEST_MODES */

}

int main(const int argc, const char** argv)
{
	std::ios_base::sync_with_stdio(false);

	ApplicationParameters app_params;

	if(!app_params.read_from_command_line_args(argc, argv))
	{
		if(!app_params.error_log_for_options_parsing.str().empty())
		{
			std::cerr << app_params.error_log_for_options_parsing.str() << "\n";
		}
		else
		{
			std::cerr << "Error: invalid command line arguments.\n";
		}
		return 1;
	}

#ifdef VORONOTALT_OPENMP
		omp_set_num_threads(app_params.max_number_of_processors);
#endif

	ApplicationLogRecorders app_log_recorders(app_params);

	voronotalt::SpheresInput::Result spheres_input_result;

	{
		app_log_recorders.time_recoder_for_input.reset();

		std::string input_data;

		if(app_params.input_from_file.empty() || app_params.input_from_file=="_stdin")
		{
			std::istreambuf_iterator<char> stdin_eos;
			std::string stdin_data(std::istreambuf_iterator<char>(std::cin), stdin_eos);
			input_data.swap(stdin_data);
			app_log_recorders.time_recoder_for_input.record_elapsed_miliseconds_and_reset("read stdin data to memory");
		}
		else
		{
			std::ifstream infile(app_params.input_from_file.c_str(), std::ios::in|std::ios::binary);
			if(infile.is_open())
			{
				std::string file_data;
				infile.seekg(0, std::ios::end);
				file_data.resize(infile.tellg());
				infile.seekg(0, std::ios::beg);
				infile.read(&file_data[0], file_data.size());
				infile.close();
				input_data.swap(file_data);
				app_log_recorders.time_recoder_for_input.record_elapsed_miliseconds_and_reset("read file data to memory");
			}
			else
			{
				std::cerr << "Error: failed to open file '" << app_params.input_from_file << "' without errors\n";
				return 1;
			}
		}

		if(input_data.empty())
		{
			std::cerr << "Error: empty input provided\n";
			return 1;
		}

		const voronotalt::MolecularFileReading::Parameters molecular_file_reading_parameters(app_params.pdb_or_mmcif_heteroatoms, app_params.pdb_or_mmcif_hydrogens, app_params.pdb_or_mmcif_as_assembly);

		if(!voronotalt::SpheresInput::read_labeled_or_unlabeled_spheres_from_string(input_data, molecular_file_reading_parameters, app_params.probe, spheres_input_result, std::cerr, app_log_recorders.time_recoder_for_input))
		{
			std::cerr << "Error: failed to read input without errors\n";
			return 1;
		}
	}

	if(!app_params.write_input_balls_to_file.empty())
	{
		std::ofstream foutput(app_params.write_input_balls_to_file.c_str(), std::ios::out);
		if(foutput.good())
		{
			voronotalt::PrintingCustomTypes::print_balls_to_stream(spheres_input_result.spheres, spheres_input_result.sphere_labels, app_params.probe, foutput);
		}
		else
		{
			std::cerr << "Error (non-terminating): failed to write input balls to file '" << app_params.write_input_balls_to_file << "'\n";
		}
	}

	if((app_params.compute_only_inter_chain_contacts || app_params.need_summaries_on_chain_level) && spheres_input_result.number_of_chain_groups<2)
	{
		std::cerr << "Error: inter-chain contact selection not possible - not enough distinct chains derived from labels\n";
		return 1;
	}

	if((app_params.compute_only_inter_residue_contacts || app_params.need_summaries_on_residue_level) && spheres_input_result.number_of_residue_groups<2)
	{
		std::cerr << "Error: inter-residue contact selection not possible - not enough distinct residues derived from labels\n";
		return 1;
	}

	ApplicationGraphicsRecorder app_graphics_recorder(app_params);
	ApplicationMeshRecorder app_mesh_recorder(app_params);

	if(app_params.running_mode==ApplicationParameters::RunningMode::radical)
	{
		run_mode_radical(app_params, spheres_input_result, app_log_recorders, app_graphics_recorder, app_mesh_recorder);
	}
	else if(app_params.running_mode==ApplicationParameters::RunningMode::simplified_aw)
	{
		run_mode_simplified_aw(app_params, spheres_input_result, app_log_recorders, app_graphics_recorder);
	}
	else
	{
#ifdef VORONOTALT_WITH_TEST_MODES
		if(app_params.running_mode==ApplicationParameters::RunningMode::test_updateable || app_params.running_mode==ApplicationParameters::RunningMode::test_updateable_with_backup)
		{
			run_mode_test_updateable(app_params, spheres_input_result, app_log_recorders);
		}
		else if(app_params.running_mode==ApplicationParameters::RunningMode::test_updateable_extensively)
		{
			run_mode_test_updateable_extensively(app_params, spheres_input_result, app_log_recorders);
		}
		else if(app_params.running_mode==ApplicationParameters::RunningMode::test_maskable)
		{
			run_mode_test_maskable(app_params, spheres_input_result, app_log_recorders);
		}
		else if(app_params.running_mode==ApplicationParameters::RunningMode::test_second_order_cell_volumes_calculation)
		{
			run_mode_test_second_order_cell_volumes_calculation(app_params, spheres_input_result, app_log_recorders);
		}
		else if(app_params.running_mode==ApplicationParameters::RunningMode::test_raw_collisions)
		{
			run_mode_test_raw_collisions(app_params, spheres_input_result, app_log_recorders, app_graphics_recorder);
		}
		else
		{
			std::cerr << "Error: invalid running mode.\n";
			return 1;
		}
#else
		std::cerr << "Error: invalid running mode (test modes are disabled in this build).\n";
		return 1;
#endif
	}

	app_graphics_recorder.finalize_and_output(app_params, app_log_recorders);

	app_mesh_recorder.finalize_and_output(app_params, app_log_recorders);

	app_log_recorders.finalize_and_output(app_params);

	return 0;
}

