/**
@file big_filestream.hpp
@brief 64bitのサイズのファイルも扱えるストリーム


*/
#ifndef DKUTIL_FILE_SYSTEM_BIG_FILESTREAM_HPP
#define DKUTIL_FILE_SYSTEM_BIG_FILESTREAM_HPP

#include <dkutil/filesystem/interface.hpp>
#include <dkutil/synchronized.hpp>
#include <dkutil/os.hpp>
#include <dkutil/macro.hpp>
#include <dkutil/output.hpp>



namespace dkutil{

namespace policy{


#ifdef WIN32


class bigfile_operate_policy{

public:
	struct open_arg_data{
		DWORD DesiredAccess; // access (read-write) mode

		DWORD ShareMode; // share mode

		LPSECURITY_ATTRIBUTES lpSecurityAttributes;// pointer to security attributes

		DWORD CreationDisposition; // how to create

		DWORD FlagsAndAttributes; // file attributes

		HANDLE TemplateFile; // handle to file with 
	};
	typedef uint64 size_type;
	typedef size_type size_type64;
	typedef std::size_t size_type32;
	typedef HANDLE handle_type;
private:
	///ハンドル
	HANDLE mfp;
	///Openするときの引数の保存
	open_arg_data mArg;
	///ファイルのオフセット
	ULONGLONG mOffset;
	///
	OVERLAPPED mOL;


	///GetLastErrorの保存用
	DWORD mLastError;

	///EOFの時TRUE eof()を呼び出されるとフラグがfalseになる･･･。
	bool mIsEOF;

	void _construct___(){
	
		mfp = INVALID_HANDLE_VALUE;
		mIsEOF = false;
		mLastError = NO_ERROR;
		DKUTIL_STRUCTURE_INIT(mOL);
	}
	static void exception__(const char *p){
		throw filesystem_error(p);
	}
	void check_fp()const{
		if(mfp==INVALID_HANDLE_VALUE){
			exception__("big_file_operator:openしてない。");
		}
	}

	void correct_copy_arg_data(open_arg_data &data,const open_arg_data &x)
	{
		data = x;

		if(isOS9x()){
			//WinNTのみ
			if(data.ShareMode & FILE_SHARE_DELETE){
				DKUTIL_FLAG_DOWN(data.ShareMode,FILE_SHARE_DELETE);
			}
			//FILE_FLAG_BACKUP_SEMANTICS 
			//Windows NT のみ : バックアップまたは復元操作のために、ファイルをオープンまたは作成します。 
			if(data.FlagsAndAttributes & FILE_FLAG_BACKUP_SEMANTICS){
				DKUTIL_FLAG_DOWN(data.FlagsAndAttributes,FILE_FLAG_BACKUP_SEMANTICS);
			}
			//NULLで無い場合は失敗する。
			if(isOSWin95()){
				if(data.TemplateFile){
					throw filesystem_error("Windows95なのにTemplateFileを指定している。ヽ(`Д´)ﾉムキィ");
				}
			}

		}
	}
	inline void to_arg_data(open_arg_data &data,ULONG flag)
	{
		bool wm = (flag & write_mode) != 0;
		bool rm = (flag & read_mode) != 0;
		bool pm = (flag & postscript_mode) != 0;
	
		if(pm){	//追記モード
			/*if(wm && rm){
				data.CreationDisposition |= OPEN_EXISTING;
			}else if(wm){
				data.CreationDisposition |= OPEN_EXISTING;
			}else if(rm){
				data.CreationDisposition |= OPEN_EXISTING;
			}*/
			data.CreationDisposition |= OPEN_EXISTING;
		}else{//追記じゃないモード
			if(wm && rm){
				//もし、存在したらオープンする指定
				data.CreationDisposition |= OPEN_EXISTING;
			}else if(wm){
				//上書きし指定
				data.CreationDisposition |= CREATE_ALWAYS;
			}else if(rm){
				//存在したらオープンする指定
				data.CreationDisposition |= OPEN_EXISTING;
			}

		}


		if(wm){
			//書き込み中は読み込みだけ許可
			data.ShareMode |= FILE_SHARE_READ;
			
			data.DesiredAccess |= GENERIC_WRITE;
		}
		if(rm){
			//同じく読み込み中も
			data.ShareMode |= FILE_SHARE_READ;
			
			data.DesiredAccess |= GENERIC_READ;
		}



		//普通ファイル指定
		data.FlagsAndAttributes |= FILE_ATTRIBUTE_NORMAL;

		
	}
public:
	static void visual_lasterror(){
#	ifdef DEBUG
		VISUAL_LASTERROR();
#	else
		Win32LastError err;
		dODS(err());
#	endif
	}
	BOOST_STATIC_CONSTANT(int,invalid_handle = INVALID_HANDLE_VALUE);

