
#ifndef DKUTIL_ARCHIVE_PROFESSIONAL_HPP
#define DKUTIL_ARCHIVE_PROFESSIONAL_HPP

#include <dkutil/archive/pack.hpp>
#include <dkutil/output/logger.hpp>

namespace dkutil{


	struct CProfessionalArchiveDefine{
		typedef CPackArchiveGenerate base_archive_generator;
		typedef CPackArchiveExtract base_archive_extractor;

		///SHA512を計算させると共にwrite()する。
		typedef write_with_hash_calculate_functor<fs_type,CSHA512> write_withSHA512_functor;
		typedef read_with_hash_calculate_functor<fs_type,CSHA512> read_withSHA512_functor;
	};
	/**
	仕様：little endian base
	*/
	class CProfessionalArchiveGenerate : public IdKingyoArchiveGenerate
		,public CProfessionalArchiveDefine,public COutputLog_Base
	{
	public:
		typedef IdKingyoArchiveGenerate base_type;
		typedef CProfessionalArchiveGenerate self_type;

		typedef CProfessionalArchiveDefine define_type;

		typedef define_type::base_archive_generator archive_generator;
		typedef define_type::write_withSHA512_functor write_withSHA512_functor;
		typedef define_type::read_withSHA512_functor read_withSHA512_functor;
		typedef define_type::header_cipher_encoder header_cipher_encoder;

		BOOST_STATIC_CONSTANT(uint32,signature = eda_Professional);
		static uint32 getSignature(){
			return signature;
		}
		///これでversion1
		BOOST_STATIC_CONSTANT(uint32,version = 0x00000010);
		static uint32 getVersion(){
			return version;
		}
	protected:

		typedef std::list<data_list> archive_list;

		
		//IConvertSerializeStream::convert_ptr mCS;
	public:

		
		CProfessionalArchiveGenerate(){
			//reset(filename);
			mPS = NULL;
			mCS = NULL;
		}
		virtual ~CProfessionalArchiveGenerate(){
			clear();
		}
		void clear(){
			base_type::clear();
		}
		void setOutputStream(std::ostream *p){
			mPS = p;
		}
		//ファイル全体の出力に変換プログラムを挿入する
		//void setConvertStream(IConvertSerializeStream::convert_ptr p){	mCS = p;}
		/**
		アーカイブを生成する
		*/
		virtual bool generate(const dKingyoArchiveGenerateOption &x)
		{
		
			uint64 al_offset;
			uint64 allsize = 0;
			uint64 data_length_count = 0;
			std::string filename = x.mFilename;
			fs_type FStream;//scope outを狙っている
			if(false==FStream.reset(filename,write_mode | binary_mode))
			{
				return false;
			}
			if(false==FStream.open()){
				return false;
			}
			

			if(FStream.error()){
				return false;
			}
			archive_list al;
			//データを読み込む
			{
				size_t size = mC.size();
				container_type::iterator it = mC.begin();
				for(;it != mC.end();it++)
				{
					const dKingyoArchiveReserveElement &a = (*it);
					data_list dl;
					if(a.mIsFile)
					{
						if(false==FileExist(a.mFilename.c_str())){
							setLog(a.mFilename.c_str());
							setLog("が存在しません。\n");
							continue;
						}
						uint64 filesize;
						if(false==GetFileSize(a.mFilename,&filesize)){
							dMB("んなあほな");
							continue;
						}
						if(0==filesize){
							setLog(a.mFilename.c_str());
							setLog("はファイルサイズが0です。\n");
						}

						allsize += filesize;

						dl.c_filesize = filesize;
						//if(a.mKey.size() > sizeof(dl.key)){
						if(false==dl.setKey(a.mKey.get(),a.mKey.size() ))
						{
							//setLog(filename);
							setLog(a.mFilename.c_str());
							setLog("keyのサイズが大きすぎます。\n");
							continue;
						}
						//dl.key = a.mKey;
						//dl.length = data_length_count;
						//dl.b_length = FStream.tell();
					
						al.push_back(dl);

					}else{//memory buffer
						dl.c_filesize = a.mBuffer.size();
						//dl.b_length = FStream.tell();

						if(false==dl.setKey(a.mKey.get(),a.mKey.size())){
							//memcpy(dl.a_key.get(),a.mKey.get(),sizeof(dl.a_key));
							std::string str;
							to_string_or_data_hex(str,a.mKey.get(),
								(a.mKey.size() > 1024) ? 1024 : a.mKey.size());
							str += "...";
							setLog("memory : ");
							setLog(str.c_str());
							setLog(" / keyのサイズが大きすぎます。\n");
							continue;
						}



						//
						al.push_back(dl);

					}//end of file or memory 

				}//end of registed element iterate



			}

			FStream.seek(0,seek_begin);
			header_type h;
			//ヘッダ準備
			{
				
				{
					uint32 sig = getSignature();
					h.a_signature = sig;
				}
				uint64 temp = al.size();
				h.b_listnum = temp;//header write
				//uint8 sha512size[SHA512_BUFFER_BIN_SIZE];
				//h.c_datalists_sha(sha512size,sizeof(sha512size));//header temp write
				
				archive_list::iterator it = al.begin();
				uint64 dl_size = 0; 
				for(;it != al.end();it++)
				{
					dl_size += (*it).size();
				}
				h.d_datalists_size = dl_size;
				h.f_version = getVersion();

				//あえて書き込み
				//h.serialize(&FStream);
				h.seek_next(&FStream);

			}
			//データリスト分シーク
			{
				archive_list::iterator it = al.begin();
				for(;it != al.end();it++)
				{
					//(*it).serialize(&FStream);
					(*it).seek_next(&FStream);
				}
			}
			//データ書き込み
			{
				container_type::iterator it = mC.begin();
				archive_list::iterator alit = al.begin();
				CSHA512 sha;
				for(;it != mC.end();it++,alit++)
				{
					const dKingyoArchiveReserveElement &a = (*it);
					data_list &x = (*alit);

					x.b_length = FStream.tell();
					if(a.mIsFile){
						{
							fs_type rs;
							if(false==rs.reset(a.mFilename.c_str(),read_mode | binary_mode) || 
								false==rs.open())
							{
								setLog(a.mFilename.c_str());
								setLog("がリードモードでオープンできません。\n");
								continue;
							}
							
							
							//CSHA512 sha;
							write_withSHA512_functor func(&FStream,&sha);
							div_read_each_functor<fs_type,write_withSHA512_functor> div_read_each;
							div_read_each(&rs,func);
							sha.final();
							x.d_sha512.set_output(&sha);
							//sha.getSignature(dl.d_sha512,sizeof(dl.d_sha512),binary_signature);
							
						}

					}else{//memory
						{
							sha.load(a.mBuffer.get(),a.mBuffer.size());
							sha.final();
							x.d_sha512.set_output(&sha);
							//sha.getSignature(dl.d_sha512,sizeof(dl.d_sha512),binary_signature);
						}
						FStream.write(a.mBuffer.get(),a.mBuffer.size());
					}
					sha.init();
				}//eof


			}
			//ヘッダ書き込み
			{
				//巻き戻し
				FStream.seek(0,seek_begin);
				header_cipher_encoder *ttp = new header_cipher_encoder((PasswordOption &)x,x.workbuffer_size);
				//arcfourで暗号化
				IConvertSerializeStream::convert_ptr cp;
				cp.reset(ttp);
				archive_list::iterator it = al.begin();
				
				CSHA512 sh;
				for(;it != al.end();it++){
					data_list &a = (*it);
					a.load_sig(&sh);
				}
				//signature_buffer_bin<CSHA512> sbb;
				//sbb.set_output(&sh);
				h.c_datalists_sha.set_output(&sh);
				h.serialize(&FStream);//header write
				
				//data list write
				for(it = al.begin();it != al.end();it++)
				{
					data_list &a = (*it);
					a.crypt_serialize(cp,&FStream);
				}
			}
			//FStream.clear();

			return true;

		}
		
	};

