/* Tachyon - Command Line for Graph Theoretic
 * Copyright (C) 2001-2002  Jay Freeman (saurik)
*/

/*
 *        Redistribution and use in source and binary
 * forms, with or without modification, are permitted
 * provided that the following conditions are met:
 * 
 * 1. Redistributions of source code must retain the
 *    above copyright notice, this list of conditions
 *    and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the
 *    above copyright notice, this list of conditions
 *    and the following disclaimer in the documentation
 *    and/or other materials provided with the
 *    distribution.
 * 3. The name of the author may not be used to endorse
 *    or promote products derived from this software
 *    without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS''
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING,
 * BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include "stdafx.h"

#include "apr_getopt.h"

#include <boost/graph/graph_utility.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/properties.hpp>
#include <boost/property_map.hpp>

#include "GEM.h"

#define FUCKING_A { std::wcout << L"Fucking-A!" << std::endl; int dude; std::cin >> dude;}

const apr_getopt_option_t options[] = {
	{NULL, 'v', FALSE},
	{NULL, 0, 0}
};

void Decompile();

int main(int argc, char *argv[]) {
	//ManualOverride.SetName("Tachyon");

	apr_initialize();
	Diapexis::PexInitialize();

	apr_pool_t *context;
	apr_pool_create(&context, NULL);

	apr_getopt_t *opt;
	apr_getopt_init(&opt, context, argc, argv);
	opt->interleave = TRUE;

	int option;
	cstr_t arg;

	while (apr_getopt_long(opt, options, &option, &arg) == APR_SUCCESS)
		switch (option) {
			case 'v':	break;
		}

	/*if (opt->ind == opt->argc)
		; //showusage();
	else {
		HANDLE(Dialect::IDialect) dialect("Dialect.CSharpDialect");
		dialect->Assign(std::wcout);

		dialect->Formulate(const_cast<Metallurgy::Method *>(dynamic_cast<const Metallurgy::Method *>(token)));
		std::cout << opt->argv[opt->ind] << std::endl;
	}*/

	Decompile();

	Diapexis::PexTerminate();
	apr_terminate();
	return 0;
}

void DecompileJava(uint8_t *data, uint32_t length) {
	JavaVM *jvm;
	JNIEnv *env;

	JavaVMOption options[1];
	options[0].optionString = "-Djava.class.path=java;"
								"java/Xalan/BCEL.jar;"
								"java/Xalan/xalan.jar;"
								"java/Xalan/xercesImpl.jar;"
								"java/Xalan/xml-apis.jar;"
								"java/Xalan/xsltc.jar;"
								"C:\\jdk1.3.1_02\\lib\\tools.jar;"
								"D:\\Code\\CVS\\Apache\\jakarta-ant\\build\\lib\\ant.jar;"
								"D:\\Code\\CVS\\Apache\\jakarta-ant\\lib\\crimson.jar;"
								"D:\\Code\\CVS\\Apache\\jakarta-ant\\lib\\optional\\junit.jar;"
								"D:\\Code\\CVS\\Apache\\jakarta-commons\\collections\\dist\\commons-collections.jar";

	JavaVMInitArgs vm_args;
	vm_args.version = JNI_VERSION_1_2;
	vm_args.options = options;
	vm_args.nOptions = 1;
	vm_args.ignoreUnrecognized = 1;

	jint res = JNI_CreateJavaVM(&jvm, (void **) &env, &vm_args);

	jclass cls = env->FindClass("Parser");
	jmethodID mid = env->GetMethodID(cls, "parse", "([B)V");

	jobject parser = env->NewObject(cls, env->GetMethodID(cls, "<init>", "()V"));

	jbyteArray bytes = env->NewByteArray(length);
	env->SetByteArrayRegion(bytes, 0, length, (signed char *) data);
	env->CallVoidMethod(parser, mid, bytes);

	jvm->DestroyJavaVM();
}

typedef int32_t CoreType;

class CoreTypes {
  public:
	template <typename core>
	class Factory {
	  public:
		static CoreType instance(uint8_t *data, uint32_t &offset) {
			core value;
			::memcpy(&value, data + offset, sizeof(core));
			offset += sizeof(core);
			return (CoreType) value;
		}
	};

	typedef CoreType (*Construct)(uint8_t *data, uint32_t &offset);

  private:
	typedef std::pair<std::wstring, Construct> Type;
	typedef std::map<std::wstring, Construct> Types;
	Types types;