	/*bigfile_operate_policy(parm_string filename,open_arg_data &data){
		_construct___();
		if(filename.c_str()){
			mfilename = filename.c_str();
		}
		mArg = data;
		
	}*/
	bigfile_operate_policy(){
		_construct___();
	}

	/*bigfile_operate_policy(const bigfile_operate_policy &c){
		_construct___();
		mfp = c.mfp;
		//mfilename = c.filename();//.c_str();
		//mArg = c.mArg;
		
		
	}*/

	virtual ~bigfile_operate_policy(){
		close();
	}


	inline bool open(const char *filename,ULONG flag)
	{
		open_arg_data data={0};
		bool r = false;
		
		//dkcmFORCE_NOT_ASSERT(flag & text_mode || "テキストモードはサポートしていない");
		to_arg_data(data,flag);

		
		if(flag & postscript_mode){
			size_type64 sz = size(filename);
			if(sz > LONGLONG_MAX){
				return false;
			}
			r = open(filename,data,(LONGLONG)sz);
		}else{
			r = open(filename,data);
		}
	
		return r;
	}
	/*!
	@param filename[in] ファイル名
	@param Arg[in] open_arg_data構造体への参照
	@param file_poiner[in] ファイルの書き込み、読み出しは何バイト目からはじめるかの値
	*/
	bool open(const char *filename,const open_arg_data &GetArg,LONGLONG file_pointer = 0)
	{
		close();

		open_arg_data Arg;

		correct_copy_arg_data(Arg,GetArg);
				


		mfp = CreateFile(
				filename,
				Arg.DesiredAccess, // access (read-write) mode
				Arg.ShareMode, // share mode
				Arg.lpSecurityAttributes,// pointer to security attributes
				Arg.CreationDisposition, // how to create
				Arg.FlagsAndAttributes,// file attributes
				Arg.TemplateFile // handle to file with 
			//GENERIC_WRITE, 0, 0, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL
		);
		if(mfp==INVALID_HANDLE_VALUE){
			visual_lasterror();
			return false;
		}

		/*DWORD high,low;
		
		dkcULONGLONGToTwoDWORD(&high,&low,file_pointer);
		*/
		LONG high,low;
		dkcLONGLONGToTwoLONG(&high,&low,file_pointer);

		SetFilePointer(
			mfp,    // ファイルハンドル 
			low,    // ファイルポインタを移動する距離
			&high,    // 距離６４ビット分の上位３２ビット 
			FILE_BEGIN     // 移動の仕方 
			 );

		//オフセット記憶
		mOffset =file_pointer;

		//引数記憶
		mArg = Arg;

		//EOFフラグを倒す
		mIsEOF = false;

		//LastErrorを初期化
		mLastError = NO_ERROR;

		return true;
	}
	HANDLE handle()const{
		return mfp;
	}
	/*HANDLE *handle(){
		return mfp;
		}*/

	/*size_type64 filesize()const{
		check_fp();
		size_type64 ull;
		dkcFileSize64(filename(),&ull);
		return ull;
	}*/
	size_type read(void *buf,size_type32 size){
		check_fp();
		DWORD red = 0;
		//OVERLAPPED OverLapped={0};
		//hFile ハンドルが FILE_FLAG_OVERLAPPED フラグを持つときに、lpOverlapped パラメータに NULL を指定すると、この関数は、読み取り操作が完了したと間違って通知することがあります。FILE_FLAG_OVERLAPPED フラグを持つときは、NULL を指定しないでください。
		BOOL r = ReadFile(
			mfp, // handle of file to read
			buf, // pointer to buffer that receives data
			size, // number of bytes to read
			&red, // pointer to number of bytes read
			NULL//&mOL // pointer to structure for data
		);

		if(FALSE==r)
		{
			DWORD LastError =  GetLastError();
			mLastError = LastError;

			if ( LastError == ERROR_ACCESS_DENIED ){
				 visual_lasterror();
				 return false;
			}
			else if ( LastError == ERROR_BROKEN_PIPE ){
				mIsEOF = true;
				 return false;
			}
			/*else {//error
        
					return -1;
			}*/
		}
		if (FALSE != r && red== 0)
		{// ファイルの終わりに達した。
			mIsEOF = true;
		}
		//if(TRUE==r && 0==red || tell() >= size()){
		//}
		/*
		同期的
		bResult = ReadFile(hFile, &inBuffer, nBytesToRead, &nBytesRead, NULL) ;

		// ファイルの終わりをチェックする。

		if (bResult && nBytesRead == 0,)

		{

				// ファイルの終わりに達した。

		}
		非同期
		 関数を呼び出して非同期の読み取り操作を行った際に EOF を検出した場合、
		 ReadFile 関数は 0（FALSE）を返し、GetLastError 関数は ERROR_HANDLE_EOF を返します。

		*/
		mOffset += red;

		return red;
	}

