
#ifndef DKUTIL_CRYPTOGRAPH_HC256_HPP
#define DKUTIL_CRYPTOGRAPH_HC256_HPP


#include <dkutil_c/dkc.h>
#include <dkutil/memory/interface.hpp>
#include <dkutil/cryptograph/interface.hpp>
namespace dkutil{


class CHC256ConvertStream : public IStreamConvert{
public:
	typedef IStreamConvert base_type;
	typedef IStreamConvert::first_header first_header;
	typedef CHC256ConvertStream self_type;
	BOOST_STATIC_CONSTANT(uint32,signature = edkICS_cryptograph_hc256);
	typedef null_convert_second_header second_header;

protected:
	PasswordOption mPO;
	DKC_HC256 *mpObj;
	int mState;
	size_t mIBSize;
	first_header mFH;
	void header_update(size_t size)
	{
		mFH.mOriginSize += size;
		mFH.mConvertedSize += size;
	}
	bool check_obj(){
		if(!mpObj)
		{
			DKUTIL_THROW_OR_NOTICE(convert_error("CArcfourConvertStream not call begin()"));
			return false; 
		}
		return true;
	}
public:
	typedef struct HC256Password : public PasswordOption{
		bool setIV(const shared_buffer &x){
			if(x.size() != 32) return false;
			mIV = x;
			return true;
		}
		bool setPassword(const shared_buffer &x){
			if(x.size() != 32) return false;
			setNoLimitPassword(x);
		}
		bool setPassword(const std::string &x){
			if(x.size() != 32) return false;
			mPassword = x;
			isPasswordBinary = false;
			return true;
		}
		bool setNoLimitPassword(const shared_buffer &x){
			mPasswordBin = x;
			isPasswordBinary = true;
			return true;
		}

	}password_type;
	CHC256ConvertStream(const PasswordOption &x,uint32 signature_ = self_type::signature,
		size_t inner_buffer_size = base_type::default_inner_buffer_size){
		mpObj = NULL;
		mPO = x;
		if(inner_buffer_size == 0){
			inner_buffer_size = 1024 * 64;
		}
		mIBSize = inner_buffer_size;
		mFH.mSignature = signature_;
	}
	virtual ~CHC256ConvertStream()
	{
		end(NULL);
	}
	virtual bool begin(){
		size_t size_str= mPO.mPassword.size();
		const char *pass_str = mPO.mPassword.c_str();
		size_t size_bin = mPO.mPasswordBin.size();
		const uint8 *pass_bin = mPO.mPasswordBin.get();
		const uint32 *iv = (uint32 *)mPO.mIV.get();
		size_t ivsize = mPO.mIV.size();
		end(NULL);
		if(mPO.isPasswordBinary){
			if(32==size_bin){
				mpObj = dkcAllocHC256Const(pass_bin,size_bin,iv,ivsize);
			}else{
				mpObj = dkcAllocHC256NoLimitKeyLength(pass_bin,size_bin);
			}
		}else{
			if(32==size_str){
				mpObj = dkcAllocHC256Const((const uint8 *)pass_str,size_str,iv,ivsize);
			}else{
				mpObj = dkcAllocHC256NoLimitKeyLength((const uint8 *)pass_str,size_str);
			}
		}
		if(!mpObj){
			mState = edk_FAILED;
			return false;
		}
		mState = edk_SUCCEEDED;
		mFH.mConvertedSize = mFH.mOriginSize = 0;
		return true;
	}

	virtual bool convert(uint8 *dest,size_t dsize,const uint8 *src,size_t ssize,convert_result *r)
	{
		/*if(dsize < ssize){		
			return false;
		}
		memcpy(dest,src,ssize);
		return convert(dest,ssize,r);*/
		DKUTIL_RETURN_FALSE(check_obj());
		if(DKUTIL_FAILED(dkcHC256EncryptDOE(mpObj,dest,dsize,src,ssize)))
		{
			return false;
		}
		r->converted_size = ssize;
		header_update(ssize);
		return true;
	}
	virtual bool convert(uint8 *buff,size_t size,convert_result *r)
	{
		DKUTIL_RETURN_FALSE(check_obj());
		if(DKUTIL_FAILED(dkcHC256EncryptNoDestDOE(mpObj,buff,size))){
			return false;
		}
		r->converted_size = size;
		header_update(size);
		return true;
	}

	/*virtual bool convert(IStream *out,IStream *in){
		scoped_buffer_byte sb(mIBSize);
		for(;false==in->eof();)
		{
			in->read(sb.get(),sb.size());
			mState = dkcHC256EncryptNoDestDOE(mpObj,sb.get(),sb.size());
			if(DKUTIL_FAILED(mState)){
				return false;
			}
			out->write(sb.get(),sb.size());
		}
		mState = edk_SUCCEEDED;
		return true;
	}
	virtual int state()const{
		return mState;
	}*/

	virtual size_t getConvertOutputSize(size_t size)const
	{
		return size;
	}

	///@return SecondHeaderが存在しない場合はfalse
	virtual bool getSecondHeader(void *){
		return false;
	}

