#ifndef BANDIT_INFO_REPORTER_H
#define BANDIT_INFO_REPORTER_H

namespace bandit {
namespace detail {

struct info_reporter : public progress_reporter
{
	struct context_info
	{
		context_info(const char *d) : desc(d), total(0), skipped(0), failed(0) {}
		void merge(const context_info &ci)
		{
			total += ci.total;
			skipped += ci.skipped;
			failed += ci.failed;
		}
		const char *desc;
		int total;
		int skipped;
		int failed;
	};

	info_reporter(std::ostream &stm, const failure_formatter &failure_formatter, const detail::colorizer &colorizer)
	  : progress_reporter(failure_formatter)
	  , stm_(stm)
	  , colorizer_(colorizer)
	  , indentation_(0)
	  , not_yet_shown_(0)
	  , context_stack_()
	{}

	info_reporter(const failure_formatter &failure_formatter, const detail::colorizer &colorizer)
	  : progress_reporter(failure_formatter)
	  , stm_(std::cout)
	  , colorizer_(colorizer)
	  , indentation_(0)
	  , not_yet_shown_(0)
	  , context_stack_()
	{}

	info_reporter &operator=(const info_reporter &)
	{
		return *this;
	}

	void list_failures_and_errors()
	{
		if (specs_failed_ > 0) {
			stm_
			  << colorizer_.red()
			  << "List of failures:"
			  << std::endl;
			std::for_each(failures_.begin(), failures_.end(), [&](const std::string &failure) {
				stm_
				 << colorizer_.white()
				 << " (*) "
				 << colorizer_.red()
				 << failure << std::endl;
			});
		}
		if (test_run_errors_.size() > 0) {
			stm_
			  << colorizer_.red()
			  << "List of run errors:"
			  << std::endl;
			std::for_each(test_run_errors_.begin(), test_run_errors_.end(), [&](const std::string &error) {
				stm_
				 << colorizer_.white()
				 << " (*) "
				 << colorizer_.red()
				 << error << std::endl;
			});
		}
	}

	void summary()
	{
		stm_
		  << colorizer_.white()
		  << "Tests run: " << specs_run_
		  << std::endl;
		if (specs_skipped_ > 0) {
			stm_
			  << colorizer_.yellow()
			  << "Skipped: " << specs_skipped_
			  << std::endl;
		}
		if (specs_succeeded_ > 0) {
			stm_
			  << colorizer_.green()
			  << "Passed: " << specs_succeeded_
			  << std::endl;
		}
		if (specs_failed_ > 0) {
			stm_
			  << colorizer_.red()
			  << "Failed: " << specs_failed_
			  << std::endl;
		}
		if (test_run_errors_.size() > 0) {
			stm_
			  << colorizer_.red()
			  << "Errors: " << test_run_errors_.size()
			  << std::endl;
		}
		stm_
		  << colorizer_.reset()
		  << std::endl;
	}

	void test_run_complete()
	{
		progress_reporter::test_run_complete();
		stm_ << std::endl;
		list_failures_and_errors();
		summary();
		stm_.flush();
	}

	void test_run_error(const char *desc, const struct test_run_error &err)
	{
		progress_reporter::test_run_error(desc, err);

		std::stringstream ss;
		ss << "Failed to run \"" << current_context_name() << "\": error \"" << err.what() << "\"" << std::endl;
		test_run_errors_.push_back(ss.str());
	}

	virtual void context_starting(const char *desc)
	{
		progress_reporter::context_starting(desc);
		context_stack_.emplace(desc);
		if (context_stack_.size() == 1) {
			output_context_start_message();
		} else {
			++not_yet_shown_;
		}
	}

	void output_context_start_message()
	{
		stm_
		  << indent()
		  << colorizer_.blue()
		  << "begin "
		  << colorizer_.white()
		  << context_stack_.top().desc
		  << colorizer_.reset()
		  << std::endl;
		++indentation_;
		stm_.flush();
	}

