/**
@filename basic.hpp
@brief 簡単なアーカイブ処理
@note
簡単なアーカイブ処理をする。
<PRE>
ファイル先頭にシグネチャあり
暗号化なし、圧縮なし
SHA512によるチェックサム付き
1要素毎に約1KBのヘッダ
ファイルは数ギガのまで対応可能（理論上）
ヘッダを書き換えれば（SHA512値とオフセットを変える）簡単にアーカイブを改造されてしまう。
</PRE>
物好きならこのソースを元に自分用のを作っちゃうでしょう。＾＾；

@section dkutil_archive_basic_hpp_history 更新履歴
<PRE>
2005/09/26:内部で使うファイルストリームクラスを変えた。
2005/09/24:完成 テスト終了 多分大丈夫。目立ったバグがない限りいじらない。
</PRE>
*/
#ifndef DKUTIL_ARCHIVE_BASIC_HPP
#define DKUTIL_ARCHIVE_BASIC_HPP

#include <dkutil/archive/interface.hpp>
#include <dkutil/filesystem/littleendian_filestream.hpp>
#include <dkutil/dktl/adapters.hpp>
#include <dkutil/parser/string_util.hpp>
#include <dkutil/signature/sha512.hpp>
#include <dkutil/filesystem/utility.hpp>

namespace dkutil{

	/**
	仕様：little endian base
	*/
	class CBasicArchive /*: public IArchieGenerate*/{
	public:
		BOOST_STATIC_CONSTANT(uint32,signature = eda_Basic);
		struct CBasicArchiveHeader{
			uint32 a_signature;
			uint64 b_listnum;
			//datalistのSHA512
			//uint8 b_datalistsha512[SHA512_BIN_BUFFER_SIZE];
			
		};
		static uint32 getSignature(){
			return signature;
		}
		typedef size_t size_type;
		typedef big_filestream fs_type;
		///データの並び構造
		struct CBasicArchiveDataList{
			///キーデータ名
			uint8 a_key[1024];
			///ファイルの先頭から何byteか？
			uint64 b_length;
			///ファイルのサイズ
			uint64 c_filesize;
			///このデータのSHA512値
			uint8 d_sha512[SHA512_BIN_BUFFER_SIZE];
			bool setKey(uint8 *key,size_t keysize){
				if(keysize > sizeof(a_key)) return false;
				memcpy(a_key,key,keysize);
				return true;
			}
			bool setSHA(uint8 *sha512,size_t sha512size)
			{	
				if(sha512size != sizeof(d_sha512)) return false;
				memcpy(d_sha512,sha512,sha512size);
				return true;
			}
			void save(fs_type *p)
			{	
				p->responsible_write(a_key,sizeof(a_key));
				*p << b_length;
				*p << c_filesize;
				p->responsible_write(d_sha512,sizeof(d_sha512));
			}
			void load(fs_type *p){
			
				p->responsible_read(a_key,sizeof(a_key));
				//if(t != sizeof(a_key)) throw std::runtime_error("CBasicArchiveDataList load error");
				*p << b_length;
				*p << c_filesize;
				p->responsible_read(d_sha512,sizeof(d_sha512));
				//if(t != sizeof(d_sha512)) throw std::runtime_error("CBasicArchiveDataList load error");
			}
		};
	protected:
		//CLittleEndianFileStream FStream;
		typedef set_ex_adapter<std::set<dKingyoArchiveReserveElement> > container_type;
		typedef std::list<CBasicArchiveDataList> archive_list;

		container_type mC;
		std::string mErrorLog;
		std::string mProcessLog;
	public:

		
		CBasicArchive(){
			//reset(filename);
			
		}
		virtual ~CBasicArchive(){

		}
		void clear(){
			mErrorLog.clear();
			mProcessLog.clear();
			mC.clear();
		}
		/*
		bool reset(parm_string filename){
			
			mC.clear();
			return FStream.reset(filename);
		}*/
		virtual bool addToFile(parm_string filename){
			dKingyoArchiveReserveElement a;
			a.regist_filename(filename);
			return mC.rb_insert(a);
		}
		virtual bool addToFile(const shared_buffer &key,parm_string filename)
		{
			dKingyoArchiveReserveElement a;
			a.regist_filename(filename,key);
			return mC.rb_insert(a);
		}
		virtual bool addToMemory(const shared_buffer &key,const uint8 *pbuff,size_type size)
		{
			dKingyoArchiveReserveElement a;
			shared_buffer f;
			f.copy(pbuff,size);
			return addToMemory(key,f);
		}
		virtual bool addToMemory(const shared_buffer &key,const shared_buffer &x){
			dKingyoArchiveReserveElement a;
			a.regist_buffer(x,key);
			return mC.rb_insert(a);
		}
		///SHA512を計算させると共にwrite()する。
		class write_withSHA512_functor{
		private:
			fs_type *mps;
			CSHA512 *mSHA;
		public:
			write_withSHA512_functor(fs_type *ps,CSHA512 *p){
				mps = ps;
				mSHA = p;
				p->init();
			}
			void operator()(const BYTE *buff,size_type size){
				
				mSHA->load(buff,size);
				mps->write(buff,size);
			}

		};
		void count_write(fs_type *p,size_t size){

			uint8 b = 0;
			for(size_t i=0;i<size;i++){
				p->write(&b,sizeof(b));
			}
		}

