
#ifndef DKUTIL_CRYPTOGRAPH_ARCFOUR_HPP
#define DKUTIL_CRYPTOGRAPH_ARCFOUR_HPP


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



class CArcfourConvertStream : public IStreamConvert{
public:
	typedef IStreamConvert base_type;
	typedef IStreamConvert::first_header first_header;
	typedef CArcfourConvertStream self_type;
	BOOST_STATIC_CONSTANT(uint32,signature = edkICS_cryptograph_arcfour);
	typedef null_convert_second_header second_header;
private:
	PasswordOption mPO;
	DKC_ARCFOUR_STATE *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_ARCFOUR_STATE *getObj(){
		return mpObj;
	}

	CArcfourConvertStream(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 ~CArcfourConvertStream(){
		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();
		end(NULL);
		if(mPO.isPasswordBinary){
			mpObj = dkcAllocArcfour(pass_bin,size_bin);
		}else{
			mpObj = dkcAllocArcfour((const uint8*)pass_str,size_str);
		}
		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("CArcfourConvertStream not call begin()"));
			return false; 
		}
		dkcArcfourEncryptNoDest(mpObj,buff,size);
		r->converted_size = size;
		header_update(size);
		return true;
	
	}
	/*virtual bool convert(IStream *out,IStream *in){
		scoped_buffer_byte sb(mIBSize);
		if(false==sb.isValid()){
			mState = edk_OutOfMemory;
			return false;
		}

		for(;false==in->eof();)
		{
			in->read(sb.get(),sb.size());
			dkcArcfourEncryptNoDest(mpObj,sb.get(),sb.size());
			
			out->write(sb.get(),sb.size());
		}
		mState = edk_SUCCEEDED;
		return true;
	}*/
	/*virtual int state()const{
		return mState;
	}*/
	virtual int type()const{
		return signature;
	}
	virtual bool end(first_header *ph)
	{
		if(ph)
		{
			*ph = mHeader;
		}
		return DKUTIL_SUCCEEDED_BOOL(dkcFreeArcfour(&mpObj));
	}
	virtual size_t getConvertOutputSize(size_t s)const{
		return s ;
	}
	virtual bool getSecondHeader(void *){
		return false;
	}
};

class CArcfourStreamCipherConvert : public IStreamCipherConvert{
	CArcfourConvertStream ms;
public:
	CArcfourStreamCipherConvert(PasswordOption &x) : ms(x)
	{
		ms.begin();
	}
	virtual ~CArcfourStreamCipherConvert(){
		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();
	}
};

