//
// Copyright (C) 2005-2025 Claes Nästénn <pekdon@gmail.com>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

//
// Configuration file parser with file inclusion support and command
// output parsing support. The format being parsed:
//
// $var = "value"
// INCLUDE = "file to include"
//
// section = "name" {
//   key = "name" {
//     value = "$var"
//   }
// }
//

#ifndef _PEKWM_CFGPARSER_HH_
#define _PEKWM_CFGPARSER_HH_

#include "config.h"

#include "CfgParserKey.hh"
#include "CfgParserSource.hh"
#include "CfgParserVarExpander.hh"
#include "String.hh"

#include <vector>
#include <map>
#include <set>
#include <string>
#include <cstring>
#include <iostream>
#include <cstdlib>

//! @brief Helper class
class TimeFiles {
public:
	typedef std::vector<std::string> filev;
	typedef filev::iterator filev_it;

	TimeFiles() : mtime(0) {}

	time_t mtime;
	filev files;

	bool requireReload(const std::string &file);
	void clear() { files.clear(); mtime = 0; }
};


const size_t PARSE_BUF_SIZE = 1024;

struct CfgParserState {
	CfgParserState()
	{
		have_value = false;
		buf.reserve(PARSE_BUF_SIZE);
	}

	void reset()
	{
		buf = "";
		value = "";
		have_value = false;
	}

	bool have_value;
	std::string buf;
	std::string value;
};

/**
 * CfgParser options.
 */
class CfgParserOpt {
public:
	CfgParserOpt(const std::string& command_path)
		: _command_path(command_path),
		  _register_x_resource(false),
		  _os(nullptr)
	{
	}

	const std::string& commandPath() const { return _command_path; }
	void setCommandPath(const std::string& command_path) {
		_command_path = command_path;
	}

	bool registerXResource() const { return _register_x_resource; }
	void setRegisterXResource(bool register_x_resource) {
		_register_x_resource = register_x_resource;
	}
	const std::string &getEndEarlyKey() const { return _end_early_key; }
	void setEndEarlyKey(const std::string &key) { _end_early_key = key; }

	Os *getOs() const { return _os; }
	void setOs(Os *os) { _os = os; }

private:
	std::string _command_path;
	std::string _end_early_key;
	bool _register_x_resource;
	Os *_os;
};

//! @brief Configuration file parser.
class CfgParser {
public:
	typedef std::vector<CfgParserKey*>::const_iterator key_cit;

	//! @brief Entry in parsed data structure.
	class Entry {
	public:
		typedef std::vector<CfgParser::Entry*>::iterator entry_it;
		typedef std::vector<CfgParser::Entry*>::const_iterator
			entry_cit;

		Entry(const std::string &source_name, int line,
		      const std::string &name, const std::string &value,
		      CfgParser::Entry *section=0);
		Entry(const Entry &entry);
		~Entry(void);

		entry_cit begin(void) const { return _entries.begin(); }
		entry_cit end(void) const { return _entries.end(); }
		size_t size() const { return _entries.size(); }

		const std::string &getName(void) const;
		const std::string &getValue(void) const;
		int getLine(void) const;
		const std::string &getSourceName(void) const;

		Entry *addEntry(Entry *entry, bool overwrite=false);
		Entry *addEntry(const std::string &source_name, int line,
				const std::string &name,
				const std::string &value,
				CfgParser::Entry *section=0,
				bool overwrite=false);

		//! @brief Returns the sub section.
		Entry *getSection(void) { return _section; }
		Entry *setSection(Entry *section, bool overwrite=false);

		Entry *findEntry(const std::string &name,
				 bool include_sections=false,
				 const char *value=0) const;
		Entry *findSection(const std::string &name,
				   const char *value=0) const;
		void parseKeyValues(key_cit begin, key_cit end) const;

		void print(uint level = 0);
		void copyTreeInto(CfgParser::Entry *from, bool overwrite=false);

		//! @brief Matches Entry name agains op_rhs.
		bool operator==(const char *rhs) {
			return pekwm::ascii_ncase_equal(rhs, _name);
		}
		friend std::ostream &operator<<(std::ostream &stream,
						const CfgParser::Entry &entry);

