/*=============================================================================
  Copyright (c) 2011-2015 Bolero MURAKAMI
  https://github.com/bolero-MURAKAMI/Sprout

  Distributed under the Boost Software License, Version 1.0. (See accompanying
  file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
=============================================================================*/

#include <cstddef>
#include <cstdint>
#include <iterator>
#include <iostream>
#include <fstream>
#include <sprout/config.hpp>
#include <sprout/endian_traits.hpp>
#include <sprout/string.hpp>
#include <sprout/array.hpp>
#include <sprout/sub_array.hpp>
#include <sprout/container/functions.hpp>
#include <sprout/range/algorithm.hpp>
#include <sprout/preprocessor/stringize.hpp>
#include <sprout/assert.hpp>


//
// PE関連の構造体
// <windows.h> からコピペ
//
typedef unsigned long       DWORD;
typedef int                 BOOL;
typedef unsigned char       BYTE;
typedef unsigned short      WORD;


//
// File header format.
//

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;


//
// Directory format.
//

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

#define IMAGE_NUMBEROF_DIRECTORY_ENTRIES    16

//
// Optional header format.
//

typedef struct _IMAGE_OPTIONAL_HEADER {
    //
    // Standard fields.
    //

    WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;

    //
    // NT additional fields.
    //

    DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;


//
// Section header format.
//

#define IMAGE_SIZEOF_SHORT_NAME              8

typedef struct _IMAGE_SECTION_HEADER {
    BYTE    Name[IMAGE_SIZEOF_SHORT_NAME];
//    union {
//            DWORD   PhysicalAddress;
//            DWORD   VirtualSize;
//    } Misc;
	struct {
            DWORD   VirtualSize;
	} Misc;
    DWORD   VirtualAddress;
    DWORD   SizeOfRawData;
    DWORD   PointerToRawData;
    DWORD   PointerToRelocations;
    DWORD   PointerToLinenumbers;
    WORD    NumberOfRelocations;
    WORD    NumberOfLinenumbers;
    DWORD   Characteristics;
} IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;


//
// 出力バイナリサイズ
//
#ifndef BRAINFUCK_BINARY_SIZE
#	define BRAINFUCK_BINARY_SIZE (16 * 1024)
#endif

//
// ループ制限
//
#ifndef BRAINFUCK_LOOP_LIMIT
#	define BRAINFUCK_LOOP_LIMIT 256
#endif

//
// 入力ファイル名
//
#ifndef BRAINFUCK_SOURCE_FILE
#	define BRAINFUCK_SOURCE_FILE a.bf
#endif


//
// 定数
//
SPROUT_STATIC_CONSTEXPR std::size_t bin_size = BRAINFUCK_BINARY_SIZE;

SPROUT_STATIC_CONSTEXPR std::size_t loop_limit = BRAINFUCK_LOOP_LIMIT;

SPROUT_STATIC_CONSTEXPR auto source = sprout::to_string(
#	include SPROUT_PP_STRINGIZE(BRAINFUCK_SOURCE_FILE)
	);

SPROUT_STATIC_CONSTEXPR std::int32_t addr_putchar = 0x00405044;
SPROUT_STATIC_CONSTEXPR std::int32_t addr_getchar = addr_putchar + 4;
SPROUT_STATIC_CONSTEXPR std::int32_t addr_buf = 0x00406000;


//
// 出力用関数
//
template<typename OutputIterator, typename T>
SPROUT_CXX14_CONSTEXPR std::size_t
write_bytes(OutputIterator& out, T const& val) {
	typedef sprout::little_endian_traits<T> traits;
	for (std::size_t i = 0; i != traits::size(); ++i) {
		*out++ = sprout::little_endian_traits<T>::get_byte(val, i);
	}
	return traits::size();
}
template<typename OutputIterator, typename T, std::size_t N>
SPROUT_CXX14_CONSTEXPR std::size_t
write_bytes(OutputIterator& out, T const(& vals)[N]) {
	typedef sprout::little_endian_traits<T> traits;
	for (std::size_t i = 0; i != N; ++i) {
		::write_bytes(out, vals[i]);
	}
	return traits::size() * N;
}
template<typename OutputIterator, typename Head, typename... Tail>
SPROUT_CXX14_CONSTEXPR std::size_t
write_bytes(OutputIterator& out, Head const& head, Tail const&... tail) {
	return ::write_bytes(out, head) + ::write_bytes(out, tail...);
}

template<typename OutputIterator>
SPROUT_CXX14_CONSTEXPR std::size_t
write_pe_header(OutputIterator& out, std::size_t n = 0) {
	SPROUT_CONSTEXPR unsigned char stub[] = {
		// 00-3b: DOS Header
		'M',  'Z',  0x50, 0x00, 0x02, 0x00, 0x00, 0x00, 0x04, 0x00, 0x0f, 0x00, 0xff, 0xff, 0x00, 0x00,
		0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x1a, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
		// 3c-3f: Pointer to PE Header (=80)
		0x80, 0x00, 0x00, 0x00,
		// 40-7f: DOS Stub
		0xba, 0x10, 0x00, 0x0e, 0x1f, 0xb4, 0x09, 0xcd, 0x21, 0xb8, 0x01, 0x4c, 0xcd, 0x21, 0x90, 0x90,
		'T', 'h', 'i', 's', ' ', 'p', 'r', 'o', 'g', 'r', 'a', 'm', ' ', 'c', 'a', 'n',
		'n', 'o', 't', ' ', 'b', 'e', ' ', 'r', 'u', 'n', ' ', 'i', 'n', ' ', 'D', 'O',
		'S', ' ', 'm', 'o', 'd', 'e', '.', '\r', '\n', '$', 0, 0, 0, 0, 0, 0,
		// 80-83: PE Signature
		'P', 'E', 0, 0
	};
	n += ::write_bytes(
		out,
		stub
		);

	SPROUT_CONSTEXPR ::IMAGE_FILE_HEADER coff = {
		0x014c,								// Machine
		3,									// NumberOfSections
		0,									// TimeDateStamp
		0,									// PointerToSymbolTable
		0,									// NumberOfSymbols
		sizeof(::IMAGE_OPTIONAL_HEADER32),	// SizeOfOptionalHeader
		0x030f								// Characteristics
	};
	n += ::write_bytes(
		out,
		coff.Machine,
		coff.NumberOfSections,
		coff.TimeDateStamp,
		coff.PointerToSymbolTable,
		coff.NumberOfSymbols,
		coff.SizeOfOptionalHeader,
		coff.Characteristics
		);

	SPROUT_CONSTEXPR ::IMAGE_OPTIONAL_HEADER32 opt = {
		0x010b,			// Magic
		6, 0,			// MajorLinkerVersion, MinorLinkerVersion
		::bin_size,		// SizeOfCode
		0,				// SizeOfInitializedData
		65536,			// SizeOfUninitializedData
		0x1000,			// AddressOfEntryPoint
		0x1000,			// BaseOfCode
		0x6000,			// BaseOfData
		0x00400000,		// ImageBase
		0x1000,			// SectionAlignment
		0x0200,			// FileAlignment
		4, 0,			// MajorOperatingSystemVersion, MinorOperatingSystemVersion
		0, 0,			// MajorImageVersion, MinorImageVersion
		4, 0,			// MajorSubsystemVersion, MinorSubsystemVersion
		0,				// Win32VersionValue
		0x16000,		// SizeOfImage
		0x200,			// SizeOfHeaders
		0,				// CheckSum
		3,				// Subsystem
		0,				// DllCharacteristics
		1024 * 1024,	// SizeOfStackReserve
		8 * 1024,		// SizeOfStackCommit
		1024 * 1024,	// SizeOfHeapReserve
		4 * 1024,		// SizeOfHeapCommit
		0,				// LoaderFlags
		16,				// NumberOfRvaAndSizes
		{
			{},
			{
				0x5000,	// VirtualAddress (import table)
				100		// Size
			},
			{}
		}
	};
	n += ::write_bytes(
		out,
		opt.Magic,
		opt.MajorLinkerVersion, opt.MinorLinkerVersion,
		opt.SizeOfCode,
		opt.SizeOfInitializedData,
		opt.SizeOfUninitializedData,
		opt.AddressOfEntryPoint,
		opt.BaseOfCode,
		opt.BaseOfData,
		opt.ImageBase,
		opt.SectionAlignment,
		opt.FileAlignment,
		opt.MajorOperatingSystemVersion, opt.MinorOperatingSystemVersion,
		opt.MajorImageVersion, opt.MinorImageVersion,
		opt.MajorSubsystemVersion, opt.MinorSubsystemVersion,
		opt.Win32VersionValue,
		opt.SizeOfImage,
		opt.SizeOfHeaders,
		opt.CheckSum,
		opt.Subsystem,
		opt.DllCharacteristics,
		opt.SizeOfStackReserve,
		opt.SizeOfStackCommit,
		opt.SizeOfHeapReserve,
		opt.SizeOfHeapCommit,
		opt.LoaderFlags,
		opt.NumberOfRvaAndSizes,
		opt.DataDirectory[0].VirtualAddress, opt.DataDirectory[0].Size,
		opt.DataDirectory[1].VirtualAddress, opt.DataDirectory[1].Size
		);
	for (std::size_t i = 2; i != IMAGE_NUMBEROF_DIRECTORY_ENTRIES; ++i) {
		n += ::write_bytes(
			out,
			opt.DataDirectory[i].VirtualAddress, opt.DataDirectory[i].Size
			);
	}

	SPROUT_CONSTEXPR ::IMAGE_SECTION_HEADER sects[3] = {
		{
			".text",		// Name
			{::bin_size},	// Misc.VirtualSize
			0x1000,			// VirtualAddress
			::bin_size,		// SizeOfRawData
			0x400,			// PointerToRawData
			0,				// PointerToRelocations
			0,				// PointerToLinenumbers
			0,				// NumberOfRelocations
			0,				// NumberOfLinenumbers
			0x60500060		// Characteristics
		},
		{
			".idata",		// Name
			{100},			// Misc.VirtualSize
			0x5000,			// VirtualAddress
			512,			// SizeOfRawData
			0x200,			// PointerToRawData
			0,				// PointerToRelocations
			0,				// PointerToLinenumbers
			0,				// NumberOfRelocations
			0,				// NumberOfLinenumbers
			0xc0300040		// Characteristics
		},
		{
			".bss",			// Name
			{65536},		// Misc.VirtualSize
			0x6000,			// VirtualAddress
			0,				// SizeOfRawData
			0,				// PointerToRawData
			0,				// PointerToRelocations
			0,				// PointerToLinenumbers
			0,				// NumberOfRelocations
			0,				// NumberOfLinenumbers
			0xc0400080		// Characteristics
		}
	};
	for (std::size_t i = 0; i != 3; ++i) {
		n += ::write_bytes(
			out,
			sects[i].Name,
			sects[i].Misc.VirtualSize,
			sects[i].VirtualAddress,
			sects[i].SizeOfRawData,
			sects[i].PointerToRawData,
			sects[i].PointerToRelocations,
			sects[i].PointerToLinenumbers,
			sects[i].NumberOfRelocations,
			sects[i].NumberOfLinenumbers,
			sects[i].Characteristics
			);
	}

	for (; n != 0x200; ) {
		n += ::write_bytes(
			out,
			(unsigned char)0
			);
	}
	return n;
}

template<typename OutputIterator>
SPROUT_CXX14_CONSTEXPR std::size_t
write_idata(OutputIterator& out, std::size_t n = 0) {
	SPROUT_CONSTEXPR int idt[] = {
		// IDT 1
		0x5028, 0, 0, 0x5034, 0x5044,
		// IDT (終端)
		0, 0, 0, 0, 0
	};
	n += ::write_bytes(
		out,
		idt
		);

	SPROUT_CONSTEXPR int ilt_iat[]  = {
		0x5050, 0x505a, 0
	};

	// ILT
	n += ::write_bytes(
		out,
		ilt_iat
		);
	n += ::write_bytes(
		out,
		"msvcrt.dll\0\0\0\0\0"
		);

	// IAT
	n += ::write_bytes(
		out,
		ilt_iat
		);
	n += ::write_bytes(
		out,
		(std::int16_t)0,
		"putchar",
		(std::int16_t)0,
		"getchar"
		);

	for (; n != 0x400; ) {
		n += ::write_bytes(
			out,
			(unsigned char)0
			);
	}
	return n;
}

//
// コンパイル
//
template<std::size_t LoopLimit = 256, typename InputIterator, typename RandomAccessIterator>
SPROUT_CXX14_CONSTEXPR std::size_t
compile(InputIterator first, InputIterator last, RandomAccessIterator& out_first) {
	sprout::array<int, LoopLimit> loops {{}};	// ループを保存するスタック
	auto loop_first = sprout::begin(loops);
	auto loop_last = sprout::end(loops);
	auto loop = loop_first;

	int idx = 0;
	auto out = out_first;

	idx += ::write_bytes(
		out,
		(unsigned char)0x31, (unsigned char)0xc9,														// xor ecx, ecx
		(unsigned char)0x57,																			// push edi
		(unsigned char)0xbf, ::addr_buf																	// mov edi, addr_buf
		);

	for (; first != last; ++first) {
		switch (*first) {
			case '>':
				idx += ::write_bytes(
					out,
					(unsigned char)0x66, (unsigned char)0x41											// inc cx
					);
				break;
			case '<':
				idx += ::write_bytes(
					out,
					(unsigned char)0x66, (unsigned char)0x49											// dec cx
					);
				break;
			case '+':
				idx += ::write_bytes(
					out,
					(unsigned char)0xfe, (unsigned char)0x04, (unsigned char)0x0f						// inc byte [edi+ecx]
					);
				break;
			case '-':
				idx += ::write_bytes(
					out,
					(unsigned char)0xfe, (unsigned char)0x0c, (unsigned char)0x0f						// dec byte [edi+ecx]
					);
				break;
			case '.':
				idx += ::write_bytes(
					out,
					(unsigned char)0x51,																// push ecx
					(unsigned char)0x0f, (unsigned char)0xb6, (unsigned char)0x04, (unsigned char)0x0f,	// movzx eax,byte [edi+ecx]
					(unsigned char)0x50,																// push eax
					(unsigned char)0xa1, ::addr_putchar,												// mov eax, [addr_putchar]
					(unsigned char)0xff, (unsigned char)0xd0,											// call eax
					(unsigned char)0x58,																// pop eax
					(unsigned char)0x59																	// pop ecx
					);
				break;
			case ',':
				idx += ::write_bytes(
					out,
					(unsigned char)0x51,																// push ecx
					(unsigned char)0xa1, ::addr_getchar,												// mov eax, [addr_getchar]
					(unsigned char)0xff, (unsigned char)0xd0,											// call eax
					(unsigned char)0x59,																// pop ecx
					(unsigned char)0x88, (unsigned char)0x04, (unsigned char)0x0f						// mov [edi+ecx],al
					);
				break;
			case '[':
				SPROUT_ASSERT_MSG(loop != loop_last, "loop overflow");
				*loop++ = idx;	// インデックスをスタックに積む
				idx += ::write_bytes(
					out,
					(unsigned char)0x80, (unsigned char)0x3c, (unsigned char)0x0f, (unsigned char)0x00,	// cmp byte [edi+ecx],0
					(unsigned char)0x0f, (unsigned char)0x84,											// jz 対応する]の直後
					(unsigned char)0xde, (unsigned char)0xad, (unsigned char)0xbe, (unsigned char)0xef	// （アドレスは後で決定）
					);
				break;
			case ']':
			{
				SPROUT_ASSERT_MSG(loop != loop_first, "missing '['");
				int idx_loop = *--loop;
				idx += ::write_bytes(
					out,
					(unsigned char)0xe9, (std::int32_t)(idx_loop - (idx + 5))							// jmp idx_loop
					);
				auto out_loop = out_first + (idx_loop + 6);
				::write_bytes(
					out_loop,
					(std::int32_t)(idx - (idx_loop + 10))												// （アドレス）
					);
				break;
			}
		}
		SPROUT_ASSERT_MSG(idx <= (int)::bin_size - 32, "buffer overflow");
	}

	SPROUT_ASSERT_MSG(loop == loop_first, "missing ']'");

	// 終了処理
	idx += ::write_bytes(
		out,
		(unsigned char)0x5f,																			// pop edi
		(unsigned char)0x31, (unsigned char)0xc0,														// xor eax, eax
		(unsigned char)0xc3																				// ret
		);

	return idx;
}

//
// ビルド
//
template<std::size_t LoopLimit = 256, typename InputIterator, typename RandomAccessIterator>
SPROUT_CXX14_CONSTEXPR std::size_t
build(InputIterator first, InputIterator last, RandomAccessIterator& out) {
	std::size_t n = 0;
	n += ::write_pe_header(out, n);					// ヘッダ出力
	n += ::write_idata(out, n);						// .idataセクション出力
	n += ::compile<LoopLimit>(first, last, out);	// コンパイル
	return n;
}


SPROUT_CXX14_CONSTEXPR sprout::sub_array<sprout::array<unsigned char, ::bin_size> >
build_brainfuck() {
	sprout::array<unsigned char, ::bin_size> bin{{}};
	auto out = sprout::begin(bin);
	auto n = ::build<::loop_limit>(sprout::begin(::source), sprout::end(::source), out);
	return sprout::sub_copy(bin, 0, n);
}

int main(int argc, char* argv[]) {
	SPROUT_CXX14_CONSTEXPR auto bin = ::build_brainfuck();

	char const* filename = argc >= 2 ? argv[1]
		: "a.exe"
		;
	std::ofstream ofs(filename);
	if (!ofs) {
		std::cout
			<< "could not open file" << std::endl
			;
		return -1;
	}
	sprout::range::copy(bin, std::ostreambuf_iterator<char>(ofs));
}
