/*
 * Copyright (C) 2002-2016 by the Widelands Development Team
 *
 * 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 Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */

#ifndef WL_PROFILE_PROFILE_H
#define WL_PROFILE_PROFILE_H

#include <cstring>
#include <memory>
#include <string>
#include <vector>

#include "base/macros.h"
#include "base/point.h"
#include "io/filesystem/layered_filesystem.h"
// TODO(unknown): as soon as g_fs is not needed anymore,
// include "filesystem.h" instead of layered_filesystem.h.

extern class Profile g_options;
class FileSystem;

/**
 * Represents one section inside the .ini-style file, basically as a list of
 * key-value pairs.
 *
 * get_string:
 * Returns the value of the first key with the given name. If the key isn't
 * found, def is returned.
 *
 * get_int, get_bool, convert the value string to the desired type.
 *
 * get_safe_*:
 * Like above, but throw an exception if the key doesn't exist.
 *
 * get_next_*:
 * Returns the name of the next key of the given name. You can pass name == 0 to
 * retrieve any unused key. Value::used is used to determine which key is next.
 * The value of the key is stored in the second parameter.
 */
class Section {
public:
	friend class Profile;

	struct Value {
		using string = std::string;

		Value(const string& name, const char* const value);
		Value(const Value&);
		Value(Value&& other);

		// destructor would be empty

		Value& operator=(Value);
		Value& operator=(Value&& other);

		char const* get_name() const {
			return name_.c_str();
		}

		bool is_used() const;
		void mark_used();

		int32_t get_int() const;
		uint32_t get_natural() const;
		uint32_t get_positive() const;
		bool get_bool() const;
		char const* get_string() const {
			return value_.get();
		}
		char* get_string() {
			return value_.get();
		}
		Point get_point() const;

		void set_string(char const*);

		friend void swap(Value& first, Value& second);

	private:
		bool used_;
		string name_;
		std::unique_ptr<char[]> value_;

		Value() = default;
	};

	using ValueList = std::vector<Value>;

	Section(Profile*, const std::string& name);

	/// \returns whether a value with the given name exists.
	/// Does not mark the value as used.
	bool has_val(char const* name) const;

	Value* get_val(char const* name);
	Value* get_next_val(char const* name = nullptr);
	uint32_t get_num_values() const {
		return values_.size();
	}

	char const* get_name() const;
	void set_name(const std::string&);

	bool is_used() const;
	void mark_used();

	void check_used() const;

	int32_t get_int(char const* name, int32_t def = 0);
	uint32_t get_natural(char const* name, uint32_t def = 0);
	uint32_t get_positive(char const* name, uint32_t def = 1);
	bool get_bool(char const* name, bool def = false);
	const char* get_string(char const* name, char const* def = nullptr);
	Point get_point(char const* name, Point def = Point(0, 0));

	int32_t get_safe_int(const char* name);
	uint32_t get_safe_natural(char const* name);
	uint32_t get_safe_positive(char const* name);
	bool get_safe_bool(const char* name);
	const char* get_safe_string(const char* name);
	const char* get_safe_string(const std::string& name);

	char const* get_next_bool(char const* name, bool* value);

	void set_int(char const* name, int32_t value);
	void set_bool(char const* const name, bool const value) {
		set_string(name, value ? "true" : "false");
	}
	void set_string(char const* name, char const* value);
	void set_string_duplicate(char const* name, char const* value);
	void set_string(char const* const name, const std::string& value) {
		set_string(name, value.c_str());
	}
	void set_string_duplicate(char const* const name, const std::string& value) {
		set_string_duplicate(name, value.c_str());
	}

	/// If a Value with this name already exists, update it with the given
	/// value. Otherwise create a new Value with the given name and value.
	Value& create_val(char const* name, char const* value);

	/// Create a new Value with the given name and value.
	Value& create_val_duplicate(char const* name, char const* value);

private:
	Profile* profile_;
	bool used_;
	std::string section_name_;
	ValueList values_;
};

/**
 * Parses an .ini-style file into sections and key-value pairs.
 * The destructor prints out warnings if a section or value hasn't been used.
 *
 * get_section:
 * Returns the first section of the given name (or 0 if none exist).
 *
 * get_safe_section:
 * Like get_section, but throws an exception if the section doesn't exist.
 *
 * get_next_section:
 * Returns the next unused section of the given name, or 0 if all sections
 * have been used. name can be 0 to retrieve any remaining sections.
 */
class Profile {
public:
	enum { err_ignore = 0, err_log, err_throw };

	Profile(int32_t error_level = err_throw);
	Profile(char const* filename,
	        char const* global_section = nullptr,
	        int32_t error_level = err_throw);
	Profile(char const* filename,
	        char const* global_section,
	        const std::string& textdomain,
	        int32_t error_level = err_throw);

	void error(char const*, ...) const __attribute__((format(printf, 2, 3)));
	void check_used() const;

	void read(const char* const filename,
	          const char* const global_section = nullptr,
	          FileSystem& = *g_fs);
	void write(const char* const filename, bool used_only = true, FileSystem& = *g_fs);

	Section* get_section(const std::string& name);
	Section& get_safe_section(const std::string& name);
	Section& pull_section(char const* name);
	Section* get_next_section(char const* name = nullptr);

	/// If a section with the given name already exists, return a reference to
	/// it. Otherwise create a new section with the given name and return a
	/// reference to it.
	Section& create_section(char const* name);

	/// Create a new section with the given name and return a reference to it.
	Section& create_section_duplicate(char const* name);

private:
	using SectionList = std::vector<Section>;

	std::string filename_;
	SectionList sections_;
	int32_t error_level_;

	DISALLOW_COPY_AND_ASSIGN(Profile);
};

#endif  // end of include guard: WL_PROFILE_PROFILE_H