	class CPackArchiveExtract : public IdKingyoArchiveExtract , public CPackArchiveDefine
	{
	public:
		typedef IdKingyoArchiveExtract base_type;
		BOOST_STATIC_CONSTANT(uint32,signature = eda_Pack);
		uint32 getSignature()const{return signature;}
		typedef CPackArchiveDefine define_type;
		typedef define_type::fs_type fs_type;
		typedef define_type::data_list data_list;
		//typedef define_type::data_list_less data_list_less;
	
		typedef define_type::write_withSHA512_functor write_withSHA512_functor;
		typedef define_type::read_withSHA512_functor read_withSHA512_functor;
		typedef define_type::header_cipher_decoder header_cipher_decoder;
	protected:
		fs_type mFS;
		std::ostream *mPS;
//#define CPackArchiveExtract_SET 1
#if CPackArchiveExtract_SET
		typedef set_ex_adapter<std::set<data_list> > container_type; 
#else
		typedef map_ex_adapter<std::map<key_type,data_list> > container_type;
#endif
		///データリスト
		container_type mDL;
		typedef container_type::iterator iterator;
		typedef container_type::const_iterator const_iterator;
		void setLog(parm_string str){
			if(mPS)
				*mPS << str.c_str();
		}
		void signature_error_log(const sha512buffer_type &origin,const sha512buffer_type &c)
		{
			setLog("シグネチャが合いませんでした。\n");
			setLog("  origin :");
			setLog(toHexString(origin.get(),origin.size()));
			setLog("\n");
			setLog("  current:");
			setLog(toHexString(c.get(),c.size()));
			setLog("\n");
		}
		iterator find_it(const key_type &key){
#if CPackArchiveExtract_SET
			data_list d;
			d.a_key = key;
			return mDL.find(d);
#else
			return mDL.find_by_key(key);
#endif
		}
		IConvertSerializeStream::convert_ptr mCS;
	public:	
		CPackArchiveExtract(){
			mPS = NULL;
		}
		virtual ~CPackArchiveExtract(){
			clear();
		}
		void setOutputStream(std::ostream *p){
			mPS = p;
		}
		///@return 対象のアーカイブファイルらしかったらtrue
		virtual bool isArchive(parm_string archive_filename){
			fs_type fs;
			bool f;
			f = fs.reset(archive_filename,read_mode | binary_mode);
			if(false==f) return false;
			f = fs.open();
			if(false==f) return false;
			uint32 t;
			fs << t;
			if(getSignature() != t) return false;
			return true;
		}

