/******************************************************************************/
/* FILES **********************************************************************/
/******************************************************************************/


#include <ebcl/Files.hh>
using namespace ebcl;


/*= T_File ==================================================================*/

T_File::T_File( )
	: path_( ) , file_( nullptr )
{ }

T_File::T_File( T_FSPath const& path ,
		const E_FileMode mode )
	: path_( path ) , mode_( mode ) , file_( nullptr )
{ }

T_File::T_File( T_File&& other ) noexcept
	: T_File( )
{
	swap( *this , other );
}

T_File& T_File::operator= ( T_File&& other ) noexcept
{
	swap( *this , other );
	return *this;
}

/*---------------------------------------------------------------------------*/

void T_File::open( )
{
	if ( file_ != nullptr ) {
		return;
	}

	// Copy file path to C string
	const T_Buffer< char > path( path_.toString( ).toOSString( ) );

	// Select the right mode
	char const* const mode( ([this]() {
		switch ( mode_ ) {
		    case E_FileMode::READ_ONLY:
			return "rb";
		    case E_FileMode::READ_WRITE:
			return "rb+";
		    case E_FileMode::OVERWRITE:
			return "wb+";
		}
		std::abort( );
	})( ) );

	// Open the file
#ifdef WIN32_
	file_ = _wfopen( ( wchar_t const* ) &path[ 0 ] , mode );
#else
	file_ = fopen( &path[ 0 ] , mode );
#endif
	if ( file_ == nullptr ) {
		throw X_StreamError( errno , path_ );
	}

	// Get initial size
	if ( fseek( file_ , 0 , SEEK_END ) != 0 ) {
		auto err( errno );
		close( );
		throw X_StreamError( err , path_ );
	}
	off_t sz( ftell( file_ ) );
	if ( sz < 0 || fseek( file_ , 0 , SEEK_SET ) != 0 ) {
		auto err( errno );
		close( );
		throw X_StreamError( err , path_ );
	}
	pos_ = 0;
	size_ = sz;
}

void T_File::close( ) noexcept
{
	if ( file_ != nullptr ) {
		fclose( file_ );
		file_ = nullptr;
	}
}

/*---------------------------------------------------------------------------*/

void T_File::position( size_t position , bool fromEnd )
{
	open( );
	const auto rv( fromEnd
		      ? fseek( file_ , -position , SEEK_END )
		      : fseek( file_ , +position , SEEK_SET ) );
	if ( rv < 0 ) {
		throw X_StreamError( errno , path_ );
	}
	pos_ = rv;
}

void T_File::move( ssize_t offset )
{
	open( );
	const auto rv( fseek( file_ , offset , SEEK_CUR ) );
	if ( rv < 0 ) {
		throw X_StreamError( errno , path_ );
	}
	pos_ = rv;
}

/*---------------------------------------------------------------------------*/

size_t T_File::read( void* data , size_t size )
{
	open( );
	if ( pos_ >= size_ ) {
		throw X_StreamError( E_StreamError::END , path_ );
	}

	const auto rv( fread( data , 1 , size , file_ ) );
	if ( ferror( file_ ) ) {
		throw X_StreamError( errno , path_ );
	} else {
		pos_ += rv;
		return rv;
	}
}

/*---------------------------------------------------------------------------*/

size_t T_File::write( void const* data , size_t size )
{
	if ( mode_ == E_FileMode::READ_ONLY ) {
		throw X_StreamError( E_StreamError::NOT_SUPPORTED , path_ );
	}

	open( );
	auto rv( fwrite( data , 1 , size , file_ ) );
	if ( ferror( file_ ) ) {
		throw X_StreamError( errno , path_ );
	} else {
		pos_ += rv;
		if ( pos_ > size_ ) {
			size_ = pos_;
		}
		return rv;
	}
}

/*---------------------------------------------------------------------------*/

void T_File::flush( )
{
	if ( !isOpen( ) || mode_ == E_FileMode::READ_ONLY ) {
		return;
	}
	fflush( file_ );
}


/*= T_FileInputStream =======================================================*/


T_FileInputStream::T_FileInputStream( T_File& file , ssize_t offset , size_t limit )
	: A_InputStream( 0 , 0 ) , fileRaw_( &file ) , fileOwned_( )
{
	init( offset , limit );
}

T_FileInputStream::T_FileInputStream( OP_File&& file , ssize_t offset , size_t limit )
	: A_InputStream( 0 , 0 ) , fileRaw_( nullptr ) , fileOwned_( std::move( file ) )
{
	file.clear( );
	init( offset , limit );
}


