
#ifndef DKUTIL_ARCHIVE_CONVERT_BLOCK_TO_STREAM_HPP
#define DKUTIL_ARCHIVE_CONVERT_BLOCK_TO_STREAM_HPP

#include <dkutil/archive/stream_to_convert_block.hpp>

namespace dkutil{

/**
@note
使い方
reset()して
getFirstHeader()やgetSecondHeader()をつかって

*/
template<class POLICY_T,class STREAM_T,class INTERRUPT_T>
class convert_block_reader
{
public:
	typedef typename POLICY_T policy_type;
	typedef typename STREAM_T stream_type;
	typedef typename INTERRUPT_T interrupt_type;
	typedef typename generic_convert_first_header first_header;
	typedef typename policy_type::second_header second_header;
	convert_block_reader(){
		mReseted = false;
		mIsEOF = false;
	}
	~convert_block_reader(){}
	/**
	内部でpolicyはbegin()される。
	*/
	bool reset(policy_type *policy,stream_type *stream)
	{
		
		if(headers_size() > stream->size()){
			return false;
		}

		mFH.serialize(stream);
		mSH.serialize(stream);

		mP = policy;
		mP->begin();
		mS = stream;
		mStreamBegin = mS->tell();
		mReseted=true;
		mIsEOF = false;
		
		return true;
	}
	bool getFirstHeader(first_header *p)
	{
		DKUTIL_RETURN_FALSE(mReseted);
		*p = mFH
		return true;
	}
	bool getSecondHeader(second_header *p)
	{
		DKUTIL_RETURN_FALSE(mReseted);
		*p = mSH;
		return true;
	}
	size_t headers_size()const{
		return mFH.serialize_size() + mSH.serialize_size();
	}
	int readsize_correct(size_t &readsize,bool &isEOF){
		size_t bs=mS->tell(),limit = mFH.mConvertedSize;
		{//overflow and logic check
			if(bs < mStreamBegin){
				DKUTIL_THROW_OR_NOTICE(convert_error("convert_block_reader::read() stream offset changed..."));
				return edk_FAILED;
			}
			if(dkcCheckOverflow64(mStreamBegin,limit)){
				return edk_FAILED;
			}
			uint64 t = (mStreamBegin + limit);
			if(t < bs){//stream length over...
				return edk_LogicError;
			}
			t -= bs;
			if(t > dkcdUINT32_MAX){//too big length...
				return edk_LogicError;
			}
			if(readsize > t){//limit
				readsize = (size_t)t;
				isEOF = true;
			}
		}
		return edk_SUCCEEDED;
	}

	
	/**
	@param x[out] バッファ
	@param readsize[in][out] readするサイズおよびreadしたサイズ
	@param buffer_limit[in] xのリミットサイズを入れる
	*/
	int read(shared_buffer &x,size_t &readsize,size_t buffer_limit = 1024 * 1024){
		
		bool isEOF = false;
		
		if(false==mReseted)
			return edk_LogicError;

		{
			int r = readsize_correct(readsize,isEOF);
			if(DKUTIL_FAILED(r)){
				return r;
			}
		}



		//read process

		size_t cs = mP->getConvertOutputSize(readsize);
		if(cs > buffer_limit){//limit over
			return edk_BufferOverFlow;
		}
		if(false==x.if_resize(cs) || false==mIB.if_resize(readsize))
		{
			return edk_OutOfMemory;
		}
			
		if(false==mS->read(mIB.get(),readsize)){
			return edk_FAILED;
		}
		
		
		IStreamConvert::convert_result r;
		
		if(false==mP->convert(x.get(),cs,(const uint8 *)mIB.get(),readsize,&r)){
			return edk_FAILED;
		}
		//DKUTIL_RETURN_FALSE(x.copy(mIB.get(),r.converted_size));
		if(isEOF){
			mIsEOF = true;
			mReseted = false;
			mP->end(NULL);
		}
		return edk_SUCCEEDED;
	}
	bool eof()const{
		return mIsEOF;
	}
	int all_read(stream_type *dest,
		void *user_ptr,interrupt_type it,
		size_t inner_buffer_size = DKUTIL_DEFAULT_INNER_BUFFER_SIZE,
		size_t buffer_limit = DKUTIL_DEFAULT_INNER_BUFFER_SIZE * 4,
		bool stream_data_show = false)
	{
		

		size_t t=mS->tell();
		if(false==mReseted || mStreamBegin != t)
			return edk_LogicError;
		if(dest->is_write_mode()==false)
			return edk_ArgumentException;

		shared_buffer buff;
		if(false==buff.reset(inner_buffer_size))
		{
			return edk_OutOfMemory;
		}

		stream_process_interrupt_arg arg;
		//first interrupt
		{
			scoped_buffer_byte tb;
			arg.mAllSize = mFH.mConvertedSize + headers_size();
			arg.mFinishedSize += headers_size();
			arg.mNowFinishedSize = headers_size();
			if(stream_data_show){
				tb.if_resize(headers_size());
				
				memcpy(tb.get(),&mFH,mFH.serialize_size());
				memcpy(tb.get() + mFH.serialize_size(),&mSH,mSH.serialize_size());
				arg.mStreamData = (const void *)tb.get();
			}else{
				arg.mStreamData = (const void *)NULL;
			}
			it(&arg,user_ptr);
		}


		uint64 limit;
		if(dkcCheckOverflow64(t,mFH.mConvertedSize)){
			return edk_VariableOverFlow;
		}
		limit = t + mFH.mConvertedSize;
		
		
		size_t buffsize = inner_buffer_size;
		for(;mS->tell() < limit;)
		{
			
			//limitは計算してくれる buffsizeに実際に読み取ったサイズが返される
			int tr = read(buff,buffsize,inner_buffer_size);
			
			if(DKUTIL_FAILED(tr))
			{
				return tr;
			}
		
			arg.mFinishedSize += buffsize;
			arg.mNowFinishedSize = buffsize;
			arg.mStreamData = (const void *)buff.get();

			//interrupt
			it(&arg,user_ptr);
			
			if(false==dest->write(buff.get(),buffsize))
			{
				return edk_FAILED;
			}
			buffsize = inner_buffer_size;
		}
		//mReseted = false;
		return edk_SUCCEEDED;
	}

private:
	first_header mFH;
	second_header mSH;
	shared_buffer mIB;
	seek_arg_type mStreamBegin;
	policy_type *mP;
	stream_type *mS;
	bool mReseted;
	bool mIsEOF;
};


template<class T,class FS_T,class INTERRUPT_T>
class convert_block_to_stream_functor{
public:
	typedef typename T policy_type;
	///file stream type
	typedef typename FS_T fs_type;
	typedef typename INTERRUPT_T interrupt_type;
	typedef typename convert_block_reader<policy_type,fs_type,interrupt_type> reader;
	typedef typename convert_block_to_stream_functor<T,FS_T,INTERRUPT_T> self_type;
	typedef typename stream_process_interrupt_functor_base null_interrupt_functor;
	typedef typename reader::first_header first_header;
	typedef typename reader::second_header second_header;
protected:
	reader mReader;
	int mState;
	class convert_functor{
		
