EnvPtr Env::InitializeGlobalEnv()
{
	EnvPtr global = make_shared<Env>();

	global->AddProcedure("+", [] (const List& lst) -> LispData {
		auto summator = [] (const LispData& a, const DataPtr b) -> LispData {
			if (a.GetType() == integer_type && b->GetType() == integer_type) {
				return LispData(a.GetInt() + b->GetInt());
			}
			else {
				return LispData(a.GetFloat() + b->GetFloat());
			}
		};
		return accumulate(begin(lst), end(lst), LispData(0L), summator);
	});

	global->AddProcedure("-", [] (const List& lst) -> LispData {
		if (lst.size() != 2) {
			throw WrongArity("-");
		}
		LispData a = *lst.front();
		LispData b = *lst.back();
		if (a.GetType() == integer_type && b.GetType() == integer_type) {
			return LispData(a.GetInt() - b.GetInt());
		}
		else {
			return LispData(a.GetFloat() - b.GetFloat());
		}
	});

	global->AddProcedure("*", [] (const List& lst) -> LispData {
		auto multiplicator = [] (const LispData& a, const DataPtr b) -> LispData {
			if (a.GetType() == integer_type && b->GetType() == integer_type) {
				return LispData(a.GetInt() * b->GetInt());
			}
			else {
				return LispData(a.GetFloat() * b->GetFloat());
			}
		};
		return accumulate(begin(lst), end(lst), LispData(1), multiplicator);
	});


	global->AddProcedure("/", [] (const List& lst) -> LispData {
		if (lst.size() != 2) {
			throw WrongArity("/");
		}
		LispData a = *lst.front();
		LispData b = *lst.back();
		if (fabs(b.GetFloat()) < 1e-15) {
			throw runtime_error("Zero division");
		}
		if (a.GetType() == integer_type && b.GetType() == integer_type) {
			return LispData(a.GetInt() / b.GetInt());
		}
		else {
			return LispData(a.GetFloat() / b.GetFloat());
		}
	});


	global->AddProcedure("=", [] (const List& lst) -> LispData {
		if (lst.size() != 2) {
			throw WrongArity("=");
		}
		LispData a = *lst.front();
		LispData b = *lst.back();
		return LispData(a.GetFloat() == b.GetFloat());
	});


	global->AddProcedure("cons", [] (const List& lst) -> LispData {
		if (lst.size() != 2) {
			throw WrongArity("cons");
		}
		return LispData(lst);
	});


	global->AddProcedure("car", [] (const List& lst) -> LispData {
		if (lst.size() != 1) {
			throw WrongArity("car");
		}
		if (lst.front()->GetType() != list_type) {
			throw runtime_error("Not pair type given to 'car'.");
		}
		return LispData(*lst.front()->GetListRef()->front());
	});


	global->AddProcedure("cdr", [] (const List& lst) -> LispData {
		if (lst.size() != 1) {
			throw WrongArity("cdr");
		}
		auto pair = lst.front();
		if (pair->GetType() != list_type || pair->GetListRef()->size() != 2) {
			throw runtime_error("Not pair type given to 'cdr'.");
		}
		return LispData(*pair->GetListRef()->back());
	});


	global->AddProcedure("null?", [] (const List& lst) -> LispData {
		if (lst.size() != 1) {
			throw WrongArity("null?");
		}
		auto pair = lst.front();
		if (pair->GetType() != list_type) {
			throw runtime_error("Not pair type given to 'null?'.");
		}
		return LispData(pair->GetListRef()->empty());
	});


	(*global)["nil"] = LispData(List());


	global->AddProcedure("exit", [] (const List&) -> LispData {
		exit(0);
	});

	return global;
}