// -*- c++ -*-
// Distributed under the BSD 2-Clause License.
// See accompanying file LICENSE for details.
#include "util.h"

#include <iostream>
#include <fstream>
#include <algorithm>

std::string to_lower(const std::string& str)
{
	std::string out{str};
	std::transform(out.begin(), out.end(), out.begin(), ::tolower);
	return out;
}

std::string readFile(const std::string& fileName)
{
	std::ifstream ifs(fileName.c_str(),
	                  std::ios::in | std::ios::binary | std::ios::ate);

	std::ifstream::pos_type fileSize = ifs.tellg();
	ifs.seekg(0, std::ios::beg);

	std::vector<char> bytes(fileSize);
	ifs.read(bytes.data(), fileSize);

	return std::string(bytes.data(), fileSize);
}

std::vector<std::string> readDeps(const std::string& depFile)
{
	if(!std::filesystem::exists(depFile))
	{
		return {};
	}

	auto str = readFile(depFile);

	std::vector<std::string> output;
	std::string tmp;
	bool start{false};
	bool in_whitespace{false};
	for(const auto& c : str)
	{
		if(c == '\\' || c == '\n')
		{
			continue;
		}

		if(c == ':')
		{
			start = true;
			continue;
		}

		if(!start)
		{
			continue;
		}

		if(c == ' ' || c == '\t')
		{
			if(in_whitespace)
			{
				continue;
			}

			if(!tmp.empty())
			{
				output.push_back(tmp);
			}
			tmp.clear();
			in_whitespace = true;
		}
		else
		{
			in_whitespace = false;
			tmp += c;
		}
	}

	if(!tmp.empty())
	{
		output.push_back(tmp);
	}

	return output;
}

ctor::language languageFromExtension(const std::filesystem::path& file)
{
	auto ext = file.extension().string();

	// First a few case sensitive comparisons
	if(ext == ".c")
	{
		return ctor::language::c;
	}

	if(ext == ".C")
	{
		return ctor::language::cpp;
	}

	// The rest are compared in lowercase
	ext = to_lower(ext);

	if(ext == ".cc" ||
	   ext == ".cpp" ||
	   ext == ".c++" ||
	   ext == ".cp" ||
	   ext == ".cxx")
	{
		return ctor::language::cpp;
	}

	if(ext == ".s" ||
	   ext == ".asm")
	{
		return ctor::language::assembler;
	}

	std::cerr << "Could not deduce language from " << file.string() << "\n";
	exit(1);
	return {};
}

namespace
{
bool isClean(char c)
{
	return c != '.' && c != '/';
}
}

std::string cleanUp(const std::string& path)
{
	std::string cleaned;
	for(const auto& c : path)
	{
		if(isClean(c))
		{
			cleaned += c;
		}
		else
		{
			cleaned += '_';
		}
	}
	return cleaned;
}

std::string esc(const std::string& in)
{
	std::string out;
	for(auto c : in)
	{
		switch(c)
		{
		case '\\': out += "\\\\"; break;
		case '"': out += "\\\""; break;
		default:
			out += c;
			break;
		}
	}
	return out;
}

std::vector<std::string> get_paths(const std::string& path_env)
{
	std::vector<std::string> paths;

#ifdef _WIN32
	const char sep{';'};
#else
	const char sep{':'};
#endif

	std::stringstream ss(path_env);
	std::string path;
	while (std::getline(ss, path, sep))
	{
		paths.push_back(path);
	}

	return paths;
}

bool check_executable(const std::filesystem::path& prog)
{
	auto perms = std::filesystem::status(prog).permissions();

	if((perms & std::filesystem::perms::owner_exec) != std::filesystem::perms::none)
	{
		return true;
	}

	if((perms & std::filesystem::perms::group_exec) != std::filesystem::perms::none)
	{
		return true;
	}

	if((perms & std::filesystem::perms::others_exec) != std::filesystem::perms::none)
	{
		return true;
	}

	return false;
}

std::string locate(const std::string& prog,
                   const std::vector<std::string>& paths,
                   const std::string& arch)
{
	std::string program = prog;
	if(!arch.empty())
	{
		program = arch + "-" + prog;
	}

	// first check if arch contains an absolute path to prog
	if(std::filesystem::exists(program))
	{
		if(check_executable(program))
		{
			return program;
		}
	}

	for(const auto& path_str : paths)
	{
		std::filesystem::path path(path_str);
		auto prog_path = path / program;
		if(std::filesystem::exists(prog_path))
		{
			if(check_executable(prog_path))
			{
				return prog_path.string();
			}
		}
	}

	return {};
}