		policy_type *mPolicy;
		fs_type *mDest;
		void *mUserPtr;
		interrupt_type mIt;
		size_t mInnerBufferSize;
		stream_process_interrupt_arg mArg;
	public:
		convert_functor(policy_type *policy,fs_type *dest,void *user_ptr,
			interrupt_type it,size_t inner_buffer_size,uint64 allsize) : mArg(allsize)
		{
			mPolicy = policy;
			mDest = dest;
			mUserPtr = user_ptr;
			mIt = it;
			mInnerBufferSize = inner_buffer_size;
			if(dest->is_write_mode()){
				DKUTIL_THROW_OR_NOTICE(convert_error("convert_functor constructor not write_mode"));
			}
		}

		void operator()(const uint8 *p,size_t size){
			
			stream_process_interrupt_arg arg;
			
		
			mPolicy->convert(p,size);
			mArg.mFinishedSize += size;
			//stack上で
			arg = mArg;
			arg.mNowFinishedSize = size;
			arg.mStreamData = (const void *)p;

			//interrupt
			mIt(&arg,mUserPtr);
			//write
			mDest->write(p,size);
		}



	};
public:
	convert_block_to_stream_functor(){
		mState = edk_FAILED;
	}
	~convert_block_to_stream_functor(){}
	int getState()const{
		return mState;
	}