/*
///ストリーム暗号なのでデコーダーも一緒
class CArcfourDecodeStream : public CArcfourConvertStream {
public:
	typedef CArcfourConvertStream base_type;
	typedef base_type::first_header first_header;
	typedef CArcfourDecodeStream self_type;
	BOOST_STATIC_CONSTANT(uint32,signature = edkICS_cryptograph_arcfour_decode);

	CArcfourDecodeStream(PasswordOption &x,uint32 signature_ = self_type::signature,
		size_t inner_buffer_size = base_type::default_inner_buffer_size) :
	base_type(x,signature_,inner_buffer_size)
	{

	}
};

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

	size_t mEncodedSize;

	struct CArcfourHeader{
		///シグネチャ
		ULONG sig;
		///オリジナル 兼 暗号化済みのサイズ
		size_t size;
	};
	DKC_ARCFOUR_STATE *mP;
	BOOST_STATIC_CONSTANT(int,header_size = sizeof(CArcfourHeader));
	
	bool check_sig(CArcfourHeader *p){
		return (p->sig == getSignature());
	}
	void check_obj__()const{
		if(NULL == mP){
			DKUTIL_THROW_OR_NOTICE(std::runtime_error("CArcfour uninitialize error"));
			dkcmFORCE_NOT_ASSERT(NULL != mP);
		}
	
	}
public:
	///@see reset()
	CArcfour(const BYTE *key = NULL,size_t keysize = 0,ULONG signature=edk_ARCFOUR_SIGNATURE){
		mP = NULL;
		mEncodedSize = 0;
		reset(key,keysize);
		
	}
	void clear(){
		dkcFreeArcfour(&mP);
	}
	~CArcfour(){
		clear();
	}
	///@param signature[in] BlockSortで圧縮されていることを証明するID（デフォルトでは dkcdBlockSort_SIGNATURE )
	bool reset(const BYTE *key ,size_t keysize,ULONG signature=edk_ARCFOUR_SIGNATURE){
		setSignature(signature);
		setResult(edk_FAILED);
		clear();
		
		DKC_ARCFOUR_STATE *p = dkcAllocArcfour(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;
		//バッファの先頭にヘッダファイルがある。
		CArcfourHeader *hp = (CArcfourHeader*)src;
		check_obj__();

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



		dkcArcfourDecrypt(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(dkcArcfourEncrypt(mP,bp,bsize,src,size))){
			return edk_FAILED;
		}

		//update
		CArcfourHeader *hp = (CArcfourHeader *)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("CArcfour::getDecodeOutputSize() size argument exception"));
			return 0;
		}
		CArcfourHeader *p = (CArcfourHeader *)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);
	}


};

class CArcfour2Byte : public IArrayProcess{
	size_t mEncodedSize;

	struct CArcfour2ByteHeader{
		///シグネチャ
		ULONG sig;
		///オリジナルのサイズ
		size_t origin_size;
		///エンコード済みサイズ
		size_t encoded_size;
	};
	DKC_ARCFOUR2BYTE_STATE *mP;
	BOOST_STATIC_CONSTANT(int,header_size = sizeof(CArcfour2ByteHeader));
	
	bool check_sig(CArcfour2ByteHeader *p){
		return (p->sig == getSignature());
	}
	void check_obj__()const{
		if(NULL == mP){
			DKUTIL_THROW_OR_NOTICE(std::runtime_error("CArcfour2Byte uninitialize error"));
			dkcmFORCE_NOT_ASSERT(NULL != mP);
		}
	
	}
public:
	///@see reset()
	CArcfour2Byte(const BYTE *key = NULL,size_t keysize = 0,ULONG signature=edk_ARCFOUR_SIGNATURE){
		mP = NULL;
		mEncodedSize = 0;
		reset(key,keysize);
		
	}
	void clear(){
		dkcFreeArcfour2Byte(&mP);
	}
	~CArcfour2Byte(){
		clear();
	}
	///@param signature[in] BlockSortで圧縮されていることを証明するID（デフォルトでは dkcdBlockSort_SIGNATURE )
	bool reset(const BYTE *key ,size_t keysize,ULONG signature=edk_ARCFOUR_SIGNATURE){
		setSignature(signature);
		setResult(edk_FAILED);
		clear();
		
		DKC_ARCFOUR2BYTE_STATE *p = dkcAllocArcfour2Byte(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;
		//バッファの先頭にヘッダファイルがある。
		CArcfour2ByteHeader *hp = (CArcfour2ByteHeader*)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(ssize % 2){
			return r;
		}


		dkcArcfour2ByteDecrypt(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__();

		//エンコード済みのサイズを取得
		size_t enc_size = getEncodeOutputSize(size);
		if(enc_size > destsize){
			return edk_FAILED;
		}
		if(size % 2){
			return edk_FAILED;
		}


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

		//update
		CArcfour2ByteHeader *hp = (CArcfour2ByteHeader *)dest;
		hp->sig = getSignature();
		hp->origin_size = size;
		hp->encoded_size = enc_size;


		setResult(edk_SUCCEEDED);
		
		return edk_SUCCEEDED;
	}	
	
	
	virtual size_t getEncodedSize()const{
		return mEncodedSize;
	}
	
	///エンコード時の出力予定のサイズ (普通より大きめの値を返されます。) 
	virtual size_t getEncodeOutputSize(size_t size)const{
		//return (size % 2) + size + header_size;
		return (size & 0x00000001) + 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("CArcfour2Byte::getDecodeOutputSize() size argument exception"));
			return 0;
		}
		CArcfour2ByteHeader *p = (CArcfour2ByteHeader *)pHeader;
		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("CArcfour2Byte::getDecodeOutputSize() size argument exception"));
			return 0;
		}
		CArcfour2ByteHeader *p = (CArcfour2ByteHeader *)pHeader;
		size_t s = p->origin_size;
		return s;
	}

};


}//end of dkutil namespace





#endif