/*
 * (C) Copyright 2008-2024 Diomidis Spinellis
 *
 * This file is part of CScout.
 *
 * CScout 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 3 of the License, or
 * (at your option) any later version.
 *
 * CScout 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 CScout.  If not, see <http://www.gnu.org/licenses/>.
 *
 *
 * HTML utility functions
 *
 */

#include <map>
#include <string>
#include <deque>
#include <vector>
#include <stack>
#include <iterator>
#include <iostream>
#include <fstream>
#include <list>
#include <set>
#include <utility>
#include <functional>
#include <algorithm>		// set_difference
#include <cctype>
#include <sstream>		// ostringstream
#include <cstdio>		// perror, rename
#include <cstdlib>		// atoi
#include <cerrno>		// errno

#include "swill.h"
#include "getopt.h"

#include "cpp.h"
#include "debug.h"
#include "error.h"
#include "parse.tab.h"
#include "attr.h"
#include "metrics.h"
#include "fileid.h"
#include "tokid.h"
#include "token.h"
#include "ptoken.h"
#include "fchar.h"
#include "pltoken.h"
#include "macro.h"
#include "pdtoken.h"
#include "eclass.h"
#include "ctoken.h"
#include "type.h"
#include "stab.h"
#include "fdep.h"
#include "version.h"
#include "call.h"
#include "html.h"
#include "fileutils.h"
#include "option.h"

/*
 * Return as a C string the HTML equivalent of character c
 * Handles tab-stop expansion provided all output is processed through this
 * function
 */
const char *
html(char c)
{
	static char str[2];
	static int column = 0;
	static vector<string> spaces(0);
	int space_idx;

	switch (c) {
	case '&': column++; return "&amp;";
	case '<': column++; return "&lt;";
	case '>': column++; return "&gt;";
	case '"': column++; return "&quot;";
	case ' ': column++; return "&nbsp;";
	case '\t':
		if ((int)(spaces.size()) != Option::tab_width->get()) {
			spaces.resize(Option::tab_width->get());
			for (int i = 0; i < Option::tab_width->get(); i++) {
				string t;
				for (int j = 0; j < Option::tab_width->get() - i; j++)
					t += "&nbsp;";
				spaces[i] = t;
			}
		}
		space_idx = column % Option::tab_width->get();
		if (DP())
			cout << "Tab; " << " column before:" << column << " space_idx: " << space_idx << endl;
		column += 8 - space_idx;
		return spaces[space_idx].c_str();
	case '\n':
		column = 0;
		return "<br>\n";
	case '\r':
		column = 0;
		return "";
	case '\f':
		column = 0;
		return "<br><hr><br>\n";
	default:
		column++;
		str[0] = c;
		return str;
	}
}

// HTML-encode the given string
string
html(const string &s)
{
	string r;

	for (string::const_iterator i = s.begin(); i != s.end(); i++)
		r += html(*i);
	return r;
}

// Output s as HTML in of
void
html_string(FILE *of, string s)
{
	for (string::const_iterator i = s.begin(); i != s.end(); i++)
		fputs(html(*i), of);
}


// Create a new HTML file with a given filename and title
// The heading, if not given, will be the same as the title
void
html_head(FILE *of, const string fname, const string title, const char *heading)
{
	swill_title(title.c_str());
	if (DP())
		cerr << "Write to " << fname << endl;
	fprintf(of,
		"<!doctype html public \"-//IETF//DTD HTML//EN\">\n"
		"<html>\n"
		"<head>\n"
		"<meta name=\"GENERATOR\" content=\"CScout %s - %s\">\n",
		Version::get_revision().c_str(),
		Version::get_date().c_str());
	fputs(
		"<meta http-equiv=\"Content-Style-Type\" content=\"text/css\">"
		"<style type=\"text/css\" >"
		"<!--\n", of);

	ifstream in;
	string css_fname;
	if (cscout_input_file("style.css", in, css_fname)) {
		int val;
		while ((val = in.get()) != EOF)
			putc(val, of);
	} else
		fputs(
		#include "css.c"
		, of);
	fputs(
		"-->"
		"</style>"
		"</head>", of);
	fprintf(of,
		"<title>%s</title>\n"
		"</head>\n"
		"<body>\n"
		"<h1>%s</h1>\n",
		title.c_str(),
		heading ? heading : title.c_str());
}