	virtual bool end(first_header *pfh)
	{
		if(pfh){
			*pfh = mFH;
		}
		return DKUTIL_SUCCEEDED_BOOL(dkcFreeHC256(&mpObj));
	}
	virtual int type()const{
		//return edkICS_cryptograph_hc256;
		return signature;
	}
};


/**
ヘッダ作ってストリーム作って・・・みたいな。
*/
template<class STREAM_T>
class CHC256Factory{
public:
	BOOST_STATIC_CONSTANT(size_t,default_work_size = 1024 * 64);
	typedef STREAM_T stream_type;
	typedef boost::shared_ptr<stream_type> stream_ptr_type;
	struct CHC256Header{
		///シグネチャ
		ULONG sig;
		///オリジナル 兼 暗号化済みのサイズ
		size_t size;
	};
	stream_ptr_type create(stream_type::password_type pass,size_t def_wsize = default_work_size){
		stream_ptr_type p;
		p.reset(new stream_type(pass,def_wsize));
		return p;
	}
};
/**
HC256暗号化を行うクラス
type:ストリーム暗号
*/
class CHC256 : public IArrayProcess{

	size_t mEncodedSize;

	struct CHC256Header{
		///シグネチャ
		ULONG sig;
		///オリジナル 兼 暗号化済みのサイズ
		size_t size;
	};
	DKC_HC256 *mP;
	BOOST_STATIC_CONSTANT(int,header_size = sizeof(CHC256Header));
	
	bool check_sig(CHC256Header *p){
		return (p->sig == getSignature());
	}
	void check_obj__()const{
		if(NULL == mP){
			DKUTIL_THROW_OR_NOTICE(std::runtime_error("CHC256 uninitialize error"));
			dkcmFORCE_NOT_ASSERT(NULL != mP);
		}
	
	}
public:
	///@see reset()
	CHC256(const BYTE *key = NULL,size_t keysize = 0,ULONG signature=edk_HC256_SIGNATURE){
		mP = NULL;
		mEncodedSize = 0;
		reset(key,keysize);
		
	}
	void clear(){
		dkcFreeHC256(&mP);
	}
	~CHC256(){
		clear();
	}
	///@param signature[in] このクラスで処理されている事を証明するID
	bool reset(const BYTE *key ,size_t keysize,ULONG signature=edk_HC256_SIGNATURE){
		setSignature(signature);
		setResult(edk_FAILED);
		clear();
		
		DKC_HC256 *p = dkcAllocHC256NoLimitKeyLength(key,keysize);
		if(NULL==p){
			return false;
		}
		mP = p;
		mEncodedSize = 0;
		return true;
	}
	///キーのサイズが32byte限定。
	bool reset_keysize32(const BYTE *key,const uint32 *iv4,ULONG signature=edk_HC256_SIGNATURE){
		setSignature(signature);
		setResult(edk_FAILED);
		clear();
		
		DKC_HC256 *p = dkcAllocHC256Const(key,32,iv4,32);
		if(NULL==p){
			return false;
		}
		mP = p;
		mEncodedSize = 0;
		return true;
	}

	/*
	IStreamConvert *createEncoder(PasswordOption &x,
		size_t inner_buffer_size = IStreamConvert::default_inner_buffer_size)
		{
			return new CHC256EncodeStream(x,inner_buffer_size);
		}
	IStreamConvert *createDecoder(PasswordOption &x,
		size_t inner_buffer_size = IStreamConvert::default_inner_buffer_size)
		{
			return new CHC256DecodeStream(x,inner_buffer_size);
		}

	void destroyObject(
	*/
	int decode(const BYTE *src,size_t size,BYTE *dest,size_t destsize){
		const BYTE *sp = src + header_size;
		size_t ssize = size - header_size;
		int r = edk_FAILED;
		//バッファの先頭にヘッダファイルがある。
		CHC256Header *hp = (CHC256Header*)src;
		check_obj__();

		dkcmNOT_ASSERT(check_sig(hp)==FALSE);
		if(check_sig(hp)==FALSE){
			return r;
		}
		if(getDecodeOutputSize(hp,size) > destsize){//destが足りない
			return r;
		}



		dkcHC256DecryptDOE(mP,dest,destsize,sp,ssize);


		r = edk_SUCCEEDED;
		//結果を入れる。
		setResult(r);

		return r;

	}

	int encode(const BYTE *src,size_t size,BYTE *dest,size_t destsize){
		BYTE *bp = dest + header_size;
		size_t bsize = destsize - header_size;
		//DWORD outputsize;
		check_obj__();

		if(getEncodeOutputSize(size) > destsize){
			return edk_FAILED;
		}


		//bsize分暗号化
		if(DKUTIL_FAILED(dkcHC256EncryptDOE(mP,bp,bsize,src,size))){
			return edk_FAILED;
		}

		//update
		CHC256Header *hp = (CHC256Header *)dest;
		hp->sig = getSignature();
		hp->size = size;


		setResult(edk_SUCCEEDED);
		
		return edk_SUCCEEDED;
	}	
	
	
	virtual size_t getEncodedSize()const{
		return mEncodedSize;
	}
	
	///エンコード時の出力予定のサイズ (普通より大きめの値を返されます。) 
	virtual size_t getEncodeOutputSize(size_t size)const{
		return size + header_size;
	}
	/**
	@param pHeader[in] デコードする予定のデータ
	@param size[in] デコードする予定のサイズ
	@throw アクセス不可な所へアクセスしようとすると std::runtime_errorをぷっぱなす。
	*/
	///デコード時の出力予定のサイズ　
	virtual size_t getDecodeOutputSize(const void *pHeader,size_t size)const{
		if(size < header_size){
			DKUTIL_THROW_OR_NOTICE(std::runtime_error("CHC256::getDecodeOutputSize() size argument exception"));
			return 0;
		}
		CHC256Header *p = (CHC256Header *)pHeader;
		//size_t s = p->origin_size;
		size_t s = p->size;
		return s;
	}
	virtual size_t getDecodeValidOutputSize(const void *pHeader,size_t size)const{
		return getDecodeOutputSize(pHeader,size);
	}


};



}//end of dkutil namespace





#endif