/*

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 <cstdlib>

#include <time.h>

#include <map>
#include <string>

#include <auto_ptr_3/h/obj_ptr.hpp>

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

#include <oess_1/stdsn/h/inout_templ.hpp>

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

using namespace std;

//
// dir_item_t
//

// Элемент каталога файла в хранилище.
class	dir_item_t :
	public oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( dir_item_t )
private :
	// Имя файла.
	std::string	m_file_name;
	// Размер файла.
	unsigned int	m_file_size;
	// Идентификатор файла в хранилище.
	oess_1::ent_id_t	m_id;

public :
	dir_item_t()
	:
		m_file_size( 0 )
	{}
	dir_item_t(
		const std::string & file_name,
		unsigned int file_size,
		const oess_1::ent_id_t & id )
	:
		m_file_name( file_name )
	,	m_file_size( file_size )
	,	m_id( id )
	{}
	dir_item_t(
		const dir_item_t & o )
	{
		(*this) = o;
	}
	virtual ~dir_item_t()
	{}

	const dir_item_t &
	operator=( const dir_item_t & o )
	{
		if( &o == this )
		{
			m_file_name = o.m_file_name;
			m_file_size = o.m_file_size;
			m_id = o.m_id;
		}
		return *this;
	}

	const std::string &
	file_name() const
	{
		return m_file_name;
	}

	// Получение имени файла как ключа.
	static const std::string &
	file_name_key( const dir_item_t & o )
	{
		return o.file_name();
	}

	unsigned int
	file_size() const
	{
		return m_file_size;
	}

	// Получение размера файла как ключа.
	static unsigned int
	file_size_key( const dir_item_t & o )
	{
		return o.file_size();
	}

	const oess_1::ent_id_t &
	id() const
	{
		return m_id;
	}
};

//
// file_content_t
//

// Содержимое файла.
class	file_content_t :
	public  oess_1::stdsn::serializable_t
{
	OESS_SERIALIZER( file_content_t )
	public :
		file_content_t();
		virtual ~file_content_t();

		std::string &
		content();

		const std::string &
		content() const;

		unsigned int
		size() const;

	private :
		std::string	m_content;
};

file_content_t::file_content_t()
{
}

file_content_t::~file_content_t()
{
}

std::string &
file_content_t::content()
{
	return m_content;
}

const std::string &
file_content_t::content() const
{
	return m_content;
}

unsigned int
file_content_t::size() const
{
	return m_content.size();
}

//
// dir_t
//
class	dir_t
{
private :
	// Тип индекса по оглавлению через имя файла.
	typedef oess_1::db::cln::slice_index_t<
			dir_item_t,
			std::string,
			const std::string & (*)( const dir_item_t & ) >
		dir_item_name_index_t;

	// Тип индекса по оглавлению через размер файла.
	typedef oess_1::db::cln::slice_index_t<
			dir_item_t,
			unsigned int,
			unsigned int (*)( const dir_item_t & ) >
		dir_item_size_index_t;

	// Тип образа раздела с оглавлением.
	typedef oess_1::db::cln::slice_image_t<
			dir_item_t >
		dir_item_image_t;

	// Образ раздела с оглавлением.
	dir_item_image_t	m_image;
	// Индекс по именам файлов.
	dir_item_name_index_t	m_name_index;
	// Индекс по размерам файлов.
	dir_item_size_index_t	m_size_index;

public :
	dir_t(
		oess_1::db::cln::db_t & db );
	virtual ~dir_t();

	// Найти файл в архиве.
	// Возвращает эквивалент NULL, если файл не найден.
	oess_1::ent_id_t
	find(
		const std::string & file_name ) const;

	// Поместить описание файла в архив.
	void
	insert(
		const std::string & file_name,
		unsigned int size,
		const oess_1::ent_id_t & id );

	void
	list(
		std::ostream & s ) const;

	void
	erase(
		const std::string & file_name );
};

dir_t::dir_t(
	oess_1::db::cln::db_t & db )
:
	m_image( db, "dir" )
,	m_name_index( m_image, &dir_item_t::file_name_key )
,	m_size_index( m_image, &dir_item_t::file_size_key )
{
	// В базе должен быть этот раздел.
	db.slice_create( "dir" );

	m_image.reload();
}

dir_t::~dir_t()
{
}

oess_1::ent_id_t
dir_t::find(
	const std::string & file_name ) const
{
	dir_item_name_index_t::const_iterator it(
		m_name_index.find( file_name ) );
	if( it != m_name_index.end() )
	{
		return it->id();
	}

	return oess_1::ent_id_t();
}

void
dir_t::insert(
	const std::string & file_name,
	unsigned int size,
	const oess_1::ent_id_t & id )
{
	m_image.insert( dir_item_t( file_name, size, id ) );
}

void
dir_t::list(
	std::ostream & s ) const
{
	for( dir_item_name_index_t::const_iterator
		it = m_name_index.begin(), it_end = m_name_index.end();
		it != it_end; ++it )
	{
		s << it->file_name() << ", "
			<< it->file_size() << ", "
			<< it->id() << std::endl;
	}
}

void
dir_t::erase(
	const std::string & file_name )
{
	m_name_index.erase( file_name );
}

#include "main.ddl.cpp"

//
// app_t
//
class	app_t {
	public :
		app_t( oess_1::db::cln::db_t & db );
		~app_t();

		void
		attach();

		void
		pack(
			char ** files,
			int file_count );

		void
		list() const;

		void
		erase(
			char ** files,
			int file_count );

	private :
		oess_1::db::cln::db_t	& m_db;

		auto_ptr_3::obj_ptr_t< dir_t >	m_dir;

		void
		load_dir();

		void
		pack_file(
			const std::string & file_name );

		bool
		load_file_from_filesys(
			const std::string & file_name,
			file_content_t & ent );

		void
		erase_file(
			const std::string & file_name );

};

app_t::app_t(
	oess_1::db::cln::db_t & db )
:
	m_db( db )
{
}

app_t::~app_t()
{
}

void
app_t::attach()
{
	// В базе должен быть этот раздел.
	m_db.slice_create( "archive" );

	load_dir();
}

void
app_t::pack(
	char ** files,
	int file_count )
{
	// Выполняем операцию в транзакции.
	oess_1::db::cln::transaction_t trx( m_db );

	for( int i = 0; i != file_count; ++i )
	{
		pack_file( files[ i ] );
	}

	trx.commit();
}

void
app_t::list() const
{
	m_dir->list( std::cout );
}

void
app_t::erase(
	char ** files,
	int file_count )
{
	// Выполняем операцию в транзакции.
	oess_1::db::cln::transaction_t trx( m_db );

	for( int i = 0; i != file_count; ++i )
	{
		erase_file( files[ i ] );
	}

	trx.commit();
}

void
app_t::load_dir()
{
	m_dir.reset( new dir_t( m_db ) );
}

void
app_t::pack_file(
	const std::string & file_name )
{
	std::cout << "packing: " << file_name;
	file_content_t ent;
	if( load_file_from_filesys( file_name, ent ) )
	{
		oess_1::ent_id_t id( m_dir->find( file_name ) );
		if( !id )
		{
			id = m_db.ent_create( "archive", ent );
			m_dir->insert( file_name, ent.size(), id );

			std::cout << " new entity created: " << id << std::endl;
		}
		else
		{
			m_db.ent_update( id, ent );
			std::cout << " existed entity updated: " << id << std::endl;
		}
	}
}

void
app_t::erase_file(
	const std::string & file_name )
{
	std::cout << "erasing from archive: " << file_name;
	oess_1::ent_id_t id( m_dir->find( file_name ) );
	if( !id )
	{
		std::cout << " not found in archive" << std::endl;
	}
	else
	{
		m_db.ent_destroy( id );

		m_dir->erase( file_name );
		std::cout << std::endl;
	}
}

bool
app_t::load_file_from_filesys(
	const std::string & file_name,
	file_content_t & ent )
{
	std::ifstream f( file_name.c_str(), std::ios::in | std::ios::binary );
	if( f )
	{
		char buf[ 256 ];
		while( f )
		{
			f.read( buf, sizeof( buf ) );
			size_t s = f.gcount();
			if( s )
				ent.content().append( buf, s );
		}

		return true;
	}
	else
	{
		std::cerr << file_name << ": " << errno
			<< ", " << strerror( errno ) << std::endl;
	}

	return false;
}

int
main(
	int argc,
	char ** argv )
{
	if( 1 == argc ) {
		std::cout << "sample_file_archive_2 <cmd> [file [file [...]]]\n\n"
			"cmd:\n"
			"\tpack\n"
			"\tlist\n"
			"\terase\n"
			<< std::endl;
		return 0;
	}

	try {
		const std::string physic_name( "./sample/file_archive_2/db" );

		oess_1::db::cln::local_db_t db( physic_name );

		app_t	app( *db.query_db() );
		app.attach();

//FIX ME: реализовать:
//	- команды: find, unpack;
//	- чтение resp-файлов.

		if( 0 == strcmp( "pack", argv[ 1 ] ) )
			app.pack( &argv[ 2 ], argc - 2 );
		else if( 0 == strcmp( "list", argv[ 1 ] ) )
			app.list();
		else if( 0 == strcmp( "erase", argv[ 1 ] ) )
			app.erase( &argv[ 2 ], argc - 2 );

		return 0;
	}
	catch( oess_1::ex_t & x ) {
		std::cerr << "*** " << x.query_err_code() << " ***"
			<< std::endl;
	}
	catch( ... ) {
		std::cerr << "Oops! Unknown exception!" << std::endl;
	}

	return 1;
}
