/*

Copyright (c) 2002-2005, Yauheni Akhotnikau
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.

- Redistributions in binary form must reproduce the above copyright notice, this
list of conditions and the following disclaimer in the documentation and/or
other materials provided with the distribution.

- The name of the author may not be used to endorse or promote products derived
from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
OF SUCH DAMAGE.

*/

#include <iostream>
#include <fstream>
#include <cerrno>
#include <cstring>
#include <set>
#include <algorithm>
#include <functional>
#include <stdexcept>

#include <args_4/h/args_iostream.hpp>

#include <auto_ptr_3/h/obj_ptr.hpp>

#include <oess_1/defs/log/h/err.hpp>

#include <oess_1/defs/h/ex.hpp>

#include <oess_1/defs/util/h/logo.hpp>

#include <oess_1/db/cln/h/local_db.hpp>

//! \name Имена параметров командной строки.
/*! \{ */
const std::string arg_name_db = "--db";
const std::string arg_name_to = "--to";
const std::string arg_name_append = "--append";
const std::string arg_name_slice = "--slice";
const std::string arg_name_ignore_slice = "--ignore-slice";
const std::string arg_name_ent = "--ent";
const std::string arg_name_ignore_ent = "--ignore-ent";
/*! \} */

void
show_usage()
{
	std::cout << OESS_1__LOGO << std::endl
		<< "Export database content\n"
			"\n"
			"oess_db_export [options] [@reponse-file]\n\n"
			"options:\n"
			"\t" << arg_name_db
					<< " <name-of-db>  database name (mandatory).\n"
			"\t" << arg_name_to
					<< " <filename>  name of file to store DB content.\n"
							"\t\tStandard output will be used if this argument "
							"not specified.\n"
			"\t" << arg_name_append
					<< "	append DB content to specified file.\n"
			"\t" << arg_name_slice
					<< " <slice_name>	export only content of this slice.\n"
							"\t\tMay be specified multiple times.\n"
			"\t" << arg_name_ignore_slice
					<< " <slice_name>	ignore content of this slice.\n"
							"\t\tMay be specified multiple times.\n"
			"\t" << arg_name_ent
					<< " <ent id>	export this entity.\n"
							"\t\tMay be specified multiple times.\n"
			"\t" << arg_name_ignore_ent
					<< " <ent id>	ignore this entity.\n"
							"\t\tMay be specified multiple times.\n"
			"\nNotes:\n"
					"\tIf " << arg_name_slice << " and " << arg_name_ent
					<< " not specified then all DB content will be exported "
					"(excluding slices with names specified in "
					<< arg_name_ignore_slice << " and entities specified in "
					<< arg_name_ignore_ent << ").\n"

					"\tIf " << arg_name_slice << " specified and "
					<< arg_name_ent << " omitted then only content of "
					"specified slices will be exported (excluding entities "
					"specified in " << arg_name_ignore_ent << ").\n"

					"\tArguments " << arg_name_slice << " and "
					<< arg_name_ignore_slice << " cannot be used together.\n"

					"\tIf " << arg_name_ent << " specified and "
					<< arg_name_slice << " omitted then only entities "
					"will be exported.\n"

					"\tIf " << arg_name_slice << " and " << arg_name_ent
					<< " specified then only content of specified slices "
					"(excluding entities from " << arg_name_ignore_ent
					<< ") and specified entities will be exported.\n"

					"\tIf same entity id specified in " << arg_name_ent
					<< " and " << arg_name_ignore_ent << " then "
					<< arg_name_ignore_ent << " take no effect.\n"

			<< std::endl;
}

