
#ifndef DKUTIL_CRYPTOGRAPH_SNOW2_HPP
#define DKUTIL_CRYPTOGRAPH_SNOW2_HPP


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


class CSNOW2ConvertStream : public IStreamConvert{
public:
	typedef IStreamConvert base_type;
	typedef IStreamConvert::first_header first_header;
	typedef CSNOW2ConvertStream self_type;
	BOOST_STATIC_CONSTANT(uint32,signature = edkICS_cryptograph_snow2);
	typedef null_convert_second_header second_header;
private:
	PasswordOption mPO;
	DKC_SNOW2 *mpObj;
	//int mState;
	size_t mIBSize;
	first_header mHeader;
	second_header mSHeader;
	void header_update(size_t size)
	{
		mHeader.mOriginSize += size;
		mHeader.mConvertedSize += size;
	}
public:
	DKC_SNOW2 *getObj(){
		return mpObj;
	}
	bool check_keysize(const PasswordOption &x){
		size_t size;
		if(x.isPasswordBinary){
			size = x.mPasswordBin.size();
		}else{
			size = x.mPassword.size();
		}
		if(size == 16 || size == 32){
			return true;
		}
		return false;
	}
	DKC_SNOW2 *allocSNOW2(const PasswordOption &x){
		size_t size_str= x.mPassword.size();
		const char *pass_str = x.mPassword.c_str();
		size_t size_bin = x.mPasswordBin.size();
		const uint8 *pass_bin = x.mPasswordBin.get();
		
		
		if(check_keysize(x)==false){
			if(x.isPasswordBinary){
				return dkcAllocSNOW2NoLimitKeyLength(pass_bin,size_bin);
			}else{
				return dkcAllocSNOW2NoLimitKeyLength((const uint8*)pass_str,size_str);
			}
		}
		uint32 iv[4];
		iv[0] = 0x6a09e667;
		iv[1] = 0xbb67ae85;
		iv[2] = 0x3c6ef372;
		iv[3] = 0xa54ff53a;
		if(x.mIV.size() >= sizeof(uint32) * 1){
			iv[0] = *((uint32 *)x.mIV.get());
		}
		if(x.mIV.size() >= sizeof(uint32) * 2){
			iv[1] = *((uint32 *)x.mIV.get());
		}
		if(x.mIV.size() >= sizeof(uint32) * 3){
			iv[2] = *((uint32 *)x.mIV.get());
		}
		if(x.mIV.size() >= sizeof(uint32) * 4){
			iv[3] = *((uint32 *)x.mIV.get());
		}
		if(x.isPasswordBinary){
			return dkcAllocSNOW2Const(pass_bin,size_bin,iv[3],iv[2],iv[1],iv[0]);
		}

		return dkcAllocSNOW2Const((const uint8*)pass_str,size_str,iv[3],iv[2],iv[1],iv[0]);
	}

	CSNOW2ConvertStream(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;
		mHeader.mSignature = signature_;
	}
	virtual ~CSNOW2ConvertStream(){
		end(NULL);
	}

	virtual bool begin()
	{
		mpObj = allocSNOW2(mPO);
		if(!mpObj){
			
			return false;
		}
	
		//memset(&mHeader,0,sizeof(mHeader));
		mHeader.mOriginSize = mHeader.mConvertedSize = 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);
	}
	virtual bool convert(uint8 *buff,size_t size,convert_result *r)
	{
		if(!mpObj)//
		{
			DKUTIL_THROW_OR_NOTICE(convert_error("CSNOW2ConvertStream not call begin()"));
			return false; 
		}
		dkcSNOW2EncryptNoDestDOE(mpObj,buff,size);
		r->converted_size = size;
		header_update(size);
		return true;
	
	}
	virtual int type()const{
		return signature;
	}
	virtual bool end(first_header *ph)
	{
		if(ph)
		{
			*ph = mHeader;
		}
		return DKUTIL_SUCCEEDED_BOOL(dkcFreeSNOW2(&mpObj));
	}
	virtual size_t getConvertOutputSize(size_t s)const{
		return s ;
	}
	virtual bool getSecondHeader(void *){
		return false;
	}
};


class CSNOW2StreamCipherConvert : public IStreamCipherConvert{
	CSNOW2ConvertStream ms;
public:
	CSNOW2StreamCipherConvert(PasswordOption &x) : ms(x)
	{
		ms.begin();
	}
	virtual ~CSNOW2StreamCipherConvert(){
		ms.end(NULL);
	}
	void convert(uint8 *p,size_t size){
		IStreamConvert::convert_result r;
		ms.convert(p,size,&r);
	}
	int type()const{
		return ms.type();
	}
};

/**
SNOW2暗号化を行うクラス
type:ストリーム暗号
*/
class CSNOW2 : public IArrayProcess{

	size_t mEncodedSize;

	struct CSNOW2Header{
		///シグネチャ
		ULONG sig;
		///オリジナル 兼 暗号化済みのサイズ
		size_t size;
	};
	DKC_SNOW2 *mP;
	BOOST_STATIC_CONSTANT(int,header_size = sizeof(CSNOW2Header));
	
	bool check_sig(CSNOW2Header *p){
		return (p->sig == getSignature());
	}
	void check_obj__()const{
		if(NULL == mP){
			DKUTIL_THROW_OR_NOTICE(std::runtime_error("CSNOW2 uninitialize error"));
			dkcmFORCE_NOT_ASSERT(NULL != mP);
		}
	
	}
public:
	///@see reset()
	CSNOW2(const BYTE *key = NULL,size_t keysize = 0,ULONG signature=edk_SNOW2_SIGNATURE){
		mP = NULL;
		mEncodedSize = 0;
		reset(key,keysize);
		
	}
	void clear(){
		dkcFreeSNOW2(&mP);
	}
	~CSNOW2(){
		clear();
	}
	///@param signature[in] BlockSortで圧縮されていることを証明するID（デフォルトでは dkcdBlockSort_SIGNATURE )
	bool reset(const BYTE *key ,size_t keysize,ULONG signature=edk_SNOW2_SIGNATURE){
		setSignature(signature);
		setResult(edk_FAILED);
		clear();
		
		DKC_SNOW2 *p = dkcAllocSNOW2NoLimitKeyLength(key,keysize);
		if(NULL==p){
			return false;
		}
		mP = p;
		mEncodedSize = 0;
		return true;
	}

	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;
		//バッファの先頭にヘッダファイルがある。
		CSNOW2Header *hp = (CSNOW2Header*)src;
		check_obj__();

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



		r = dkcSNOW2DecryptDOE(mP,dest,destsize,sp,ssize);
		//結果を入れる。
		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;
		int r;
		check_obj__();

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


		r = dkcSNOW2EncryptDOE(mP,bp,bsize,src,size);
		//bsize分暗号化
		if(DKUTIL_FAILED(r)){
			return r;
		}

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


		setResult(r);
		
		return r;
	}	
	
	
	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("CSNOW2::getDecodeOutputSize() size argument exception"));
			return 0;
		}
		CSNOW2Header *p = (CSNOW2Header *)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