	size_type ref(void *buf,size_type32 size){
		size_type trs = 0;
		size_type64 to = 0;
		synchronized swimming;
		to = tell();
		trs = read(buf,size);
		if(false==seek(to,seek_begin)){
			goto Exception;
		}
		return trs;
	Exception:
		DKUTIL_THROW_OR_NOTICE(filesystem_error("big_file_operate_policy::ref() seek error"));
		return trs;
	}

	size_type write(const void *buf,size_type32 size){
		check_fp();
		DWORD dwAccBytes;
		BOOL r = WriteFile(mfp, buf, size , &dwAccBytes, NULL);
		if(r==FALSE){
			mLastError = GetLastError();
		}
		mOffset += size;

		return dwAccBytes;
	}
#if 0
	bool seek(long offset,int origin){
		return seek(offset,0,origin);
	}
#else
	bool seek(seek_arg_type offset,int origin){
		LONG high,low;
		dkcLONGLONGToTwoLONG(&high,&low,offset);
		return seek(low,high,origin);
	}

#endif
	bool seek(LONG low,LONG high,DWORD get_origin ){
		check_fp();
		DWORD origin;
		switch(get_origin){
		case seek_begin:
			origin = FILE_BEGIN;
			break;
		case seek_current:
			origin = FILE_CURRENT;
			break;
		case seek_end:
			origin = FILE_END;
			break;
		}
		
		//hFile ハンドルが FILE_FLAG_NO_BUFFERING を持つ場合は、セクタサイズの整数倍の位置にしかファイルポインタを移動できません。
		// 事例 2 : lpDistanceToMoveHigh != NULL の場合
		DWORD dw = SetFilePointer(mfp,
								low,
								&high, origin//FILE_BEGIN
			);

		//multithread lock

		//これではダメ?
		//if (dw == 0xFFFFFFFF && error())
		
		
		if(dw == 0xFFFFFFFF)//SetFilePointerにhighを渡しているのでこの条件が必要 from MSDN
		{
			DWORD LastError = GetLastError();
			mLastError = LastError;
			if(LastError != NOERROR)
			{
				visual_lasterror();
				//< エラー処理 >
				return false;
			}
		}
		//んなわけあるかい！
		{
			//シーク成功したらとりあえずEOF解除･･･　ヽ(`Д´)ﾉｳﾜｧﾝ
			//mIsEOF = false;
		}
		{
			LARGE_INTEGER temp;
			temp.QuadPart = tell();
			//ファイル終端のサイズを取得
			LONGLONG ll;
			LONG high = 0, low = 0;
			low = SetFilePointer(mfp,low,&high, FILE_END);
			dkcTwoLONGToLONGLONG(&ll,high,low);
			
			if(ll != tell()){
				mIsEOF = false;
			}
			

			DWORD dw = SetFilePointer(mfp,temp.LowPart,&(temp.HighPart), FILE_BEGIN	);
			if(dw == 0xFFFFFFFF && GetLastError() != NOERROR)//SetFilePointerにhighを渡しているのでこの条件が必要 from MSDN
			{
				DKUTIL_THROW_OR_NOTICE(filesystem_error("seek eof check error"));
			}

		}
		return true;
	}
	///ファイルの最初からsizeバイトシークする。
	//bool seek_for_begin(size_type size){
	bool seek(seek_arg_type size)
	{
		LONG high,low;
		dkcLONGLONGToTwoLONG(&high,&low,size);
		
		return seek(low,high,seek_begin);
	}

