// See the file "COPYING" in the main distribution directory for copyright.

#pragma once

#include "zeek/zeek-config.h"

#include <cstdio>
#include <map>
#include <string>
#include <vector>

namespace zeek::zeekygen::detail
	{

class Info;
class PackageInfo;
class ScriptInfo;
class IdentifierInfo;

/**
 * Helper class to create files in arbitrary file paths and automatically
 * close it on destruction.
 */
struct TargetFile
	{
	/**
	 * Open a file.
	 * @param arg_name Path to a file to create.  It's a fatal error if
	 * creating it fails.  Creating it will also create any intermediate
	 * directories that don't already exist.
	 *
	 */
	explicit TargetFile(const std::string& arg_name);

	/**
	 * Close the file.
	 */
	~TargetFile();

	std::string name; /**< File name. */
	FILE* f; /**< File stream. */
	};

/**
 * A Zeekygen target abstract base class.  A target is generally any portion of
 * documentation that Zeek can build.  It's identified by a type (e.g. script,
 * identifier, package), a pattern (e.g. "example.zeek", "HTTP::Info"), and
 * a path to an output file.
 */
class Target
	{

public:
	/**
	 * Ctor.
	 * @param arg_name output file name of the target.
	 * @param arg_pattern pattern of info objects the target depends upon.  Only
	 * exact string and simple prefix matching is currently allowed.
	 */
	Target(const std::string& arg_name, const std::string& arg_pattern);

	/**
	 * Dtor.
	 */
	virtual ~Target() { }

	/**
	 * Filter out any dependency information from a set of all known info.
	 * @param infos All known info objects.
	 */
	void FindDependencies(const std::vector<Info*>& infos) { DoFindDependencies(infos); }

	/**
	 * Build the target by generating its output file.  Implementations may
	 * not always write to the output file if they determine an existing
	 * version is already up-to-date.
	 */
	void Generate() const { DoGenerate(); }

	/**
	 * Check if a particular info object matches the target pattern.
	 * Currently only exact string and simple prefix matching patterns are
	 * used.  E.g. for prefix matching "HTTP::*" or "base/protocols/http*".
	 * @param info An info object for some thing that is documentable.
	 * @return true if it matches, else false.
	 */
	bool MatchesPattern(Info* info) const;

	/**
	 * @return The output file name of the target.
	 */
	std::string Name() const { return name; }

	/**
	 * @return The pattern string of the target.
	 */
	std::string Pattern() const { return pattern; }

private:
	virtual void DoFindDependencies(const std::vector<Info*>& infos) = 0;

	virtual void DoGenerate() const = 0;

	std::string name;
	std::string pattern;
	std::string prefix;
	};

template <class T> static Target* create_target(const std::string& name, const std::string& pattern)
	{
	return new T(name, pattern);
	}

/**
 * Factory for creating Target instances.
 */
class TargetFactory
	{

public:
	/**
	 * Register a new target type.
	 * @param type_name The target type name as it will appear in Zeekygen
	 * config files.
	 */
	template <class T> void Register(const std::string& type_name)
		{
		target_creators[type_name] = &create_target<T>;
		}

	/**
	 * Instantiate a target.
	 * @param type_name The target type name as it appears in Zeekygen config
	 * files.
	 * @param name The output file name of the target.
	 * @param pattern The dependency pattern of the target.
	 * @return A new target instance or a pointer if \a type_name is not
	 * registered.
	 */
	Target* Create(const std::string& type_name, const std::string& name,
	               const std::string& pattern)
		{
		target_creator_map::const_iterator it = target_creators.find(type_name);

		if ( it == target_creators.end() )
			return nullptr;

		return it->second(name, pattern);
		}

private:
	using TargetFactoryFn = Target* (*)(const std::string& name, const std::string& pattern);
	using target_creator_map = std::map<std::string, TargetFactoryFn>;
	target_creator_map target_creators;
	};

/**
 * Target to build analyzer documentation.
 */
class AnalyzerTarget : public Target
	{
public:
	/**
	 * Writes out plugin index documentation for all analyzer plugins.
	 * @param f an open file stream to write docs into.
	 */
	void CreateAnalyzerDoc(FILE* f) const { return DoCreateAnalyzerDoc(f); }

protected:
	using doc_creator_fn = void (*)(FILE*);

	AnalyzerTarget(const std::string& name, const std::string& pattern) : Target(name, pattern) { }

private:
	void DoFindDependencies(const std::vector<Info*>& infos) override;

	void DoGenerate() const override;

	virtual void DoCreateAnalyzerDoc(FILE* f) const = 0;
	};

/**
 * Target to build protocol analyzer documentation.
 */
class ProtoAnalyzerTarget : public AnalyzerTarget
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	ProtoAnalyzerTarget(const std::string& name, const std::string& pattern)
		: AnalyzerTarget(name, pattern)
		{
		}

private:
	void DoCreateAnalyzerDoc(FILE* f) const override;
	};