void
parse_resp_file(
	const char * resp_file_name,
	args_4::cmd_t & arg_db,
	args_4::cmd_t & arg_to,
	args_4::cmd_t & arg_append,
	args_4::cmd_t & arg_slice,
	args_4::cmd_t & arg_ignore_slice,
	args_4::cmd_t & arg_ent,
	args_4::cmd_t & arg_ignore_ent )
	{
		args_4::cmd_parser_t args_parser;
		args_parser.add_cmds(
				&arg_db,
				&arg_to,
				&arg_append,
				&arg_slice,
				&arg_ignore_slice,
				&arg_ent,
				&arg_ignore_ent, 0 );

		args_4::words_from_file_t words_container( resp_file_name );

		args_4::iostream_error_stream_t args_error_stream;
		int error = args_parser.parse(
				words_container, &args_error_stream );
		if( args_4::c_ok != error )
			{
				if( args_4::c_io_error == error )
					{
						OESS_THROW_LOGIC( error,
							"I/O error with response file: "
							<< resp_file_name << ", error: "
							<< strerror(
								words_container.query_last_io_result() ) )
					}
				else
					OESS_THROW_LOGIC( error,
						"unable to parse response file: "
						<< resp_file_name )
			}
	}

void
check_args(
	args_4::cmd_str_value_t & arg_db,
	args_4::cmd_str_value_t & arg_to,
	args_4::cmd_on_off_switch_t & arg_append,
	args_4::cmd_strvector_value_t & arg_slice,
	args_4::cmd_strvector_value_t & arg_ignore_slice,
	args_4::cmd_strvector_value_t & arg_ent,
	args_4::cmd_strvector_value_t & arg_ignore_ent )
{
	if( !arg_db.is_defined() )
		OESS_THROW_LOGIC( args_4::c_mandatory_cmd_missed,
			"cmd-line argument missed: "
			<< arg_db.query_name() )

	if( arg_slice.is_defined() && arg_ignore_slice.is_defined() )
		OESS_THROW_LOGIC( args_4::c_invalid_value,
			arg_name_slice << " cannot be used with "
			<< arg_name_ignore_slice )
}

void
parse_args(
	int argc, char ** argv,
	args_4::cmd_str_value_t & arg_db,
	args_4::cmd_str_value_t & arg_to,
	args_4::cmd_on_off_switch_t & arg_append,
	args_4::cmd_strvector_value_t & arg_slice,
	args_4::cmd_strvector_value_t & arg_ignore_slice,
	args_4::cmd_strvector_value_t & arg_ent,
	args_4::cmd_strvector_value_t & arg_ignore_ent )
	{
		args_4::cmd_response_file_t arg_resp_file;
		args_4::cmd_parser_t args_parser;
		args_parser.add_cmds(
				&arg_db,
				&arg_to,
				&arg_append,
				&arg_slice,
				&arg_ignore_slice,
				&arg_ent,
				&arg_ignore_ent,
				&arg_resp_file, 0 );
   
		args_4::words_from_argv_t words_container( argc, argv );
		args_4::iostream_error_stream_t args_error_stream;
   
		int error = args_parser.parse(
				words_container, &args_error_stream );
		if( args_4::c_ok == error )
			{
				// Если нужно, разбираем response-файлы.
				if( arg_resp_file.is_defined() )
					{
						for( size_t i = 0, i_max =
								arg_resp_file.query_value_count();
								i != i_max; ++i )
							parse_resp_file(
									arg_resp_file.query_value( i ),
									arg_db,
									arg_to,
									arg_append,
									arg_slice,
									arg_ignore_slice,
									arg_ent,
									arg_ignore_ent );
					}
			}
		else
			OESS_THROW_LOGIC( error,
				"unable to parse cmd-line arguments" )
   
		check_args(
				arg_db,
				arg_to,
				arg_append,
				arg_slice,
				arg_ignore_slice,
				arg_ent,
				arg_ignore_ent );
	}

//! Тип множества имен разделов.
typedef std::set< std::string >	slice_name_set_t;

//! Тип множества идентификаторов сущностей.
typedef std::set< oess_1::ent_id_t > ent_set_t;

//! Преобразовать список имен слайсов из параметра командной строки
//! во множество имен для последующей обработки.
void
make_slice_set(
	const args_4::cmd_strvector_value_t & args,
	slice_name_set_t & slice_set )
	{
		for( size_t i = 0, i_max = args.query_value_count();
				i != i_max; ++i )
			slice_set.insert( args.query_value( i ) );
	}