	seek_arg_type tell()const{
		check_fp();
		LONGLONG ull = 0;
		
		LONG high = 0, low = 0;
		low = SetFilePointer(mfp,
								low,
								&high, 
								FILE_CURRENT
								//FILE_BEGIN
			);

		//multithread lock
		
		//lowと high を一緒にゲットしているので･･･　これは間違い
		//if (low == 0xFFFFFFFF && error())
		if(0xFFFFFFFF==low)//lpDistanceToMoveHigh パラメータが NULL 以外のときに
		{
			DWORD LastError = GetLastError();
			if(LastError != NOERROR)
			{
				visual_lasterror();
				//< エラー処理 >
				return false;
			}
		}

		dkcTwoLONGToLONGLONG(&ull,high,low);
		return ull;
	}
	///非奨励型
	size_type64 tell64()const{
		check_fp();
		size_type64 ull = 0;
		
		LONG high = 0, low = 0;
		low = SetFilePointer(mfp,
								low,
								&high, 
								FILE_CURRENT
								//FILE_BEGIN
			);

		//multithread lock
		
		//lowと high を一緒にゲットしているので･･･　これは間違い
		//if (low == 0xFFFFFFFF && error())
		if(0xFFFFFFFF==low)//lpDistanceToMoveHigh パラメータが NULL 以外のときに
		{
			DWORD LastError = GetLastError();
			if(LastError != NOERROR)
			{
				visual_lasterror();
				//< エラー処理 >
				return false;
			}
		}

		dkcTwoLONGToULONGLONG(&ull,high,low);
		return ull;

	}
	/*
	size_type tell(){

		check_fp();
		LONGLONG ull = 0;
		
		LONG high = 0, low = 0;
		low = SetFilePointer(mfp,
								low,
								&high, FILE_CURRENT
			);


		DWORD LastError = GetLastError();
		mLastError = LastError;
		if(LastError != NOERROR)
		{
			visual_lasterror();
			//< エラー処理 >
			return false;
		}

		dkcTwoLONGToLONGLONG(&ull,high,low);
		return (size_type)ull;
	}*/
	
	bool flush(){
		return (0==FlushFileBuffers(mfp));
	}
	///ただ、ファイルを閉じる。
	void close(){
		//check_fp();
		if(mfp==INVALID_HANDLE_VALUE){
			return;
		}
		
		if(CloseHandle(mfp) == 0){
			visual_lasterror();
		}
		mfp = INVALID_HANDLE_VALUE;
		
	}
	///ファイルの終端の印をつける。
	void write_endoffile(){
		DWORD high=0,low=0,result=0;

		//seek(low,high,FILE_BEGIN);
		flush();
		SetEndOfFile(mfp);
	}
	///ファイル終了の印を付けてファイルを閉じる。
	void close_endoffile(){
		write_endoffile();
		close();
	}


	size_type64 size()const{

		DWORD high = 0,low = 0;
		size_type64 ull;

		check_fp();
		low = ::GetFileSize(mfp,&high);
		dkcTwoDWORDToULONGLONG(&ull,high,low);

		return ull;
	}
	bool eof()const{
		check_fp();
		//dkcmNOT_ASSERT("実装されてヘンよ");
		if(::GetLastError() == ERROR_HANDLE_EOF){
			return true;
		}
		{

			register seek_arg_type pos=tell();
			return 0<=pos && size()<=pos;//これが正しい。
		}
		//リファレンスに書いているとおりの条件、または、tell()がファイルサイズを超えている場合
		//注意：tell()は0から数える size()は1から数える　よって･･･
		//return (tell() >= this->size()); 
		
		
		//EOFフラグの更新処理とか書くの面倒！だから一回きりの寿命で･･･（スマンヽ(`Д´)ﾉｳﾜｧﾝ）
		return mIsEOF;//ここのフラグが間違っているからどうにかすること！！
	}	