	bool operator()(fs_type *dest,fs_type *src,size_t srcsize,
		policy_type *policy,
		void *user_ptr = NULL,
		interrupt_type it=interrupt_type(),
		size_t inner_buffer_size = DKUTIL_DEFAULT_INNER_BUFFER_SIZE,
		size_t buffer_limit = DKUTIL_DEFAULT_INNER_BUFFER_SIZE * 4)
	{
		if(dest->is_write_mode()==false) return false;
		if(src->is_read_mode()==false) return false;

		DKUTIL_RETURN_FALSE(mReader.reset(policy,src));
		mState = mReader.all_read(dest,user_ptr,it,inner_buffer_size,buffer_limit);
		if(DKUTIL_FAILED(mState))
			return false;
		
	
		return true;
	}
	bool operator()(
		const PasswordOption &pass,uint32 signature,
		fs_type *dest,fs_type *src,size_t srcsize,
		void *user_ptr = NULL,
		interrupt_type it=interrupt_type(),
		size_t inner_buffer_size = DKUTIL_DEFAULT_INNER_BUFFER_SIZE)
	{
		policy_type p(pass,signature,inner_buffer_size);
		return operator()(dest,src,srcsize,&p,user_ptr,it,inner_buffer_size);
	}
};

/**
@param pass[in] パスワード
@param signature[in] 識別値（自由に設定してよし）
@param dest[in][out] ストリームオブジェクトへのポインタ
@param srcfile[in] ファイルへのパス
@param user_ptr[in] interrupt_で使うポインタ
@param interrupt_[in] 割り込み処理用ファンクター
@param inner_buffer_size[in] 内部のテンポラリバッファのサイズ
*/
template<class STREAM_T,class INTERRUPT_T>
inline bool ArcfourDecodeFileToStream(
	const PasswordOption &pass,uint32 signature,
	STREAM_T *dest,path_string_ref srcfile,
	void *user_ptr = NULL,
	INTERRUPT_T interrupt_ = INTERRUPT_T(),
	size_t inner_buffer_size = DKUTIL_PROPER_TEMPORARY_BUFFER_SIZE())
{
	convert_block_to_stream_functor<CArcfourConvertStream,STREAM_T,INTERRUPT_T> a;
	DKUTIL_RETURN_FALSE(
		a(
			pass,signature,dest,srcfile,user_ptr,interrupt_,inner_buffer_size
		)
	);
	return true;
}



template<class STREAM_T,class INTERRUPT_T>
inline bool HC256EncodeFileToStream(
	const PasswordOption &pass,uint32 signature,
	STREAM_T *dest,path_string_ref srcfile,
	void *user_ptr = NULL,
	INTERRUPT_T interrupt_ = INTERRUPT_T(),
	size_t inner_buffer_size = DKUTIL_PROPER_TEMPORARY_BUFFER_SIZE())
{
	convert_block_to_stream_functor<CHC256ConvertStream,STREAM_T,INTERRUPT_T> a;
	DKUTIL_RETURN_FALSE(
		a(
			pass,signature,dest,srcfile,user_ptr,interrupt_,inner_buffer_size
		)
	);
	return true;
}

/**
#define ArcfourDecodeFileToStream ArcfourEncodeFileToStream
template<class STREAM_T,class INTERRUPT_T>
inline bool ArcfourDecodeFileToStream(
	const PasswordOption &pass,uint32 signature,
	STREAM_T *dest,path_string_ref srcfile,
	void *user_ptr = NULL,
	INTERRUPT_T interrupt_ = INTERRUPT_T(),
	size_t inner_buffer_size = DKUTIL_PROPER_TEMPORARY_BUFFER_SIZE())
{
	
	file_to_stream_convert_functor<CArcfourConvertStream,STREAM_T,INTERRUPT_T> a;
	DKUTIL_RETURN_FALSE(
		a(
			pass,signature,dest,srcfile,user_ptr,interrupt_,inner_buffer_size
		)
	);
	return true;
}

template<class STREAM_T,class INTERRUPT_T>
inline bool HC256DecodeFileToFile(
	const PasswordOption &pass,
	path_string_ref destfile,path_string_ref srcfile,
	bool overwrite = false,std::ostream *pos = NULL,size_t inner_buffer_size = 1024 * 512)
{
	file_to_stream_convert_functor<CHC256DecodeStream> a;
	a.set_ostream(pos);
	return a.convert(pass,destfile,srcfile,overwrite,inner_buffer_size);
}
*/
}//end of dkutil namespace





#endif