		/**
		固める処理
		*/
		virtual bool generate(parm_string filename)
		{
		
			uint64 al_offset;
			uint64 allsize = 0;
			uint64 data_length_count = 0;
			fs_type FStream;//scope outを狙っている
			if(false==FStream.reset(filename,write_mode | binary_mode))
			{
				return false;
			}
			if(false==FStream.open()){
				return false;
			}
			
			{
				uint32 sig = getSignature();
				FStream << sig;
			}
			//data_length_count += sizeof(signature);
			al_offset = FStream.tell();
			count_write(&FStream,sizeof(uint64));
			{
				size_t cn_ = sizeof(CBasicArchiveDataList) * mC.size();
				count_write(&FStream,cn_);
				/*mProcessLog += "num = ";
				mProcessLog += to_string(cn_);
				mProcessLog += "\n";*/

			}
			//FStream.seek(sizeof(CBasicArchiveDataList) * mC.size(),seek_current);
			//data_length_count += sizeof(CBasicArchiveDataList) * mC.size();
			if(FStream.error()){
				return false;
			}
			archive_list al;

			{
				container_type::iterator it = mC.begin();
				for(;it != mC.end();it++)
				{
					const dKingyoArchiveReserveElement &a = (*it);
					CBasicArchiveDataList dl;
					if(a.mIsFile)
					{
						if(false==FileExist(a.mFilename.c_str())){
							mErrorLog += a.mFilename.c_str();
							mErrorLog += "が存在しません。\n";
							continue;
						}
						uint64 filesize;
						if(false==GetFileSize(a.mFilename,&filesize)){
							dMB("んなあほな");
							continue;
						}
						if(0==filesize){
							mErrorLog += a.mFilename.c_str();
							mErrorLog += "はファイルサイズが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() ))
						{
							mErrorLog += filename;
							mErrorLog += a.mFilename.c_str();
							mErrorLog += "keyのサイズが大きすぎます。\n";
							continue;
						}
						//dl.key = a.mKey;
						//dl.length = data_length_count;
						dl.b_length = FStream.tell();
						{
							fs_type rs;
							if(false==rs.reset(a.mFilename.c_str(),read_mode | binary_mode) || 
								false==rs.open())
							{
								mErrorLog += a.mFilename.c_str();
								mErrorLog += "がリードモードでオープンできません。\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();
							sha.getSignature(dl.d_sha512,sizeof(dl.d_sha512),binary_signature);
						
						}
						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,a.mKey.get(),sizeof(dl.a_key));
							std::string str = toHexString(dl.a_key,sizeof(dl.a_key));
							mErrorLog += "memory : ";
							mErrorLog += str;
							mErrorLog += " / keyのサイズが大きすぎます。\n";
							continue;
						}

						{

							CSHA512 sha;
							sha.load(a.mBuffer.get(),a.mBuffer.size());
							sha.final();
							sha.getSignature(dl.d_sha512,sizeof(dl.d_sha512),binary_signature);
						}

						FStream.write(a.mBuffer.get(),a.mBuffer.size());

						al.push_back(dl);

					}//end of file or memory 

				}//end of registed element iterate
			}

			{
				//巻き戻し
				FStream.seek(al_offset,seek_begin);

				archive_list::iterator it = al.begin();
				uint64 temp = al.size();
				FStream << temp;
				for(;it != al.end();it++)
				{
					CBasicArchiveDataList &a = (*it);
					/*FStream.write(a.a_key,sizeof(a.a_key));
					FStream << a.b_length;
					FStream << a.c_filesize;
					FStream.write(a.d_sha512);
					*/
					a.save(&FStream);
				}
			}
			//FStream.clear();

			return true;

		}