	bool error()const{
		check_fp();
		///この実装だと　read や write 前に呼び出すとNO_ERROR以外を返す可能性がある。
		//return (::GetLastError() != NO_ERROR);
		return mLastError;
	}
	static size_type64 size(const char *filename){
		DWORD high = 0,low = 0;
		ULONGLONG ull;
		BOOL r = dkcFileSize64(filename,&high,&low);
		if(FALSE==r){
			//std::string s = filename;
			//s += "not found";
			//throw filesystem_error(filename);
			DKUTIL_THROW_OR_NOTICE(filesystem_error(filename));
		}
		dkcTwoDWORDToULONGLONG(&ull,high,low);
		return ull;
	}
	///本当は18446744073709551614byteまでOKだがここはseek_arg_typeのlimit()を使おうと思う。スマン
	BOOST_STATIC_CONSTANT(uint64,limit_size = ULONGLONG_MAX);


};




#else


#endif



}//end of policy namespace


typedef filestream_interface<policy::bigfile_operate_policy> big_filestream;



///どんなに大きいファイルでも読み込めるもの
class CBigFileStream : public IFileStream{
public:
	typedef IFileStream base_type;
	typedef base_type::size_type64 size_type64;
	typedef base_type::size_type size_type;
	typedef CBigFileStream self_type;
private:
	big_filestream mS;
public:
	CBigFileStream() : base_type(read_mode | binary_mode){
		

	}
	CBigFileStream(const char *filename,UINT flag) : base_type(flag){
		

		reset(filename,flag);

	}
	virtual ~CBigFileStream(){
		//clear();なんかバグるのでmSのデストラクタに任せる。
	}
	///内部バッファをクリアする。
	virtual void clear(){
		mS.close();
	}
	virtual bool reset(const char *filename,UINT flag){
		clear();
		char f[4]="";
		if(false==mS.reset(filename,flag)){
			return false;
		}
		base_type::setOpenModeFlag(flag);
		return mS.open();
	}
	/*virtual ISerializeStream& operator << (ISerializeStream &c){
		*this << 

	}*/
	seek_arg_type limit()const{
		return mS.limit();
	}
	//ファイルのサイズ
	/*virtual size_type64 filesize()const{
		
		//return mS.filesize();
		//return mS.size();
	}*/
	///バッファをフラッシュ((ファイルに完全に書きこむ))する。
	virtual bool flush(){
		return mS.flush();
	}
	virtual bool good()const{
		int tmp = big_filestream::invalid_handle;
		return mS.handle() != (big_filestream::handle_type)tmp;
	}
	/*!
	@param buff[out] バッファー
	@param size[in] バッファのサイズ
	@param readsize[in] 実際に読み込んだサイズ
	@return falseならエラー。（バッファが足りない等）
	*/
	///読み込む
	virtual bool read(void *buff,size_t size,size_t *readsize){

		bool r = false;
		//sizeのテンポラリ
		size_t tsize = size;
		//読み込んだサイズのテンポラリ
		size_t t;
		//既に終わったサイズのテンポラリ
		size_t tf = 0;
		char *buf = (char *)buff;
		while(1){
			t = mS.read(buf,tsize);
			tf += t;

			if(t == size){
				break;
			}else if(tf == size){
				break;
			}
			if(eof()){
				goto End;
			}
			if(error()){
				goto End;
			}
			tsize = tsize - t;
			buf = (char *)buf + t;

		}
#	ifdef DEBUG
		/*if(redsize > ULONG_MAX){
			DKUTIL_THROW_OR_NOTICE(filesysytem_error("んなアホがあるか！"));
		}*/
#	endif
		r = (error()==false);
	End:
		if(readsize){//読みこんだ分
			*readsize = tf;
		}
		return r;
	}

	///書き込む(エンディアンチェンジは無し) @see IStream::write()
	virtual size_t write(const void *buff,size_t size){
		mS.responsible_write(buff,size);
		return size;

	}

	///責任を持ってすべてを書き込まないタイプ
	virtual bool write(const void *buff,size_t size,size_t *write_size)
	{
		dkcmASSERT(write_size);
		*write_size = mS.write(buff,size);
		return error()==false;
	}
	/*!
	@param buff[out] バッファー
	@param size[in] バッファのサイズ
	@param readsize[in] 実際に読み込んだサイズ
	@return falseならエラー。
	@note
	read()との違い：
	ref()は内部のオフセットカウントを変更しない。
	*/
	///中身を見る。
	virtual bool ref(void *buff,size_t size,size_t *readsize){
		size_type redsize = mS.ref(buff,size);
#	ifdef DEBUG
		if(redsize > ULONG_MAX){
			DKUTIL_THROW_OR_NOTICE(filesystem_error("んなアホがあるか！"));
		}
#	endif
		if(readsize){
			*readsize = (size_t)redsize;
		}
		return error()==false;
	}
	/*!
	@param offset[in] origin指定から何バイト、シークするか。
	@param origin[in] 
	@note
	動作はfseekと同じです。（爆）
	originも実はfseekと同じ指定子をぶち込んでも動いちゃったりします＾＾；
	*/
	///シークする。
	virtual bool seek(seek_arg_type offset,int origin){
		return mS.seek(offset,origin);
	}
	///今のバッファの最初からのオフセットを返す。
	virtual seek_arg_type tell()const{
		return mS.tell();
	}
	virtual bool eof()const{
		return mS.eof();
	}
	virtual bool error()const{
		return mS.error();
	}
	/*!
	@note
	serialize_interfaceのポインタをぶち込めば、
	シリアライズしたり、デシリアライズしたりしてくれます。
	*/
	///シリアライズする。
	//virtual bool serialize(serialize_interface *) = 0;
	///@see IStream::data()
	virtual const void *data()const{
		return NULL;
	}
	///内部バッファのサイズ。
	virtual size_type size()const{
		return 0;
	}
	///内部バッファをリサイズする。
	virtual void resize(size_type size){
		return;
	}

	virtual int type()const{
		return file_stream_type;
	}
	virtual const char *filename()const{
		return mS.filename();
	}
	




};


}//end of dkutil namespace



#endif