	void output_not_yet_shown_context_start_messages()
	{
		std::stack<context_info> temp_stack;
		for (int i = 0; i < not_yet_shown_; ++i) {
			temp_stack.push(context_stack_.top());
			context_stack_.pop();
		}
		for (int i = 0; i < not_yet_shown_; ++i) {
			context_stack_.push(temp_stack.top());
			output_context_start_message();
			temp_stack.pop();
		}
		not_yet_shown_ = 0;
	}

	virtual void context_ended(const char *desc)
	{
		progress_reporter::context_ended(desc);
		if (context_stack_.size() == 1
		 || context_stack_.top().total > context_stack_.top().skipped) {
			output_context_end_message();
		}
		const context_info context = context_stack_.top(); // copy
		context_stack_.pop();
		if (!context_stack_.empty()) {
			context_stack_.top().merge(context);
		}
		if (not_yet_shown_ > 0) {
			--not_yet_shown_;
		}
	}

	void output_context_end_message()
	{
		const context_info &context = context_stack_.top();
		--indentation_;
		stm_
		  << indent()
		  << colorizer_.blue()
		  << "end "
		  << colorizer_.reset()
		  << context.desc;
		if (context.total > 0) {
			stm_
			  << colorizer_.white()
			  << " " << context.total << " total";
		}
		if (context.skipped > 0) {
			stm_
			  << colorizer_.yellow()
			  << " " << context.skipped << " skipped";
		}
		if (context.failed > 0) {
			stm_
			  << colorizer_.red()
			  << " " << context.failed << " failed";
		}
		stm_ << colorizer_.reset() << std::endl;
	}

	virtual void it_skip(const char *desc)
	{
		progress_reporter::it_skip(desc);
		++context_stack_.top().total;
		++context_stack_.top().skipped;
	}

	virtual void it_starting(const char *desc)
	{
		if (context_stack_.size() > 1
		 && context_stack_.top().total == context_stack_.top().skipped) {
			output_not_yet_shown_context_start_messages();
		}

		progress_reporter::it_starting(desc);
		stm_
		  << indent()
		  << colorizer_.yellow()
		  << "[ TEST ]"
		  << colorizer_.reset()
		  << " it " << desc;
		++indentation_;
		stm_.flush();
	}

	virtual void it_succeeded(const char *desc)
	{
		progress_reporter::it_succeeded(desc);
		++context_stack_.top().total;
		--indentation_;
		stm_
		  << "\r" << indent()
		  << colorizer_.green()
		  << "[ PASS ]"
		  << colorizer_.reset()
		  << " it " << desc
		  << std::endl;
		stm_.flush();
	}

	virtual void it_failed(const char *desc, const assertion_exception &ex)
	{
		++specs_failed_;

		std::stringstream ss;
		ss << current_context_name() << " " << desc << ":" << std::endl << failure_formatter_.format(ex);
		failures_.push_back(ss.str());

		++context_stack_.top().total;
		++context_stack_.top().failed;
		--indentation_;
		stm_
		  << "\r" << indent()
		  << colorizer_.red()
		  << "[ FAIL ]"
		  << colorizer_.reset()
		  << " it " << desc
		  << std::endl;
		stm_.flush();
	}

	virtual void it_unknown_error(const char *desc)
	{
		++specs_failed_;

		std::stringstream ss;
		ss << current_context_name() << " " << desc << ": Unknown exception" << std::endl;
		failures_.push_back(ss.str());

		++context_stack_.top().total;
		++context_stack_.top().failed;
		--indentation_;
		stm_
		  << "\r" << indent()
		  << colorizer_.red()
		  << "-ERROR->"
		  << colorizer_.reset()
		  << " it " << desc
		  << std::endl;
		stm_.flush();
	}

	bool did_we_pass() const
	{
		return
		  specs_failed_ == 0 &&
		  test_run_errors_.size() == 0;
	}

private:
	std::string indent()
	{
		return std::string(2*indentation_, ' ');
	}

	std::ostream &stm_;
	const detail::colorizer &colorizer_;
	int indentation_;
	int not_yet_shown_; // number of elements in stack that are not yet shown
	std::stack<context_info> context_stack_;
};
}
}

#endif