	private:
		/** List of entries in section. */
		std::vector<CfgParser::Entry*> _entries;
		Entry *_section; /**< Sub-section of node. */

		std::string _name; /**< Name of node. */
		std::string _value; /**< Value of node. */

		int _line;
		std::string _source_name;
	};

	typedef std::map<std::string, CfgParser::Entry*> section_map;
	typedef section_map::iterator section_map_it;
	typedef section_map::const_iterator section_map_cit;

	CfgParser(const CfgParserOpt& opt);
	~CfgParser(void);

	TimeFiles getCfgFiles(void) const { return _cfg_files; }
	CfgParserOpt &getOpt() { return _opt; }

	/** Returns the root Entry node. */
	Entry *getEntryRoot(void) { return _root_entry; }
	/** Return true if data parsed included dynamic content such as
	    from COMMAND. */
	bool isDynamicContent() const { return _is_dynamic_content; }
	/** Return true if data parsing ended early due to end-early seciton
	    being found */
	bool isEndEarly() const { return _is_end_early; }

	void clear(bool realloc = true);
	bool parse(const std::string &src,
		   CfgParserSource::Type type = CfgParserSource::SOURCE_FILE,
		   bool overwrite = false);
	bool parse(CfgParserSource* source, bool overwrite = false);

	std::string getVar(const std::string& key) const {
		std::string val, error;
		_var_expander_mem->lookup(key, val, error);
		return val;
	}
	void setVar(const std::string& key, const std::string& val) {
		_var_expander_mem->define(key, val);
	}

protected:
	bool parseVarName(const std::string& line,
			  std::string::size_type &begin,
			  std::string::size_type &end,
			  std::string& var);

private:
	CfgParser(const CfgParser&);
	CfgParser& operator=(const CfgParser&);

	bool parse();
	void parseSourceNew(const std::string &name,
			    CfgParserSource::Type type);
	void parseSource(CfgParserSource *source, CfgParserState &ps);
	bool parseName(std::string &buf);
	bool parseValue(std::string &value);
	void parseEntryFinish(CfgParserState &ps);
	void parseEntryFinishStandard(CfgParserState &ps);
	void parseEntryFinishTemplate(std::string &name);
	void parseCloseCurlyBracket(CfgParserState &ps);
	void parseSectionFinish(CfgParserState &ps);
	void parseCommentLine(CfgParserSource *source);
	void parseCommentC(CfgParserSource *source);
	char parseSkipBlank(CfgParserSource *source);

	CfgParserSource *sourceNew(const std::string &name,
				   CfgParserSource::Type type);

	void variableDefine(const std::string &name, const std::string &value);
	void variableExpand(std::string& line);
	bool variableExpandName(std::string& line, const size_t begin,
				size_t &end, const std::string &var);
	bool variableExpandName(std::string& line, const size_t begin,
				size_t &end, const std::string &var,
				CfgParserVarExpander *exp);

	CfgParserOpt _opt;
	Os *_os;
	CfgParserSource *_source;
	std::vector<CfgParserVarExpander*> _var_expanders;
	CfgParserVarExpander* _var_expander_mem;

	TimeFiles _cfg_files;

	/** Vector of sources, for recursive parsing. */
	std::vector<CfgParserSource*> _sources;
	/** Vector of source names, to keep track of current source. */
	std::vector<std::string> _source_names;
	/** Set of source names, source of memory usage on long-going
	    CfgParser objects. */
	std::set<std::string> _source_name_set;
	std::vector<Entry*> _sections; //!< for recursive parsing.

	/**  Map of Define = ... sections */
	section_map _section_map;

	Entry *_root_entry; /**< Root Entry. */
	/** If true, parsed data included command or similar. */
	bool _is_dynamic_content;
	/** If true, data parsing ended early due to end early key found. */
	bool _is_end_early;
	Entry *_section; /**< Current section. */
	bool _overwrite; /**< Overwrite elements when appending. */

	static const std::string _root_source_name; //!< Root Entry Source Name.
};

#endif // _PEKWM_CFGPARSER_HH_
