/*
 * Compaction.cpp
 *
 *  Created on: 2. 11. 2014
 *	  Author: Tomas Pecka
 */

#include "Compaction.h"

#include <exception/AlibException.h>

#include <automaton/common/State.h>
#include <automaton/FSM/CompactNFA.h>
#include <automaton/FSM/DFA.h>

#include <stack>
#include <tuple>

namespace automaton {

namespace transform {

automaton::Automaton Compaction::convert(const automaton::Automaton& automaton) {
	automaton::Automaton* out = NULL;
	automaton.getData().Accept((void*) &out, Compaction::COMPACTION);
	automaton::Automaton res(std::move(*out));
	delete out;
	return res;
}

automaton::CompactNFA Compaction::convert(const automaton::CompactNFA& automaton)
{
	return automaton;
}

automaton::CompactNFA Compaction::convert(const automaton::DFA& automaton)
{
	automaton::CompactNFA res(automaton.getInitialState());
	res.setInputAlphabet(automaton.getInputAlphabet());

	std::set<automaton::State> visited;

	std::stack<std::tuple<automaton::State, automaton::State, alphabet::Symbol>> stack; // state x lastFork x symbol we used to go to that state
	for(const auto& transition: automaton.getTransitionsFromState(automaton.getInitialState()))
		stack.push(std::make_tuple(transition.second, automaton.getInitialState(), transition.first.second));

	if(automaton.getFinalStates().count(automaton.getInitialState()) > 0)
		res.addFinalState(automaton.getInitialState());

	while(!stack.empty()) {
		automaton::State q = std::move(std::get<0>(stack.top()));
		automaton::State lastFork = std::move(std::get<1>(stack.top()));
		alphabet::Symbol symbol = std::move(std::get<2>(stack.top()));
		stack.pop();

		string::LinearString path { automaton.getInputAlphabet(), { symbol } };

		std::map<std::pair<automaton::State, alphabet::Symbol>, automaton::State> transitions;
		// only 1 child and nonfinal
		while((transitions = std::move(automaton.getTransitionsFromState(q))).size() == 1 && automaton.getFinalStates().count(q) == 0) {
			const std::pair<std::pair<automaton::State, alphabet::Symbol>, automaton::State>& transition = * transitions.begin();
			path.appendSymbol(transition.first.second);
			q = transition.second;
		}

		// fork or final state
		res.addState(q);

		if(automaton.getFinalStates().count(q))
			res.addFinalState(q);

		res.addTransition(lastFork, path, q);

		for(const std::pair<std::pair<automaton::State, alphabet::Symbol>, automaton::State>& transition : automaton.getTransitionsFromState(q))
			if(visited.insert(transition.second).second)
				stack.push(std::make_tuple(transition.second, q, transition.first.second));

	}

	return res;
}

automaton::CompactNFA Compaction::convert(const automaton::NFA& automaton)
{
	automaton::CompactNFA res(automaton.getInitialState());
	// TODO
	return res;
}

void Compaction::Visit(void* data, const CompactNFA& automaton) const
{
	automaton::Automaton*& out = *((automaton::Automaton**) data);
	out = new automaton::Automaton(this->convert(automaton));
}

void Compaction::Visit(void* data, const DFA& automaton) const
{
	automaton::Automaton*& out = *((automaton::Automaton**) data);
	out = new automaton::Automaton(this->convert(automaton));
}

void Compaction::Visit(void*, const EpsilonNFA&) const
{
	throw exception::AlibException("Unsupported automaton type EpsilonNFA");
}

void Compaction::Visit(void*, const ExtendedNFA&) const
{
	throw exception::AlibException("Unsupported automaton type ExtendedNFA");
}

void Compaction::Visit(void*, const MultiInitialStateNFA&) const
{
	throw exception::AlibException("Unsupported automaton type MultiInitialStateNFA");
}

void Compaction::Visit(void* data, const NFA& automaton) const
{
	automaton::Automaton*& out = *((automaton::Automaton**) data);
	out = new automaton::Automaton(this->convert(automaton));
}

const Compaction Compaction::COMPACTION;

} /* namespace transform */

} /* namespace automaton */