/*
 * Copyright (C) the libgit2 contributors. All rights reserved.
 *
 * This file is part of libgit2, distributed under the GNU GPL v2 with
 * a Linking Exception. For full terms see the included COPYING file.
 */

#include "config.h"

#include "config_backend.h"
#include "config_parse.h"
#include "config_entries.h"

typedef struct {
	git_config_backend parent;
	git_config_entries *entries;
	git_buf cfg;
} config_memory_backend;

typedef struct {
	git_config_entries *entries;
	git_config_level_t level;
} config_memory_parse_data;

static int config_error_readonly(void)
{
	git_error_set(GIT_ERROR_CONFIG, "this backend is read-only");
	return -1;
}

static int read_variable_cb(
	git_config_parser *reader,
	const char *current_section,
	const char *var_name,
	const char *var_value,
	const char *line,
	size_t line_len,
	void *payload)
{
	config_memory_parse_data *parse_data = (config_memory_parse_data *) payload;
	git_buf buf = GIT_BUF_INIT;
	git_config_entry *entry;
	const char *c;
	int result;

	GIT_UNUSED(reader);
	GIT_UNUSED(line);
	GIT_UNUSED(line_len);

	if (current_section) {
		/* TODO: Once warnings land, we should likely warn
		 * here. Git appears to warn in most cases if it sees
		 * un-namespaced config options.
		 */
		git_buf_puts(&buf, current_section);
		git_buf_putc(&buf, '.');
	}

	for (c = var_name; *c; c++)
		git_buf_putc(&buf, git__tolower(*c));

	if (git_buf_oom(&buf))
		return -1;

	entry = git__calloc(1, sizeof(git_config_entry));
	GIT_ERROR_CHECK_ALLOC(entry);
	entry->name = git_buf_detach(&buf);
	entry->value = var_value ? git__strdup(var_value) : NULL;
	entry->level = parse_data->level;
	entry->include_depth = 0;

	if ((result = git_config_entries_append(parse_data->entries, entry)) < 0)
		return result;

	return result;
}

static int config_memory_open(git_config_backend *backend, git_config_level_t level, const git_repository *repo)
{
	config_memory_backend *memory_backend = (config_memory_backend *) backend;
	git_config_parser parser = GIT_PARSE_CTX_INIT;
	config_memory_parse_data parse_data;
	int error;

	GIT_UNUSED(repo);

	if ((error = git_config_parser_init(&parser, "in-memory", memory_backend->cfg.ptr,
					    memory_backend->cfg.size)) < 0)
		goto out;
	parse_data.entries = memory_backend->entries;
	parse_data.level = level;

	if ((error = git_config_parse(&parser, NULL, read_variable_cb, NULL, NULL, &parse_data)) < 0)
		goto out;

out:
	git_config_parser_dispose(&parser);
	return error;
}

static int config_memory_get(git_config_backend *backend, const char *key, git_config_entry **out)
{
	config_memory_backend *memory_backend = (config_memory_backend *) backend;
	return git_config_entries_get(out, memory_backend->entries, key);
}

static int config_memory_iterator(
	git_config_iterator **iter,
	git_config_backend *backend)
{
	config_memory_backend *memory_backend = (config_memory_backend *) backend;
	git_config_entries *entries;
	int error;

	if ((error = git_config_entries_dup(&entries, memory_backend->entries)) < 0)
		goto out;

	if ((error = git_config_entries_iterator_new(iter, entries)) < 0)
		goto out;

out:
	/* Let iterator delete duplicated entries when it's done */
	git_config_entries_free(entries);
	return error;
}

static int config_memory_set(git_config_backend *backend, const char *name, const char *value)
{
	GIT_UNUSED(backend);
	GIT_UNUSED(name);
	GIT_UNUSED(value);
	return config_error_readonly();
}

static int config_memory_set_multivar(
	git_config_backend *backend, const char *name, const char *regexp, const char *value)
{
	GIT_UNUSED(backend);
	GIT_UNUSED(name);
	GIT_UNUSED(regexp);
	GIT_UNUSED(value);
	return config_error_readonly();
}

static int config_memory_delete(git_config_backend *backend, const char *name)
{
	GIT_UNUSED(backend);
	GIT_UNUSED(name);
	return config_error_readonly();
}

static int config_memory_delete_multivar(git_config_backend *backend, const char *name, const char *regexp)
{
	GIT_UNUSED(backend);
	GIT_UNUSED(name);
	GIT_UNUSED(regexp);
	return config_error_readonly();
}

static int config_memory_lock(git_config_backend *backend)
{
	GIT_UNUSED(backend);
	return config_error_readonly();
}

static int config_memory_unlock(git_config_backend *backend, int success)
{
	GIT_UNUSED(backend);
	GIT_UNUSED(success);
	return config_error_readonly();
}

static int config_memory_snapshot(git_config_backend **out, git_config_backend *backend)
{
	GIT_UNUSED(out);
	GIT_UNUSED(backend);
	git_error_set(GIT_ERROR_CONFIG, "this backend does not support snapshots");
	return -1;
}

static void config_memory_free(git_config_backend *_backend)
{
	config_memory_backend *backend = (config_memory_backend *)_backend;

	if (backend == NULL)
		return;

	git_config_entries_free(backend->entries);
	git_buf_dispose(&backend->cfg);
	git__free(backend);
}

int git_config_backend_from_string(git_config_backend **out, const char *cfg, size_t len)
{
	config_memory_backend *backend;

	backend = git__calloc(1, sizeof(config_memory_backend));
	GIT_ERROR_CHECK_ALLOC(backend);

	if (git_config_entries_new(&backend->entries) < 0) {
		git__free(backend);
		return -1;
	}

	if (git_buf_set(&backend->cfg, cfg, len) < 0) {
		git_config_entries_free(backend->entries);
		git__free(backend);
		return -1;
	}

	backend->parent.version = GIT_CONFIG_BACKEND_VERSION;
	backend->parent.readonly = 1;
	backend->parent.open = config_memory_open;
	backend->parent.get = config_memory_get;
	backend->parent.set = config_memory_set;
	backend->parent.set_multivar = config_memory_set_multivar;
	backend->parent.del = config_memory_delete;
	backend->parent.del_multivar = config_memory_delete_multivar;
	backend->parent.iterator = config_memory_iterator;
	backend->parent.lock = config_memory_lock;
	backend->parent.unlock = config_memory_unlock;
	backend->parent.snapshot = config_memory_snapshot;
	backend->parent.free = config_memory_free;

	*out = (git_config_backend *)backend;

	return 0;
}
