/*
 * Copyright 2014 Andrew Ayer
 *
 * This file is part of git-crypt.
 *
 * git-crypt 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 3 of the License, or
 * (at your option) any later version.
 *
 * git-crypt 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 git-crypt.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Additional permission under GNU GPL version 3 section 7:
 *
 * If you modify the Program, or any covered work, by linking or
 * combining it with the OpenSSL project's OpenSSL library (or a
 * modified version of that library), containing parts covered by the
 * terms of the OpenSSL or SSLeay licenses, the licensors of the Program
 * grant you additional permission to convey the resulting work.
 * Corresponding Source for a non-source form of such a combination
 * shall include the source code for the parts of OpenSSL used as well
 * as that of the covered work.
 */

#include "gpg.hpp"
#include "util.hpp"
#include "commands.hpp"
#include <sstream>

static std::string gpg_get_executable()
{
	std::string gpgbin = "gpg";
	try {
		gpgbin = get_git_config("gpg.program");
	} catch (...) {
	}
	return gpgbin;
}
static std::string gpg_nth_column (const std::string& line, unsigned int col)
{
	std::string::size_type	pos = 0;

	for (unsigned int i = 0; i < col; ++i) {
		pos = line.find_first_of(':', pos);
		if (pos == std::string::npos) {
			throw Gpg_error("Malformed output from gpg");
		}
		pos = pos + 1;
	}

	const std::string::size_type	end_pos = line.find_first_of(':', pos);

	return end_pos != std::string::npos ?
	       line.substr(pos, end_pos - pos) :
	       line.substr(pos);
}

// given a key fingerprint, return the last 8 nibbles
std::string gpg_shorten_fingerprint (const std::string& fingerprint)
{
	return fingerprint.size() == 40 ? fingerprint.substr(32) : fingerprint;
}

// given a key fingerprint, return the key's UID (e.g. "John Smith <jsmith@example.com>")
std::string gpg_get_uid (const std::string& fingerprint)
{
	// gpg --batch --with-colons --fixed-list-mode --list-keys 0x7A399B2DB06D039020CD1CE1D0F3702D61489532
	std::vector<std::string>	command;
	command.push_back(gpg_get_executable());
	command.push_back("--batch");
	command.push_back("--with-colons");
	command.push_back("--fixed-list-mode");
	command.push_back("--list-keys");
	command.push_back("0x" + fingerprint);
	std::stringstream		command_output;
	if (!successful_exit(exec_command(command, command_output))) {
		// This could happen if the keyring does not contain a public key with this fingerprint
		return "";
	}

	while (command_output.peek() != -1) {
		std::string		line;
		std::getline(command_output, line);
		if (line.substr(0, 4) == "uid:") {
			// uid:u::::1395975462::AB97D6E3E5D8789988CA55E5F77D9E7397D05229::John Smith <jsmith@example.com>:
			// want the 9th column (counting from 0)
			return gpg_nth_column(line, 9);
		}
	}
	
	return "";
}

// return a list of fingerprints of public keys matching the given search query (such as jsmith@example.com)
std::vector<std::string> gpg_lookup_key (const std::string& query)
{
	std::vector<std::string>	fingerprints;

	// gpg --batch --with-colons --fingerprint --list-keys jsmith@example.com
	std::vector<std::string>	command;
	command.push_back(gpg_get_executable());
	command.push_back("--batch");
	command.push_back("--with-colons");
	command.push_back("--fingerprint");
	command.push_back("--list-keys");
	command.push_back(query);
	std::stringstream		command_output;
	if (successful_exit(exec_command(command, command_output))) {
		bool			is_pubkey = false;
		while (command_output.peek() != -1) {
			std::string		line;
			std::getline(command_output, line);
			if (line.substr(0, 4) == "pub:") {
				is_pubkey = true;
			} else if (line.substr(0, 4) == "sub:") {
				is_pubkey = false;
			} else if (is_pubkey && line.substr(0, 4) == "fpr:") {
				// fpr:::::::::7A399B2DB06D039020CD1CE1D0F3702D61489532:
				// want the 9th column (counting from 0)
				fingerprints.push_back(gpg_nth_column(line, 9));
			}
		}
	}
	
	return fingerprints;
}

std::vector<std::string> gpg_list_secret_keys ()
{
	// gpg --batch --with-colons --list-secret-keys --fingerprint
	std::vector<std::string>	command;
	command.push_back(gpg_get_executable());
	command.push_back("--batch");
	command.push_back("--with-colons");
	command.push_back("--list-secret-keys");
	command.push_back("--fingerprint");
	std::stringstream		command_output;
	if (!successful_exit(exec_command(command, command_output))) {
		throw Gpg_error("gpg --list-secret-keys failed");
	}

	std::vector<std::string>	secret_keys;

	while (command_output.peek() != -1) {
		std::string		line;
		std::getline(command_output, line);
		if (line.substr(0, 4) == "fpr:") {
			// fpr:::::::::7A399B2DB06D039020CD1CE1D0F3702D61489532:
			// want the 9th column (counting from 0)
			secret_keys.push_back(gpg_nth_column(line, 9));
		}
	}
	
	return secret_keys;
}

void gpg_encrypt_to_file (const std::string& filename, const std::string& recipient_fingerprint, bool key_is_trusted, const char* p, size_t len)
{
	// gpg --batch -o FILENAME -r RECIPIENT -e
	std::vector<std::string>	command;
	command.push_back(gpg_get_executable());
	command.push_back("--batch");
	if (key_is_trusted) {
		command.push_back("--trust-model");
		command.push_back("always");
	}
	command.push_back("-o");
	command.push_back(filename);
	command.push_back("-r");
	command.push_back("0x" + recipient_fingerprint);
	command.push_back("-e");
	if (!successful_exit(exec_command_with_input(command, p, len))) {
		throw Gpg_error("Failed to encrypt");
	}
}

void gpg_decrypt_from_file (const std::string& filename, std::ostream& output)
{
	// gpg -q -d FILENAME
	std::vector<std::string>	command;
	command.push_back(gpg_get_executable());
	command.push_back("-q");
	command.push_back("-d");
	command.push_back(filename);
	if (!successful_exit(exec_command(command, output))) {
		throw Gpg_error("Failed to decrypt");
	}
}

