/******************************************************************************
 * Icinga 2                                                                   *
 * Copyright (C) 2012-2016 Icinga Development Team (https://www.icinga.org/)  *
 *                                                                            *
 * This program is free software; you can redistribute it and/or              *
 * modify it under the terms of the GNU General Public License                *
 * as published by the Free Software Foundation; either version 2             *
 * of the License, or (at your option) any later version.                     *
 *                                                                            *
 * This program is distributed in the hope that it will be useful,            *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
 * GNU General Public License for more details.                               *
 *                                                                            *
 * You should have received a copy of the GNU General Public License          *
 * along with this program; if not, write to the Free Software Foundation     *
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA.             *
 ******************************************************************************/

#include "cli/featureutility.hpp"
#include "base/logger.hpp"
#include "base/console.hpp"
#include "base/application.hpp"
#include <boost/foreach.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <fstream>
#include <iostream>

using namespace icinga;

String FeatureUtility::GetFeaturesAvailablePath(void)
{
	return Application::GetSysconfDir() + "/icinga2/features-available";
}

String FeatureUtility::GetFeaturesEnabledPath(void)
{
	return Application::GetSysconfDir() + "/icinga2/features-enabled";
}

std::vector<String> FeatureUtility::GetFieldCompletionSuggestions(const String& word, bool enable)
{
	std::vector<String> cache;
	std::vector<String> suggestions;

	GetFeatures(cache, enable);

	std::sort(cache.begin(), cache.end());

	BOOST_FOREACH(const String& suggestion, cache) {
		if (suggestion.Find(word) == 0)
			suggestions.push_back(suggestion);
	}

	return suggestions;
}

int FeatureUtility::EnableFeatures(const std::vector<std::string>& features)
{
	String features_available_dir = GetFeaturesAvailablePath();
	String features_enabled_dir = GetFeaturesEnabledPath();

	if (!Utility::PathExists(features_available_dir) ) {
		Log(LogCritical, "cli")
		    << "Cannot parse available features. Path '" << features_available_dir << "' does not exist.";
		return 1;
	}

	if (!Utility::PathExists(features_enabled_dir) ) {
		Log(LogCritical, "cli")
		    << "Cannot enable features. Path '" << features_enabled_dir << "' does not exist.";
		return 1;
	}

	std::vector<std::string> errors;

	BOOST_FOREACH(const String& feature, features) {
		String source = features_available_dir + "/" + feature + ".conf";

		if (!Utility::PathExists(source) ) {
			Log(LogCritical, "cli")
			    << "Cannot enable feature '" << feature << "'. Source file '" << source + "' does not exist.";
			errors.push_back(feature);
			continue;
		}

		String target = features_enabled_dir + "/" + feature + ".conf";

		if (Utility::PathExists(target) ) {
			Log(LogWarning, "cli")
			    << "Feature '" << feature << "' already enabled.";
			continue;
		}

		std::cout << "Enabling feature " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << feature
		    << ConsoleColorTag(Console_Normal) << ". Make sure to restart Icinga 2 for these changes to take effect.\n";

#ifndef _WIN32
		String relativeSource = "../features-available/" + feature + ".conf";

		if (symlink(relativeSource.CStr(), target.CStr()) < 0) {
			Log(LogCritical, "cli")
			    << "Cannot enable feature '" << feature << "'. Linking source '" << relativeSource << "' to target file '" << target
			    << "' failed with error code " << errno << ", \"" << Utility::FormatErrorNumber(errno) << "\".";
			errors.push_back(feature);
			continue;
		}
#else /* _WIN32 */
		std::ofstream fp;
		fp.open(target.CStr());
		fp << "include \"../features-available/" << feature << ".conf\"" << std::endl;
		fp.close();

		if (fp.fail()) {
			Log(LogCritical, "cli")
			    << "Cannot enable feature '" << feature << "'. Failed to open file '" << target << "'.";
			errors.push_back(feature);
			continue;
		}
#endif /* _WIN32 */
	}

	if (!errors.empty()) {
		Log(LogCritical, "cli")
		    << "Cannot enable feature(s): " << boost::algorithm::join(errors, " ");
		errors.clear();
		return 1;
	}

	return 0;
}

