/* Copyright (C) 2011 Mike Fleetwood
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU Library General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */


#include "../include/nilfs2.h"

#include <cerrno>

namespace GParted
{

FS nilfs2::get_filesystem_support()
{
	FS fs ;
	fs .filesystem = GParted::FS_NILFS2 ;

	if ( ! Glib::find_program_in_path( "mkfs.nilfs2" ) .empty() )
	{
		fs .create = GParted::FS::EXTERNAL ;
	}

	if ( ! Glib::find_program_in_path( "nilfs-tune" ) .empty() )
	{
		fs .read = GParted::FS::EXTERNAL ;
		fs .read_label = GParted::FS::EXTERNAL ;
		fs .write_label = GParted::FS::EXTERNAL ;
	}

	//Nilfs2 resizing is an online only operation and needs:
	//  mount, umount, nilfs-resize and linux 3.0 with nilfs2 support.
	if ( ! Glib::find_program_in_path( "mount" ) .empty()        &&
	     ! Glib::find_program_in_path( "umount" ) .empty()       &&
	     ! Glib::find_program_in_path( "nilfs-resize" ) .empty()    )
	{
		std::ifstream input( "/proc/version" ) ;
		std::string line ;
		int linux_major_ver = -1 ;
		int linux_minor_ver = -1 ;
		int linux_patch_ver = -1 ;
		if ( input )
		{
			getline( input, line ) ;
			sscanf( line .c_str() , "Linux version %d.%d.%d", &linux_major_ver, &linux_minor_ver, &linux_patch_ver ) ;
			input .close() ;
		}

		input .open( "/proc/filesystems" ) ;
		bool kernel_supports_nilfs2 = false ;
		if ( input )
		{
			while ( input >> line )
				if ( line == "nilfs2" )
				{
					kernel_supports_nilfs2 = true ;
					break ;
				}
			input .close() ;
		}

		if ( linux_major_ver >= 3 && kernel_supports_nilfs2 )
		{
			fs .grow = GParted::FS::EXTERNAL ;		
			if ( fs .read )
				fs .shrink = GParted::FS::EXTERNAL ;
		}
	}

	fs .copy = GParted::FS::GPARTED ;
	fs .move = GParted::FS::GPARTED ;

	//Minimum FS size is 128M+4K using mkfs.nilfs2 defaults
	fs .MIN = 128 * MEBIBYTE + 4 * KIBIBYTE ;

	return fs ;
}

void nilfs2::set_used_sectors( Partition & partition )
{
	if ( ! Utils::execute_command( "nilfs-tune -l " + partition .get_path(), output, error, true ) )
	{
		Glib::ustring::size_type index = output .find( "Free blocks count:" ) ;
		if ( index == Glib::ustring::npos ||
		     sscanf( output .substr( index ) .c_str(), "Free blocks count: %Ld", &N ) != 1 )
			N = -1 ;

		index = output .find( "Block size:" ) ;
		if ( index == Glib::ustring::npos ||
		     sscanf( output .substr( index ) .c_str(), "Block size: %Ld", &S ) != 1 )
			S = -1 ;

		if ( N > -1 && S > -1 )
			partition .Set_Unused( Utils::round( N * ( S / double( partition .sector_size) ) ) ) ;
	}
	else
	{
		if ( ! output .empty() )
			partition .messages .push_back( output ) ;

		if ( ! error .empty() )
			partition .messages .push_back( error ) ;
	}
}

void nilfs2::read_label( Partition & partition )
{
	if ( ! Utils::execute_command( "nilfs-tune -l " + partition .get_path(), output, error, true ) )
	{
		Glib::ustring label = Utils::regexp_label( output, "^Filesystem volume name:[\t ]*(.*)$" ) ;
		if ( label != "(none)" )
			partition .label = label;
	}
	else
	{
		if ( ! output .empty() )
			partition .messages .push_back( output ) ;

		if ( ! error .empty() )
			partition .messages .push_back( error ) ;
	}
}

bool nilfs2::write_label( const Partition & partition, OperationDetail & operationdetail )
{
	return ! execute_command( "nilfs-tune -L \"" + partition .label + "\" " + partition .get_path(), operationdetail ) ;
}

bool nilfs2::create( const Partition & new_partition, OperationDetail & operationdetail )
{
	return ! execute_command( "mkfs.nilfs2 -L \"" + new_partition .label + "\" " + new_partition .get_path(), operationdetail ) ;
}

bool nilfs2::resize( const Partition & partition_new, OperationDetail & operationdetail, bool fill_partition )
{
	bool return_value = true ;
	Glib::ustring cmd ;
	bool success ;
	int e;

	//Create randomly named mount point
	char dtemplate[] = P_tmpdir "/gparted-XXXXXX" ;
	const char * dname = mkdtemp( dtemplate ) ;
	if ( dname == NULL )
	{
		e = errno ;
		cmd = "mkdir -v" ;
		operationdetail .add_child( OperationDetail( cmd, STATUS_NONE, FONT_BOLD_ITALIC ) ) ;
		operationdetail .get_last_child() .set_status( STATUS_ERROR ) ;
		operationdetail .get_last_child() .add_child( OperationDetail( String::ucompose( "mkdtemp(%1): ", dtemplate ) + Glib::strerror( e ), STATUS_NONE ) ) ;
		return false ;  //Report error and fail overall resize operation, stopping now
	}
	else
	{
		cmd = Glib::ustring( "mkdir -v " ) + dname ;
		operationdetail .add_child( OperationDetail( cmd, STATUS_NONE, FONT_BOLD_ITALIC ) ) ;
		operationdetail .get_last_child() .set_status( STATUS_SUCCES ) ;
		operationdetail .get_last_child() .add_child( OperationDetail( Glib::ustring( "mkdir: created directory: " ) + dname, STATUS_NONE ) ) ;
	}

	//Mount file system
	cmd = "mount -v -t nilfs2 " + partition_new .get_path() + " " + dname ;
	success = ! execute_command( cmd, operationdetail ) ;
	return_value &= success ;  //On error, fail overall resize operation but complete function
	operationdetail .get_last_child() .set_status( success ? STATUS_SUCCES : STATUS_ERROR ) ;

	if ( success )
	{
		//Resize file system
		cmd = "nilfs-resize -v -y " + partition_new .get_path() ;
		if ( ! fill_partition )
		{
			Glib::ustring size = Utils::num_to_str( floor( Utils::sector_to_unit( partition_new .get_sector_length(), partition_new .sector_size, UNIT_KIB ) ) ) + "K" ;
			cmd += " " + size ;
		}
		success = ! execute_command( cmd, operationdetail ) ;
		operationdetail .get_last_child() .set_status( success ? STATUS_SUCCES : STATUS_ERROR ) ;
		return_value &= success ;  //On error, fail overall resize operation but complete function

		//Unmount file system
		cmd = Glib::ustring( "umount -v " ) + dname ;
		success = ! execute_command( cmd, operationdetail ) ;
		operationdetail .get_last_child() .set_status( success ? STATUS_SUCCES : STATUS_ERROR ) ;
		return_value &= success ;  //On error, fail overall resize operation but complete function
	}

	//Delete mount point
	cmd = Glib::ustring( "rmdir -v " ) + dname ;
	operationdetail .add_child( OperationDetail( cmd, STATUS_NONE, FONT_BOLD_ITALIC ) ) ;
	if ( rmdir( dname ) )
	{
		//Report error as warning and don't fail overall resize operation
		e = errno ;
		operationdetail .get_last_child() .set_status( STATUS_N_A ) ;
		operationdetail .get_last_child() .add_child( OperationDetail( String::ucompose( "rmdir(%1): ", dname ) + Glib::strerror( e ), STATUS_NONE ) ) ;
	}
	else
	{
		operationdetail .get_last_child() .set_status( STATUS_SUCCES ) ;
		operationdetail .get_last_child() .add_child( OperationDetail( Glib::ustring( "rmdir: removed directory: " ) + dname, STATUS_NONE ) ) ;
	}

	return return_value ;
}

bool nilfs2::move( const Partition & partition_new
               , const Partition & partition_old
               , OperationDetail & operationdetail
               )
{
	return true ;
}

bool nilfs2::copy( const Glib::ustring & src_part_path,
		 const Glib::ustring & dest_part_path,
		 OperationDetail & operationdetail )
{
	return true ;
}

bool nilfs2::check_repair( const Partition & partition, OperationDetail & operationdetail )
{
	return true ;
}

} //GParted