		bool v1_analysis(const dKingyoArchiveExtractOption &x,header_type &h){
			
			CSHA512 sha;
			data_list t;
			typedef std::vector<data_list> dlvec;
			dlvec tv;
			IConvertSerializeStream::convert_ptr cp;
			
			cp.reset(new header_cipher_decoder((PasswordOption &)x,x.workbuffer_size));
		
			for(uint64 i=0;i<h.b_listnum;i++)
			{
				t.crypt_serialize(cp,&mFS);
				t.load_sig(&sha);
				tv.push_back(t);
			}
			sha512buffer_type buff;
			buff.set_output(&sha);
			if(h.c_datalists_sha != buff){
				setLog("data listのシグネチャが合わない。\n");
				signature_error_log(h.c_datalists_sha,buff);
				return false;
			}
			{for(dlvec::iterator it = tv.begin();it != tv.end();it++)
			{
#if CPackArchiveExtract_SET
				if(false==mDL.rb_insert((*it))){
#else
				key_type key;
				shared_buffer &tk = (*it).a_key;
				key.copy(tk.get(),tk.size());
				tk.clear();
				if(false==mDL.SetData(key,(*it))){
#endif
					setLog("同じkeyが発見されました。このアーカイブは仕様に沿わないのでエラーとします。\n");
					return false;
				}
			}}
#ifdef _DEBUG
			{for(const_iterator it = mDL.begin();it != mDL.end();it++)
			{
				std::string str;
				
#	ifdef CPackArchiveExtract_SET
				to_string_or_data_hex(str,(*it).a_key.get(),(*it).a_key.size());

#	else
				to_string_or_data_hex(str,(*it).first.get(),(*it).first.size());
#	endif
				setLog(str);
				setLog("\n");
			}}
#endif
			return true;
		}

		virtual bool open(const dKingyoArchiveExtractOption &x)
		{
			if(false==FileExist(x.mFilename)){
				setLog(x.mFilename);
				setLog("が存在しません。");
				return false;
			}
			if(false==mFS.reset(x.mFilename,read_mode | binary_mode)){
				setLog(x.mFilename);
				setLog("filestream reset error");
				return false;
			}
			if(false==mFS.open()){
				setLog(x.mFilename);
				setLog("filestream open error");
				return false;
			}
			header_type h;
			h.serialize(&mFS);
			bool r;
			switch(h.f_version){
			case 0x00000010:
				r = v1_analysis(x,h);
				break;
			default:
				setLog("未対応のVersion");
				to_hex_string_functor<uint32> hx;
				setLog(hx(h.f_version));
				setLog("\n");
				r = false;
				break;
			}

			return r;
		}
		virtual bool find(const key_type &key){
			iterator it = find_it(key);
			return it != mDL.end();
		}
		struct shared_buffer_writer{
			shared_buffer *mP;
			size_t offset;
			shared_buffer_writer(shared_buffer *p) : mP(p){}
			void write(const void *buff,size_t size){
				//if(offset + size > mP->size()){
					//mP->

				memcpy(mP->get() + offset,buff,size);
				offset += size;
			}
		};
		//typedef read_with_hash_calculate_functor<shared_,CSHA512> read_with_sha512_functor;
		virtual bool extractToMemory(shared_buffer &dest,const key_type &key){
			iterator it = find_it(key);
			if(it == mDL.end()) return false;
			
#if CPackArchiveExtract_SET
			const data_list &dl = (*it);
#else
			const data_list &dl = (*it).second;
#endif
			if(false==mFS.seek(dl.b_length,seek_begin)){
				setLog("seek 失敗.\n");
				return false;
			}
			if(false==dest.reset(dl.c_filesize)){
				setLog("extractToMemory() dest / out of memory.\n");
				return false;
			}
			/*if(false==mFS.read(dest.get(),dest.size())){
				setLog("extractToMemory() read failed.\n");
				return false;
			}*/
			CSHA512 sha;
			shared_buffer_writer writer(&dest);
			typedef write_with_hash_calculate_functor<shared_buffer_writer,CSHA512> wht;
			wht rst(&writer,&sha);
			div_read_limit_functor<fs_type,wht> div_read;
			div_read(&mFS,rst,dl.c_filesize);
			sha512buffer_type buff;
			buff.set_output(&sha);
			

			if(buff != dl.d_sha512){
				setLog("extractToMemory() ");
				signature_error_log(dl.d_sha512,buff);
				return false;
			}
			
			return true;
		}
		virtual bool extractToFile(parm_string filename,const key_type &key){
			iterator it = find_it(key);
			if(it == mDL.end()) return false;
			
			
#if CPackArchiveExtract_SET
			const data_list &dl = (*it);
#else
			const data_list &dl = (*it).second;
#endif
			if(false==mFS.seek(dl.b_length,seek_begin)){
				setLog("seek 失敗.\n");
				return false;
			}

			//ファイルに書き込み処理
			fs_type rs;
			if(false==rs.reset(filename,write_mode | binary_mode) || 
				false==rs.open())
			{
				setLog(filename.c_str());
				setLog("がwrite modeでオープンできません。\n");
				return false;
			}
			
			
			CSHA512 sha;
			write_withSHA512_functor func(&rs,&sha);
			div_read_limit_functor<fs_type,write_withSHA512_functor> div_read;
			div_read(&mFS,func,dl.c_filesize);
			sha.final();
			sha512buffer_type buff;
			buff.set_output(&sha);
			//sha.getSignature(dl.d_sha512,sizeof(dl.d_sha512),binary_signature);
			
			if(buff != dl.d_sha512){
				setLog(filename.c_str());
				signature_error_log(dl.d_sha512,buff);
				return false;
			}

			return true;
		}
		virtual bool clear(){
			mFS.close();
			mDL.clear();
			mPS = NULL;
			return true;
		}
		//ファイル全体の出力に変換プログラムを挿入する
		//void setConvertStream(IConvertSerializeStream::convert_ptr p){mCS = p;}
	};
}//end of dkutil namespace



#endif