int FeatureUtility::DisableFeatures(const std::vector<std::string>& features)
{
	String features_enabled_dir = GetFeaturesEnabledPath();

	if (!Utility::PathExists(features_enabled_dir) ) {
		Log(LogCritical, "cli")
		    << "Cannot disable features. Path '" << features_enabled_dir << "' does not exist.";
		return 0;
	}

	std::vector<std::string> errors;

	BOOST_FOREACH(const String& feature, features) {
		String target = features_enabled_dir + "/" + feature + ".conf";

		if (!Utility::PathExists(target) ) {
			Log(LogCritical, "cli")
			    << "Cannot disable feature '" << feature << "'. Target file '" << target << "' does not exist.";
			errors.push_back(feature);
			continue;
		}

		if (unlink(target.CStr()) < 0) {
			Log(LogCritical, "cli")
			    << "Cannot disable feature '" << feature << "'. Unlinking target file '" << target
			    << "' failed with error code " << errno << ", \"" + Utility::FormatErrorNumber(errno) << "\".";
			errors.push_back(feature);
			continue;
		}

		std::cout << "Disabling feature " << ConsoleColorTag(Console_ForegroundMagenta | Console_Bold) << feature
		    << ConsoleColorTag(Console_Normal) << ". Make sure to restart Icinga 2 for these changes to take effect.\n";
	}

	if (!errors.empty()) {
		Log(LogCritical, "cli")
		    << "Cannot disable feature(s): " << boost::algorithm::join(errors, " ");
		errors.clear();
		return 1;
	}

	return 0;
}

int FeatureUtility::ListFeatures(std::ostream& os)
{
	std::vector<String> disabled_features;
	std::vector<String> enabled_features;

	if (!FeatureUtility::GetFeatures(disabled_features, true))
		return 1;

	os << ConsoleColorTag(Console_ForegroundRed | Console_Bold) << "Disabled features: " << ConsoleColorTag(Console_Normal)
	    << boost::algorithm::join(disabled_features, " ") << "\n";

	if (!FeatureUtility::GetFeatures(enabled_features, false))
		return 1;

	os << ConsoleColorTag(Console_ForegroundGreen | Console_Bold) << "Enabled features: " << ConsoleColorTag(Console_Normal)
	    << boost::algorithm::join(enabled_features, " ") << "\n";

	return 0;
}

bool FeatureUtility::GetFeatures(std::vector<String>& features, bool get_disabled)
{
	/* request all disabled features */
	if (get_disabled) {
		/* disable = available-enabled */
		String available_pattern = GetFeaturesAvailablePath() + "/*.conf";
		std::vector<String> available;
		Utility::Glob(available_pattern, boost::bind(&FeatureUtility::CollectFeatures, _1, boost::ref(available)), GlobFile);

		String enabled_pattern = GetFeaturesEnabledPath() + "/*.conf";
		std::vector<String> enabled;
		Utility::Glob(enabled_pattern, boost::bind(&FeatureUtility::CollectFeatures, _1, boost::ref(enabled)), GlobFile);

		std::sort(available.begin(), available.end());
		std::sort(enabled.begin(), enabled.end());
		std::set_difference(
			available.begin(), available.end(),
			enabled.begin(), enabled.end(),
			std::back_inserter(features)
		);
	} else {
		/* all enabled features */
		String enabled_pattern = GetFeaturesEnabledPath() + "/*.conf";

		Utility::Glob(enabled_pattern, boost::bind(&FeatureUtility::CollectFeatures, _1, boost::ref(features)), GlobFile);
	}

	return true;
}

bool FeatureUtility::CheckFeatureEnabled(const String& feature)
{
	return CheckFeatureInternal(feature, false);
}

bool FeatureUtility::CheckFeatureDisabled(const String& feature)
{
	return CheckFeatureInternal(feature, true);
}

bool FeatureUtility::CheckFeatureInternal(const String& feature, bool check_disabled)
{
	std::vector<String> features;

	if (!FeatureUtility::GetFeatures(features, check_disabled))
		return false;

	BOOST_FOREACH(const String& check_feature, features) {
		if (check_feature == feature)
			return true;
	}

	return false;
}

void FeatureUtility::CollectFeatures(const String& feature_file, std::vector<String>& features)
{
	String feature = Utility::BaseName(feature_file);
	boost::algorithm::replace_all(feature, ".conf", "");

	Log(LogDebug, "cli")
	    << "Adding feature: " << feature;
	features.push_back(feature);
}
