#pragma once
#include "chocolat/string/character_graphic.hpp"
#include "chocolat/filter/stream_filter.hpp"
#include <string>
#include "chocolat/string/number_format.hpp"
#include "chocolat/string/string_aligner.hpp"

#include "debug_macro.hpp"

/** This filter dump the binary stream.
 *  Usage
 *    std::cin >> BinaryDump(4, BinaryDump::WRITE_ADDR | BinaryDump::WRITE_HEX)
 *             >> std::cout; //=> 000000: 7F 45 4C 46 ...
 */
class BinaryDump : public chocolat::filter::IStreamFilter
{
public:
	/** This enum permit bitwise operation (and, or) .*/
	enum ModeEnum{
		WRITE_UNDEF=0,
		WRITE_ADDR=1,
		WRITE_HEX=2,
		WRITE_CHAR=4,
		WRITE_ALL=7,
	};
	/** This value has bitwise operation method.*/
	class Mode
	{
		int m_mode;
	public:
		Mode(int mode=WRITE_UNDEF)
				:m_mode(mode)
		{
		}
		Mode& operator|=(Mode rh)
		{
			m_mode |= rh.m_mode;
			return *this;
		}
		bool operator==(Mode rh)const
		{
			return m_mode == rh.m_mode;
		}
		bool operator&(Mode rh)const
		{
			return m_mode & rh.m_mode;
		}
	};
private:
	unsigned m_column_number;
	Mode m_mode;
public:
	BinaryDump(unsigned line_byte_number, Mode mode=WRITE_ALL)
			:m_column_number(line_byte_number),
			 m_mode(mode)
	{
		if (m_mode == WRITE_UNDEF){
			m_mode = WRITE_ALL;
		}
	}
	BinaryDump(const BinaryDump& that)
			:m_column_number(that.m_column_number),
			 m_mode(that.m_mode)
	{
	}
	/** This method is main body of filter.
	 *  This method uses StringAligner. 
	 */
	virtual void operator()(std::istream& is, std::ostream& os)
	{
		const unsigned HEX_WIDTH=2;
		const unsigned INTERVAL_WIDTH=2;
		
		std::string hex_line;
		std::string char_line;
		unsigned current=0; // This value counts how many bytes are written.
		for (char ch; is.get(ch); ) {
			HexadecimalFormat h((unsigned char)(ch));
			hex_line += h.width(HEX_WIDTH).to_s() + " ";
			
			CharacterGraph c(ch);
			char_line += c;
			
			if (current % m_column_number == 0 ){
				if (m_mode & WRITE_ADDR){
					os << HexadecimalFormat(current).width(6) << ": ";
				}
			}
			++current;
			if (current % m_column_number == 0 || is.eof()){
				if (m_mode & WRITE_HEX){
					StringAligner left(
							m_column_number*(HEX_WIDTH+1)+INTERVAL_WIDTH,
							StringAligner::LEFT);
					os << left.align(hex_line);
				}
				if (m_mode & WRITE_CHAR){
					os << char_line;
				}
				os << std::endl;
				hex_line = "";
				char_line="";
			}
		}
	}
	virtual IStreamFilter* clone()const
	{
		return new BinaryDump(*this);
	}
};