//! Преобразовать список идентификаторов сущностей из параметра
//! командной строки во множество идентификаторов для последующей
//! обработки.
/*!
	\throw std::exception в случае невозможности преобразования.
*/
void
make_ent_set(
	const args_4::cmd_strvector_value_t & args,
	ent_set_t & ent_set )
	{
		for( size_t i = 0, i_max = args.query_value_count();
				i != i_max; ++i )
			{
				std::istringstream string_ent( args.query_value( i ) );
				oess_1::ent_id_t id;

				string_ent >> id;
				if( string_ent.fail() )
					OESS_THROW_LOGIC( args_4::c_invalid_value,
							"invalid entity id: "
							<< args.query_value( i ) )
			}
	}

//! Выполнение экспорта данных.
void
do_export(
	//! Имя БД из которой данные нужно экспортировать.
	const std::string & db_name,
	//! Имя файла, в который нужно производить экспорт.
	//! Содержит 0, если нужно экспортировать в стандартный поток вывода.
	const char * export_file_name,
	//! Признак того, что экспортируемая информация должна дописываться
	//! в указанный файл.
	bool is_append_mode,
	//! Множество имен слайсов, чье содержимое должно быть экспортировано.
	const slice_name_set_t & slices_to_export,
	//! Множество имен слайсов, которые должны быть проигнорированны.
	const slice_name_set_t & slices_to_ignore,
	//! Множество идентификаторов сущностей, которые должны быть
	//! экспортированны без учета имени слайса.
	const ent_set_t & ents_to_export,
	//! Множество идентификаторов сущностей, которые должны быть
	//! проигнорированны при экспорте разделов.
	const ent_set_t & ents_to_ignore )
	{
//FIX ME: реализовать!
	}

void
do_work( int argc, char ** argv )
{
	if( 1 == argc )
		show_usage();
	else
		{
			args_4::cmd_str_value_t arg_db(
					arg_name_db.c_str(), false );
			args_4::cmd_str_value_t arg_to(
					arg_name_to.c_str(), false );
			args_4::cmd_on_off_switch_t arg_append(
					arg_name_append.c_str(), false );
			args_4::cmd_strvector_value_t arg_slice(
					arg_name_slice.c_str(), false );
			args_4::cmd_strvector_value_t arg_ignore_slice(
					arg_name_ignore_slice.c_str(), false );
			args_4::cmd_strvector_value_t arg_ent(
					arg_name_ent.c_str(), false );
			args_4::cmd_strvector_value_t arg_ignore_ent(
					arg_name_ignore_ent.c_str(), false );
         
			parse_args( argc, argv,
				arg_db,
				arg_to,
				arg_append,
				arg_slice,
				arg_ignore_slice,
				arg_ent,
				arg_ignore_ent );

			slice_name_set_t slices_to_export;
			make_slice_set( arg_slice, slices_to_export );

			slice_name_set_t slices_to_ignore;
			make_slice_set( arg_ignore_slice, slices_to_ignore );

			ent_set_t ents_to_export;
			make_ent_set( arg_ent, ents_to_export );

			ent_set_t ents_to_ignore;
			make_ent_set( arg_ignore_ent, ents_to_ignore );

			do_export(
					arg_db.query_value(),
					( arg_to.is_defined() ? arg_to.query_value() : 0 ),
					arg_append.is_defined(),
					slices_to_export,
					slices_to_ignore,
					ents_to_export,
					ents_to_ignore );
		}
}

int
main( int argc, char ** argv )
	{
		try
			{
				do_work( argc, argv );
      
				return 0;
			}
		catch( const oess_1::ex_t & x )
			{
				std::cerr << "*** " << x.query_err_code() << " ***"
					<< std::endl;
			}
		catch( const std::exception & x )
			{
				std::cerr << "### " << x.what() << " ###"
					<< std::endl;
			}
		catch( ... )
			{
				OESS_1_LOG_ERR( "Oops! Unknown exception!" << std::endl )
			}
		
		return 2;
	}