		/*
		struct Extractor{
		
			archive_list mC;
			CBasicArchiveHeader header;
			
			bool load_from_key(shared_buffer &key,shared_buffer &target,size_t memory_limit){
				archive_list::iterator it = mC.begin();
				archive_list::value_type v;
				if(key.size() > sizeof(v.a_key)){
					return false;
				}
				for(;it != mC.end();it++)
				{
					if(0==memcmp((*it).a_key,key.get(),key.size())){
						if((*it).c_filesize > memory_limit){//でかい。
							return false;
						}
						return true;
					}
				}
			}
			void load(archive_list::value_type &x,size_t limit_size)
			{
				{
					archive_list::iterator it;
					for(it = al.begin();it != al.end();it++)
					{
						CBasicArchiveDataList &a = (*it);
						CSHA512 sha;
						{
							CLittleEndianMemoryStream rs;
							
							
							
							FStream.seek(a.b_length,seek_begin);
							write_withSHA512_functor func(&rs,&sha);
							FStream.getBase().div_read_limit(func,a.c_filesize);
							sha.final();
						}//eos

						{
							shared_buffer buff(sizeof(a.d_sha512));
							sha.getSignature(buff.get(),buff.size(),binary_signature);				
							if(0 != memcmp(a.d_sha512,buff.get(),buff.size())){
								mErrorLog += fn;
								mErrorLog += "のチェックサムが不正な値です。";
								if(DeleteFile(fn.c_str())){
									mErrorLog += "よって削除します。\n";
								}else{
									mErrorLog += "上手く削除できませんでした。\n";
								}
							}
						}//eos
					}//eof
				}//eos
			}
			bool load_from_sha512(shared_buffer &sha,shared_buffer &target,size_t memory_limit){
				archive_list::iterator it = mC.begin();
				archive_list::value_type v;
				if(sha.size() > sizeof(v.d_sha512)){
					return false;
				}
				for(;it != mC.end();it++)
				{
					if(0==memcmp((*it).d_sha512,sha.get(),sha.size())){
						if((*it).c_filesize > memory_limit){//でかい。
							return false;
						}
						return true;
					}
				}
			}
			void clear(){
				mC.clear();
			}
			void open(parm_string filename)
			{
				clear();
				CLittleEndianFileStream FStream;//scope out 
				if(false==FStream.reset(filename,read_mode | binary_mode)){
					
					return false;
				}

				{
					uint32 sig = 0;
					FStream << sig;
					if(sig != getSignature()){
						return false;
					}
					header.a_signature = sig;
				}

				{

					uint64 alnum;
					FStream << alnum;
					header.b_listnum = alnum;
					
					for(uint64 i=0;i<alnum;i++)
					{
						CBasicArchiveDataList a;
						a.load(&FStream);
						mC.push_back(a);
					}
				}
				return true;
			}
		};*/

#if 1

		/**
		解凍処理
		*/
		bool extract(parm_string filename,parm_string outputdir){
			fs_type FStream;//scope out したほうが良い
			if(false==FStream.reset(filename,read_mode | binary_mode) ||
				false==FStream.open()){
				mErrorLog += filename.c_str();
				mErrorLog += "をread_modeでオープンできませんでした。\n";
				return false;
			}

			if(FileExist(outputdir)){
				mErrorLog += outputdir.c_str();
				mErrorLog += "はすでにファイルとして存在しています。\n";
				return false;
			}
			if(!FolderExist(outputdir))
			{
				if(DKUTIL_FAILED(dkcCreateDirectory(outputdir.c_str())))
				{
					mErrorLog += outputdir.c_str();
					mErrorLog += "をディレクトリとして出力できませんでした。\n";
					return false;
				}
			}
				

			FStream.seek(0,seek_begin);
			{
				uint32 sig = 0;
				FStream << sig;
				if(sig != getSignature()){
					mErrorLog += "シグネチャが違います。\n";
					return false;
				}
			}
			archive_list al;
			{

				uint64 alnum;
				FStream << alnum;
				
				for(uint64 i=0;i<alnum;i++)
				{
					CBasicArchiveDataList a;
					a.load(&FStream);
					al.push_back(a);
				}
			}

			{
				archive_list::iterator it;
				for(it = al.begin();it != al.end();it++)
				{
					CBasicArchiveDataList &a = (*it);
					CSHA512 sha;

					std::string fn;
					fn += outputdir.c_str();
					
					std::string tfn = toHexString(a.d_sha512,sizeof(a.d_sha512),false);
					DirectoryPlusFileName(fn,tfn);
					{
						fs_type rs;
						
						if(false==rs.reset(fn.c_str(),write_mode | binary_mode) || 
							false==rs.open())
						{
							mErrorLog += fn;
							mErrorLog += "がライトモードでオープンできません。\n";
							continue;
						}
						
						FStream.seek(a.b_length,seek_begin);
						write_withSHA512_functor func(&rs,&sha);
						div_read_limit_functor<fs_type,write_withSHA512_functor> div_read_limit;

						div_read_limit(&FStream,func,a.c_filesize);
						sha.final();
					}//eos

					{
						shared_buffer buff(sizeof(a.d_sha512));
						sha.getSignature(buff.get(),buff.size(),binary_signature);				
						if(0 != memcmp(a.d_sha512,buff.get(),buff.size())){
							mErrorLog += fn;
							mErrorLog += "のチェックサムが不正な値です。";
							if(DeleteFile(fn.c_str())){
								mErrorLog += "よって削除します。\n";
							}else{
								mErrorLog += "上手く削除できませんでした。\n";
							}
						}
					}//eos
				}//eof
			}//eos



			return true;
		}

#endif
		const std::string getErrorLog()const{
			return mErrorLog;
		}
		const std::string getProcessLog()const{
			return mProcessLog;
		}
		
		
	};

}//end of dkutil namespace



#endif