/*---------------------------------------------------------------------------*/

T_FileInputStream::T_FileInputStream( T_FileInputStream const& other )
	: A_InputStream( other.position( ) , other.size( ) ) ,
	fileRaw_( &( other.file( ) ) ) , fileOwned_( ) ,
	start_( other.start_ )
{ }

T_FileInputStream& T_FileInputStream::operator= ( T_FileInputStream const& other )
{
	position_ = other.position_;
	size_ = other.size_;
	fileRaw_ = &( other.file( ) );
	fileOwned_ = OP_File( );
	start_ = other.start_;
	return *this;
}

/*---------------------------------------------------------------------------*/

void T_FileInputStream::swap( T_FileInputStream& rhs ) noexcept
{
	using std::swap;
	swap( size_ , rhs.size_ );
	swap( position_ , rhs.position_ );
	swap( fileRaw_ , rhs.fileRaw_ );
	swap( fileOwned_ , rhs.fileOwned_ );
	swap( start_ , rhs.start_ );
}

/*---------------------------------------------------------------------------*/

size_t T_FileInputStream::read( void* data , size_t size )
{
	auto& f( file( ) );
	if ( position_ >= size_ ) {
		throw X_StreamError( E_StreamError::END , f.path( ) );
	}

	f.open( );
	if ( f.position( ) != start_ + position_ ) {
		f.position( start_ + position_ );
	}

	const size_t rSize( std::min( size , size_ - position_ ) );
	const auto r( f.read( data , rSize ) );
	position_ += r;
	return r;
}

/*---------------------------------------------------------------------------*/

void T_FileInputStream::init( ssize_t offset , size_t limit )
{
	auto& f( file( ) );
	f.open( );
	const ssize_t start( f.position( ) + offset );
	if ( start < 0 || start > ssize_t( f.size( ) ) ) {
		throw X_StreamError( E_StreamError::INVALID_POSITION ,
				f.path( ) );
	}
	start_ = start;
	size_ = std::min( f.size( ) - start , limit );
}


/*= T_FileOutputStream =======================================================*/


T_FileOutputStream::T_FileOutputStream( T_File& file , ssize_t offset )
	: A_OutputStream( 0 ) , fileRaw_( &file ) , fileOwned_( )
{
	init( offset );
}

T_FileOutputStream::T_FileOutputStream( OP_File&& file , ssize_t offset )
	: A_OutputStream( 0 ) , fileRaw_( nullptr ) , fileOwned_( std::move( file ) )
{
	init( offset );
}

/*---------------------------------------------------------------------------*/

T_FileOutputStream::T_FileOutputStream( T_FileOutputStream const& other )
	: A_OutputStream( other.position( ) ) , fileRaw_( &other.file( ) ) ,
		fileOwned_( ) , start_( other.start_ )
{ }

T_FileOutputStream& T_FileOutputStream::operator= ( T_FileOutputStream const& other )
{
	position_ = other.position_;
	fileRaw_ = &other.file( );
	fileOwned_ = OP_File( );
	start_ = other.start_;
	return *this;
}

/*---------------------------------------------------------------------------*/

inline void T_FileOutputStream::swap( T_FileOutputStream& rhs ) noexcept
{
	using std::swap;
	swap( position_ , rhs.position_ );
	swap( fileRaw_ , rhs.fileRaw_ );
	swap( fileOwned_ , rhs.fileOwned_ );
	swap( start_ , rhs.start_ );
}

/*---------------------------------------------------------------------------*/

size_t T_FileOutputStream::write( void const* data , size_t size )
{
	auto& f( file( ) );
	f.open( );
	if ( f.position( ) != start_ + position_ ) {
		f.position( start_ + position_ );
	}

	const auto w( f.write( data , size ) );
	position_ += w;
	return w;
}

/*---------------------------------------------------------------------------*/

void T_FileOutputStream::flush( )
{
	file( ).flush( );
}

/*---------------------------------------------------------------------------*/

void T_FileOutputStream::init( ssize_t offset )
{
	auto& f( file( ) );
	if ( f.mode( ) == E_FileMode::READ_ONLY ) {
		throw X_StreamError( E_StreamError::NOT_SUPPORTED ,
				f.path( ) );
	}
	const ssize_t start( f.position( ) + offset );
	if ( start < 0 || start > ssize_t( f.size( ) ) ) {
		throw X_StreamError( E_StreamError::INVALID_POSITION ,
				f.path( ) );
	}
	start_ = start;
}