#include "application_option.hpp"
#include "nes_program_image.hpp"
#include "instruction_map.hpp"
#include "instruction_scanner.hpp"
#include "instruction_output.hpp"
#include "control_point.hpp"
#include "basic_block.hpp"
#include "routine_list.hpp"
#include "dominator_list.hpp"
#include "loop_structure.hpp"

#include <fstream>

using namespace std;

void writeQuadruple(InstructionMap::shared_ptr map)
{
	for (auto i=map->begin(); i!=map->end(); ++i) {
		InstructionQuadrupleOutput str(i->second);
		str.display(cout);
		cout << endl;
	}
}
void writeDisassembly(InstructionMap::shared_ptr map)
{
	for (auto i=map->begin(); i!=map->end(); ++i) {
		InstructionOutput str(i->second);
		str.display(cout);
		cout << endl;
	}
}

int main(int argc, char** argv)
{
	ApplicationOption option(argc, argv);
	if (option.bad()){
		option.dump(cout);
		throw InvalidOptionException(option.bad_option);
	}
	
	NesProgramImage image;
	if (option.has_start_address) {
		image.loadFile(option.file_name, option.start_address);
	} else {
		image.loadFile(option.file_name);
	}
	if (option.is_display_hex_dump) {
		image.dump(cout);
	}
	
	ByteVector::shared_ptr bytes = image.getPRG_ROM()->getVector();
	
	InstructionScanner scanner(image.getPRG_ROM()->startAddress());
	scanner.readByteVector(*bytes);
	
	if (option.is_display_assembly) {
		writeDisassembly(scanner.getMap());
	}
	if (option.is_display_quadruple) {
		writeQuadruple(scanner.getMap());
	}
	
	ControlPointAnalyzer ca_analyzer(image.getPRG_ROM(),
									 scanner.getMap());
	ca_analyzer.pushEntryPoint(image.getResetHandler());
	ca_analyzer.pushEntryPoint(image.getNMIHandler());
	ca_analyzer.pushEntryPoint(image.getIRQHandler());
	ca_analyzer.analyze();
	
	BasicBlockAnalyzer bb_analyzer(scanner.getMap(),
								   ca_analyzer.getControlPointList());
	bb_analyzer.analyze();
	if (option.is_display_basic_block) {
		bb_analyzer.displayBriefResult(cout);
		bb_analyzer.displayBlock(cout);
	}
	
	RoutineAnalyzer me_analyzer(bb_analyzer.getBlockList());
	me_analyzer.analyze();
	if (option.is_display_routine) {
		me_analyzer.display(cout);
	}
	DominatorAnalyzer dm_analyzer;
    RoutineList::shared_ptr routines = me_analyzer.getRoutineList();
	for (RoutineList::vector::iterator i=routines->begin(); i!=routines->end(); ++i){
        Routine::shared_ptr routine = *i;
        DominatorLattice::shared_ptr dom = dm_analyzer.getDominatorLattice(routine);
		if (option.is_display_dominator) {
			dom->display(std::cout);
			std::cout << std::endl;
		}
		LoopAnalyzer lp_analyzer;
		lp_analyzer.analyze(routine, dom);
	}
	
	return 0;
}

