#include "zeek/PolicyFile.h"

#include "zeek/zeek-config.h"

#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <map>
#include <string>
#include <vector>

#include "zeek/Debug.h"
#include "zeek/Reporter.h"
#include "zeek/util.h"

using namespace std;

struct PolicyFile
	{
	PolicyFile()
		{
		filedata = nullptr;
		lmtime = 0;
		}
	~PolicyFile()
		{
		delete[] filedata;
		filedata = nullptr;
		}

	time_t lmtime;
	char* filedata;
	vector<const char*> lines;
	};

using PolicyFileMap = map<string, PolicyFile*>;
static PolicyFileMap policy_files;

namespace zeek::detail
	{

int how_many_lines_in(const char* policy_filename)
	{
	if ( ! policy_filename )
		reporter->InternalError("NULL value passed to how_many_lines_in\n");

	FILE* throwaway = fopen(policy_filename, "r");
	if ( ! throwaway )
		{
		debug_msg("Could not open policy file: %s.\n", policy_filename);
		return -1;
		}

	fclose(throwaway);

	PolicyFileMap::iterator match;
	match = policy_files.find(policy_filename);

	if ( match == policy_files.end() )
		{
		match = policy_files.find(policy_filename);
		if ( match == policy_files.end() )
			{
			debug_msg("Policy file %s was not loaded.\n", policy_filename);
			return -1;
			}
		}

	PolicyFile* pf = match->second;
	return pf->lines.size();
	}

bool LoadPolicyFileText(const char* policy_filename,
                        const std::optional<std::string>& preloaded_content)
	{
	if ( ! policy_filename )
		return true;

	if ( policy_files.find(policy_filename) != policy_files.end() )
		debug_msg("Policy file %s already loaded\n", policy_filename);

	PolicyFile* pf = new PolicyFile;
	policy_files.insert(PolicyFileMap::value_type(policy_filename, pf));

	if ( preloaded_content )
		{
		auto size = preloaded_content->size();
		pf->filedata = new char[size + 1];
		memcpy(pf->filedata, preloaded_content->data(), size);
		pf->filedata[size] = '\0';
		}
	else
		{
		FILE* f = fopen(policy_filename, "r");

		if ( ! f )
			{
			debug_msg("Could not open policy file: %s.\n", policy_filename);
			return false;
			}

		struct stat st;
		if ( fstat(fileno(f), &st) != 0 )
			{
			char buf[256];
			util::zeek_strerror_r(errno, buf, sizeof(buf));
			reporter->Error("fstat failed on %s: %s", policy_filename, buf);
			fclose(f);
			return false;
			}

		pf->lmtime = st.st_mtime;
		off_t size = st.st_size;

		// ### This code is not necessarily Unicode safe!
		// (probably fine with UTF-8)
		pf->filedata = new char[size + 1];
		if ( fread(pf->filedata, size, 1, f) != 1 )
			reporter->InternalError("Failed to fread() file data");
		pf->filedata[size] = 0;
		fclose(f);
		}

	// Separate the string by newlines.
	pf->lines.push_back(pf->filedata);

	for ( char* iter = pf->filedata; *iter; ++iter )
		{
		if ( *iter == '\n' )
			{
			*iter = 0;
			if ( *(iter + 1) )
				pf->lines.push_back(iter + 1);
			}
		}

	for ( int i = 0; i < int(pf->lines.size()); ++i )
		assert(pf->lines[i][0] != '\n');

	return true;
	}

// REMEMBER: line number arguments are indexed from 0.
bool PrintLines(const char* policy_filename, unsigned int start_line, unsigned int how_many_lines,
                bool show_numbers)
	{
	if ( ! policy_filename )
		return true;

	FILE* throwaway = fopen(policy_filename, "r");
	if ( ! throwaway )
		{
		debug_msg("Could not open policy file: %s.\n", policy_filename);
		return false;
		}

	fclose(throwaway);

	PolicyFileMap::iterator match;
	match = policy_files.find(policy_filename);

	if ( match == policy_files.end() )
		{
		match = policy_files.find(policy_filename);
		if ( match == policy_files.end() )
			{
			debug_msg("Policy file %s was not loaded.\n", policy_filename);
			return false;
			}
		}

	PolicyFile* pf = match->second;

	if ( start_line < 1 )
		start_line = 1;

	if ( start_line > pf->lines.size() )
		{
		debug_msg("Line number %d out of range; %s has %d lines\n", start_line, policy_filename,
		          int(pf->lines.size()));
		return false;
		}

	if ( start_line + how_many_lines - 1 > pf->lines.size() )
		how_many_lines = pf->lines.size() - start_line + 1;

	for ( unsigned int i = 0; i < how_many_lines; ++i )
		{
		if ( show_numbers )
			debug_msg("%d\t", i + start_line);

		const char* line = pf->lines[start_line + i - 1];
		debug_msg("%s\n", line);
		}

	return true;
	}

	} // namespace zeek::detail