/**
 * Target to build file analyzer documentation.
 */
class FileAnalyzerTarget : public AnalyzerTarget
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	FileAnalyzerTarget(const std::string& name, const std::string& pattern)
		: AnalyzerTarget(name, pattern)
		{
		}

private:
	void DoCreateAnalyzerDoc(FILE* f) const override;
	};

/**
 * Target to build packet analyzer documentation.
 */
class PacketAnalyzerTarget : public AnalyzerTarget
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	PacketAnalyzerTarget(const std::string& name, const std::string& pattern)
		: AnalyzerTarget(name, pattern)
		{
		}

private:
	void DoCreateAnalyzerDoc(FILE* f) const override;
	};

/**
 * Target to build package documentation.
 */
class PackageTarget : public Target
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	PackageTarget(const std::string& name, const std::string& pattern)
		: Target(name, pattern), pkg_deps(), script_deps(), pkg_manifest()
		{
		}

private:
	void DoFindDependencies(const std::vector<Info*>& infos) override;

	void DoGenerate() const override;

	std::vector<PackageInfo*> pkg_deps;
	std::vector<ScriptInfo*> script_deps;

	using manifest_t = std::map<PackageInfo*, std::vector<ScriptInfo*>>;
	manifest_t pkg_manifest;
	};

/**
 * Target to build package index documentation.
 */
class PackageIndexTarget : public Target
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	PackageIndexTarget(const std::string& name, const std::string& pattern)
		: Target(name, pattern), pkg_deps()
		{
		}

private:
	void DoFindDependencies(const std::vector<Info*>& infos) override;

	void DoGenerate() const override;

	std::vector<PackageInfo*> pkg_deps;
	};

/**
 * Target to build script documentation.
 */
class ScriptTarget : public Target
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name or directory.  If it's a directory,
	 * then one document for each script that matches the pattern is written to
	 * the directory in a directory structure which mirrors the script's path
	 * relative to a component in ZEEKPATH.
	 * @param pattern Dependency pattern.
	 */
	ScriptTarget(const std::string& name, const std::string& pattern)
		: Target(name, pattern), script_deps()
		{
		}

	~ScriptTarget() override
		{
		for ( size_t i = 0; i < pkg_deps.size(); ++i )
			delete pkg_deps[i];
		}

protected:
	std::vector<ScriptInfo*> script_deps;

private:
	void DoFindDependencies(const std::vector<Info*>& infos) override;

	void DoGenerate() const override;

	bool IsDir() const { return Name()[Name().size() - 1] == '/'; }

	std::vector<Target*> pkg_deps;
	};

/**
 * Target to build script summary documentation.
 */
class ScriptSummaryTarget : public ScriptTarget
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	ScriptSummaryTarget(const std::string& name, const std::string& pattern)
		: ScriptTarget(name, pattern)
		{
		}

private:
	void DoGenerate() const override /* override */;
	};

/**
 * Target to build script index documentation.
 */
class ScriptIndexTarget : public ScriptTarget
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	ScriptIndexTarget(const std::string& name, const std::string& pattern)
		: ScriptTarget(name, pattern)
		{
		}

private:
	void DoGenerate() const override /* override */;
	};

/**
 * Target to build identifier documentation.
 */
class IdentifierTarget : public Target
	{
public:
	/**
	 * Ctor.
	 * @param name Output file name.
	 * @param pattern Dependency pattern.
	 */
	IdentifierTarget(const std::string& name, const std::string& pattern)
		: Target(name, pattern), id_deps()
		{
		}

private:
	void DoFindDependencies(const std::vector<Info*>& infos) override;

	void DoGenerate() const override;

	std::vector<IdentifierInfo*> id_deps;
	};

	} // namespace zeek::zeekygen::detail