  public:
	CoreTypes() {
		types.insert(Type(L"uint8",		Factory<uint8_t>::instance));
		types.insert(Type(L"uint16",	Factory<uint16_t>::instance));
		types.insert(Type(L"uint32",	Factory<uint32_t>::instance));
		types.insert(Type(L"uint64",	Factory<uint64_t>::instance));
		types.insert(Type(L"int8",		Factory<int8_t>::instance));
		types.insert(Type(L"int16",		Factory<int16_t>::instance));
		types.insert(Type(L"int32",		Factory<int32_t>::instance));
		types.insert(Type(L"int64",		Factory<int64_t>::instance));
		types.insert(Type(L"real32",	Factory<float>::instance));
		types.insert(Type(L"real64",	Factory<double>::instance));
	}

	Construct lookup(const std::wstring &name) {
		Types::const_iterator type = types.find(name);
		if (type != types.end())
			return type->second;
		FUCKING_A
		return NULL;
	}
};

CoreTypes coreTypes;

class Engine {
  private:
	std::wstring set;

	XalanSourceTreeDOMSupport		support;
	XalanSourceTreeParserLiaison	liaison;
	XPathEvaluator					xpath;

	XalanDocument *					engine;
	XalanNode *						opcodes;

	struct Type {
		std::wstring name;
		CoreTypes::Construct construct;

		Type(const std::wstring &name, CoreTypes::Construct construct) :
			name(name), construct(construct)
		{}
	};

	typedef std::vector<Type> Types;
	Types types;

	typedef std::pair<std::wstring, uint32_t> TypeName;
	typedef std::map<std::wstring, uint32_t> TypeNames;
	TypeNames typeNames;

  public:
	CoreType getValue(uint8_t *data, uint32_t &offset, const std::wstring &name) {
		return types[typeNames[name]].construct(data, offset);
	}

  public:
	Engine(const std::wstring &set) :
		set(set),
		liaison(support)
	{
		support.setParserLiaison(&liaison);

		engine = liaison.parseXMLStream(LocalFileInputSource((L"engines/" + set + L".xml").c_str()));
		opcodes = xpath.selectSingleNode(support, engine->getDocumentElement(), L"opcodes", NULL);

		NodeRefList datatypes = xpath.selectNodeList(support, engine->getDocumentElement(), L"datatypes/type", NULL);
		for (NodeRefList::size_type i(0); i < datatypes.getLength(); ++i) {
			XalanNode *type = datatypes.item(i);
			const XalanNamedNodeMap *attrs = type->getAttributes();

			std::wstring name = attrs->getNamedItem(XalanDOMString(L"name"))->getNodeValue().c_str();
			std::wstring stored = attrs->getNamedItem(XalanDOMString(L"stored"))->getNodeValue().c_str();

			typeNames.insert(TypeName(name, i));
			types.push_back(Type(name, coreTypes.lookup(stored)));
		}
	}

	struct Control {
		uint32_t from;
		uint32_t to;
		std::wstring name;
		bool adjust;

		Control(uint32_t from, uint32_t to, const std::wstring &name, bool adjust) :
			from(from), to(to), name(name), adjust(adjust)
		{}
	};

	typedef std::vector<Control> FlowControl;

	struct w_string {
		std::wstring data;

		w_string() { FUCKING_A }
		w_string(const std::wstring &data) : data(data) {}
	};

	class Operation {
	  protected:
		typedef std::map<std::wstring, CoreType> Arguments;

		Arguments args;
		std::wstring name;
		uint32_t start;
	};

	class Instruction : public Operation {
	  private:
		Engine *engine;

	  public:
		Instruction() {}

		Instruction(Engine *engine, XalanNode *op, uint8_t *data, uint32_t start, uint32_t &offset, FlowControl &control) :
			engine(engine)
		{
			const class XalanNamedNodeMap *attrs = op->getAttributes();
			name = attrs->getNamedItem(XalanDOMString(L"name"))->getNodeValue().c_str();
			std::wcout << name << std::endl;
			this->start = start;

			readArgs(op, data, offset, control);
		}

		inline int32_t readInt(const XalanDOMString &value) {
			return value.compare(0, 1, L"$") ? _wtoi(value.c_str()) : args[value.substr(1).c_str()];
		}

