
#ifndef DKUTIL_CRYPTOGRAPH_CAMELLIA_HPP
#define DKUTIL_CRYPTOGRAPH_CAMELLIA_HPP


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


namespace dkutil{

class CCamellia : public IArrayProcess{
	struct CCamelliaHeader{
		///シグネチャ
		ULONG sig;
		///オリジナルのサイズ
		size_t origin_size;
		///エンコード済みのサイズ
		size_t encoded_size;
	};
	DKC_CAMELLIA *mP;
	BOOST_STATIC_CONSTANT(int,header_size = sizeof(CCamelliaHeader));
	
	uint32 mEncodedSize;
	bool check_sig(CCamelliaHeader *p){
		return (p->sig == getSignature());
	}
	void check_obj__()const{
		if(NULL == mP){
			DKUTIL_THROW_OR_NOTICE(std::runtime_error("CCamellia uninitialize error"));
			dkcmFORCE_NOT_ASSERT(NULL != mP);
		}
	
	}
public:
	enum{
		ecb_mode = edkcCAMELLIA_CONVERT_ECB,
		cbc_mode = edkcCAMELLIA_CONVERT_CBC,
		cfb_mode = edkcCAMELLIA_CONVERT_CFB,
		ofb_mode = edkcCAMELLIA_CONVERT_OFB,
	};
	///@see reset()edk_BLOWFISH_SIGNATURE
	CCamellia(const BYTE *key = NULL,size_t keysize = 0,bool isDecryption=false,uint32 flags=cbc_mode,
		ULONG signature=edk_CAMELLIA_SIGNATURE)
	{
		mP = NULL;
		mEncodedSize = 0;
		reset(key,keysize,isDecryption,flags);
		
	}
	void clear(){
		dkcFreeCamellia(&mP);
	}
	
	virtual ~CCamellia(){
		clear();
	}
	///@param signature[in] BlockSortで圧縮されていることを証明するID（デフォルトでは dkcdBlockSort_SIGNATURE )
	bool reset(const BYTE *key = NULL,size_t keysize = 0,bool isDecryption=false,uint32 flags=cbc_mode,
		ULONG signature=edk_CAMELLIA_SIGNATURE)
	{
		setSignature(signature);
		setResult(edk_FAILED);
		
		if(NULL==key){
			return false;
		}
		clear();

	
		//オブジェクト取得
		DKC_CAMELLIA *p = dkcAllocCamelliaNoLimitKeyLength(key,keysize,isDecryption,flags);

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

		dkcmNOT_ASSERT(check_sig(hp)==FALSE);
		if(check_sig(hp)==FALSE){
			return r;
		}
		if(getDecodeOutputSize(hp,size) > destsize){//destが足りない
			return r;
		}
		if(hp->encoded_size > size){//input bufferが足りない
			return r;
		}
		if(mP->isDecryption==FALSE){
			return r;
		}
		dkcmNOT_ASSERT(hp->encoded_size % dkcdCAMELLIA_BLOCK_SIZE != 0);
		if(hp->encoded_size % dkcdCAMELLIA_BLOCK_SIZE != 0){//あまりがある。
			return edk_ArgumentException;
		}

		
		memcpy(dest,sp,hp->encoded_size);

		size_t tc = hp->encoded_size / dkcdCAMELLIA_BLOCK_SIZE;
#ifndef CAMELLIA_TEST
		dkcCamelliaDecryptBlockNoDest(	mP,dest,tc);
#else
		CaCBCD(mP,(uint64 *)dest,tc);
#endif
		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;
		check_obj__();

		if(getEncodeOutputSize(size) > destsize){
			return edk_FAILED;
		}
		if(mP->isDecryption==TRUE){
			return edk_FAILED;
		}
		dkcmNOT_ASSERT(bsize % dkcdCAMELLIA_BLOCK_SIZE != 0);
		if(bsize % dkcdCAMELLIA_BLOCK_SIZE != 0){//あまりがある。
			return edk_ArgumentException;
		}
		
		//input側の領域が侵されるのはイヤなのでコピーしておく
		memcpy(bp,src,size);

		size_t tb = bsize / dkcdCAMELLIA_BLOCK_SIZE;		
		//bsize分暗号化
#ifndef CAMELLIA_TEST
		dkcCamelliaEncryptBlockNoDest(mP,bp,tb);
#else
		CaCBCE(mP,(uint64 *)bp,tb);
#endif
		//update
		CCamelliaHeader *hp = (CCamelliaHeader *)dest;
		hp->sig = getSignature();
		hp->origin_size = size;
		hp->encoded_size = dkcCamelliaGetOutputLength_INL(size);
		//dMB("%u",hp->encoded_size);
		mEncodedSize = hp->encoded_size;
		setResult(edk_SUCCEEDED);
		
		return edk_SUCCEEDED;
	}	
	
	virtual size_t getEncodedSize()const{
		return mEncodedSize;
	}
	
	///エンコード時の出力予定のサイズ (普通より大きめの値を返されます。) 
	virtual size_t getEncodeOutputSize(size_t size)const{
		return dkcCamelliaGetOutputLength_INL(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("CCamellia::getDecodeOutputSize() size argument exception"));
			return 0;
		}
		CCamelliaHeader *p = (CCamelliaHeader *)pHeader;
		//size_t s = p->origin_size;
		size_t s = p->encoded_size;
		return s;
	}
	virtual size_t getDecodeValidOutputSize(const void *pHeader,size_t size)const{
		if(size < header_size){
			DKUTIL_THROW_OR_NOTICE(std::runtime_error("CCamellia::getDecodeOutputSize() size argument exception"));
			return 0;
		}
		CCamelliaHeader *p = (CCamelliaHeader *)pHeader;
		size_t s = p->origin_size;
		//size_t s = p->encoded_size;
		return s;
	}


};


}//end of dkutil namespace





#endif