// And an HTML file end
void
html_tail(FILE *of)
{
	extern Attributes::size_type current_project;

	if (current_project)
		fprintf(of, "<p> <b>Project %s is currently selected</b>\n", Project::get_projname(current_project).c_str());
	fprintf(of,
		"<p>"
		"<a href=\"index.html\">Main page</a>\n"
		" &mdash; Web: "
		"<a href=\"http://www.spinellis.gr/cscout\">Home</a>\n"
		"<a href=\"http://www.spinellis.gr/cscout/doc/index.html\">Manual</a>\n"
		"<br><hr><div class=\"footer\">CScout %s &mdash; %s",
		Version::get_revision().c_str(),
		Version::get_date().c_str());
	fprintf(of, " &mdash; Licensed under the GNU General Public License.");
	fprintf(of, "</div></body></html>\n");
}

// Return a function's label, based on the user's preferences
string
file_label(Fileid f, bool hyperlink)
{
	string result;
	char buff[256];

	if (hyperlink) {
		snprintf(buff, sizeof(buff), "<a href=\"file.html?id=%d\">", f.get_id());
		result = buff;
	}
	switch (Option::fgraph_show->get()) {
	case 'p':			// Show complete paths
		result += f.get_path() + "/";
		/* FALLTHROUGH */
	case 'n':			// Show only file names
		result += f.get_fname();
		break;
	case 'e':			// Show only edges
		result += " ";
		break;
	}
	if (hyperlink)
		result += "</a>";
	return (result);
}

// Return a function's label, based on the user's preferences
string
function_label(Call *f, bool hyperlink)
{
	string result;
	char buff[256];

	if (hyperlink) {
		snprintf(buff, sizeof(buff), "<a href=\"fun.html?f=%p\">", f);
		result = buff;
	}
	if (Option::show_function_type->get()) {
		if (f->is_file_scoped())
			result += "static:";
		else
			result += "public:";
	}
	if (Option::is_defined->get()) {
		if (f->is_defined())
			result += "1:";
		else
			result += "0:";
	}
	if (Option::line_num->get()) {
        Tokid t;
		if (f->is_defined()) {
            t = f->get_definition();
        } else {
            t = f->get_tokid();
        }
        int first = Filedetails::get_line_number(t.get_fileid(), t.get_streampos());
        int last = first + f->get_pre_cpp_metrics().get_metric(Metrics::em_nline);
        result += to_string(first) + ";" + to_string(last) + ":";
	}
	if (Option::cgraph_show->get() == 'f')		// Show files
		result += f->get_site().get_fileid().get_fname() + ":";
	else if (Option::cgraph_show->get() == 'p')	// Show paths
		result += f->get_site().get_fileid().get_path() + ":";
	if (Option::cgraph_show->get() != 'e')		// Empty labels
		result += f->get_name();
	if (hyperlink)
		result += "</a>";
	return (result);
}

// Display a system error on the HTML output.
void
html_perror(FILE *of, const string &user_msg, bool svg)
{
	string error_msg(user_msg + ": " + string(strerror(errno)) + "\n");
	fputs(error_msg.c_str(), stderr);
	if (svg)
		fprintf(of, "<?xml version=\"1.0\" ?>\n"
			"<svg>\n"
			"<text  x=\"20\" y=\"50\" >%s</text>\n"
			"</svg>\n", error_msg.c_str());
	else {
		fputs(error_msg.c_str(), of);
		fputs("</p><p>The operation you requested is incomplete.  "
			"Please correct the underlying cause, and (if possible) return to the "
			"CScout <a href=\"index.html\">main page</a> to retry the operation.</p>", of);
	}
}

// Display a non-system error on the HTML output.
void
html_error(FILE *of, const string &error_msg)
{
	fputs(error_msg.c_str(), stderr);
	fputs(error_msg.c_str(), of);
	fputs("</p><p>The operation you requested is incomplete.  "
		"Please correct the underlying cause, and (if possible) return to the "
		"CScout <a href=\"index.html\">main page</a> to retry the operation.</p>", of);
}