		void readArgs(XalanNode *where, uint8_t *data, uint32_t &offset, FlowControl &control) {
			for (XalanNode *cmd = where->getFirstChild(); cmd != NULL; cmd = cmd->getNextSibling()) {
				std::wstring cname = cmd->getNodeName().c_str();
				const XalanNamedNodeMap *attrs = cmd->getAttributes();

				if (cname == L"arg") {
					std::wstring aname = attrs->getNamedItem(XalanDOMString(L"name"))->getNodeValue().c_str();
					std::wcout << L"  " << aname << L"=";
					CoreType value = engine->getValue(data, offset, attrs->getNamedItem(XalanDOMString(L"type"))->getNodeValue().c_str());
					std::wcout << value << std::endl;

					XalanNode *branch = attrs->getNamedItem(XalanDOMString(L"branch"));
					if (branch != NULL) {
						XalanDOMString kind = branch->getNodeValue();
						if (!kind.compare(L"pre")) {
							control.push_back(Control(start, start + value, aname, false));
						} else if (!kind.compare(L"post")) {
							control.push_back(Control(start, start + value, aname, true));
						} else {
							FUCKING_A
						}
					}
				} else if (cname == L"multi") {
					int32_t start = readInt(attrs->getNamedItem(XalanDOMString(L"start"))->getNodeValue());
					int32_t end = readInt(attrs->getNamedItem(XalanDOMString(L"end"))->getNodeValue());

					for (int32_t i(start); i < end; i++) {
						readArgs(cmd, data, offset, control); // XXX: This needs a prefix or something...
					}
				}
			}
		}
	};

	typedef boost::property<boost::edge_name_t, w_string> EdgeProperty;
	typedef boost::property<boost::vertex_name_t, Operation> VertexProperty;
	typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::bidirectionalS, VertexProperty, EdgeProperty> Flow;

	struct Unit {
		Flow::vertex_descriptor vertex;
		uint32_t width;

		Unit() {}

		Unit(Flow::vertex_descriptor vertex, uint32_t width) :
			vertex(vertex), width(width)
		{}
	};

	void parse(uint8_t *data, uint32_t length) {
		typedef std::pair<uint32_t, Unit> Code;
		typedef std::map<uint32_t, Unit> Codes;

		Flow flow;
		Codes codes;
		FlowControl control;

		boost::property_map<Flow, boost::vertex_name_t>::type vertex_name_map = boost::get(boost::vertex_name, flow);
		boost::property_map<Flow, boost::edge_name_t>::type edge_name_map = boost::get(boost::edge_name, flow);

		wchar_t hex[3];

		XalanNode *strata = opcodes;
		std::wcout << L"Parsing..." << std::endl << std::endl;

		for (uint32_t offset(0), start(0); offset < length; ) {
			uint8_t code = data[offset++];
			_itow(code, hex, 16);
			std::wcout << start << L"(" << hex << L"): ";
			std::wstring check = L"[@code = '" + std::wstring(hex) + L"']";

			XalanNode *op = xpath.selectSingleNode(support, strata, (L"op" + check).c_str(), NULL);

			if (op == NULL) {
				strata = xpath.selectSingleNode(support, strata, (L"strata" + check).c_str(), NULL);
				if (strata == NULL)
					FUCKING_A
				continue;
			} else {
				strata = opcodes;
			}

			Flow::vertex_descriptor point = boost::add_vertex(Instruction(this, op, data, start, offset, control), flow);
			codes.insert(Code(start, Unit(point, offset - start)));

			const class XalanNamedNodeMap *attrs = op->getAttributes();
			if (attrs->getNamedItem(XalanDOMString(L"stop")) == NULL)
				control.push_back(Control(start, offset, L"next", false));

			start = offset;
		}

		for (FlowControl::const_iterator ctrl = control.begin(); ctrl != control.end(); ++ctrl) {
			uint32_t target = ctrl->adjust ? (codes[ctrl->from].width + ctrl->to) : ctrl->to;
			boost::add_edge(codes[ctrl->from].vertex, codes[target].vertex, w_string(ctrl->name), flow);
		}

		std::wcout << std::endl;
		boost::print_graph(flow);

		graph_embedding<Flow> embedFlow(flow);
		embedFlow.embed();

		/*graph_traits<Graph>::in_edge_iterator ei, edge_end;
		for (boost::tie(ei, edge_end) = in_edges(vertex(2, bob), bob); ei != edge_end; ++ei) {
			std::cout << "Bob!" << std::endl;
		}*/
	}
};

void DecompileBoost(uint8_t *data, uint32_t length) {
	XMLPlatformUtils::Initialize();
	XPathEvaluator::initialize();

	{
		XalanSourceTreeInit theSourceTreeInit;
		Engine engine(L"msil");
		engine.parse(data, length);
	}

	XPathEvaluator::terminate();
	XMLPlatformUtils::Terminate();
}

void Decompile() {
	Metallurgy::Assembly assembly(L"test/Test.exe");
	const Metallurgy::Module *module = assembly.getModule();
	const Metallurgy::TypeDef *type = module->FindTypeDef(L"Test");
	const Metallurgy::Method *method = type->FindMethod(L"Main");

	const Metallurgy::ILMethodImpl *impl = dynamic_cast<const Metallurgy::ILMethodImpl *>(method->getImpl());
	uint8_t *data = impl->getOffset();
	uint32_t length = impl->getLength();

	//DecompileJava(data, length);
	DecompileBoost(data, length);
}