// SPDX-License-Identifier: Apache-2.0
/*
Copyright (C) 2023 The Falco Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

The following file was originally developed in the sysdig cli
project and then contributed to the falcosecurity/libs project
(https://github.com/draios/sysdig/commit/abf90f39f8e5ea6eb4276cc3f980dbd878816ecd
with the #1737 PR). At the end, given its removal from the
falcosecurity/libs project, has been reintroduced to the
draios/sysdig project.

*/
#include <algorithm>
#include <libsinsp/sinsp.h>
#include <chisel/chisel_viewinfo.h>

using namespace std;

///////////////////////////////////////////////////////////////////////////////
// chisel_view_column_info implementation
///////////////////////////////////////////////////////////////////////////////
string chisel_view_column_info::get_field(uint32_t depth)
{
	// Trim the string
	replace_in_place(m_field, " ", "");
	replace_in_place(m_field, "\t", "");

	if(m_field.find("%depth-1") != string::npos)
	{
		string res = m_field;
		replace_in_place(res, "%depth-1", to_string(depth - 1));
		return res;
	}
	else if(m_field.find("%depth+1") != string::npos)
	{
		string res = m_field;
		replace_in_place(res, "%depth+1", to_string(depth - 1));
		return res;
	}
	else if(m_field.find("%depth") != string::npos)
	{
		string res = m_field;
		replace_in_place(res, "%depth", to_string(depth));
		return res;
	}
	else
	{
		return m_field;
	}
}

string chisel_view_column_info::get_filter_field(uint32_t depth)
{
	//
	// If m_filterfield, return it as an override to m_field
	//
	if(m_filterfield != "")
	{
		return m_filterfield;
	}
	else
	{
		return get_field(depth);
	}
}

///////////////////////////////////////////////////////////////////////////////
// chisel_view_info implementation
///////////////////////////////////////////////////////////////////////////////
chisel_view_info::chisel_view_info()
{
	m_valid = false;
}

chisel_view_info::chisel_view_info(viewtype type,
	string id,
	string name,
	string description,
	vector<string> tags,
	vector<string> tips,
	vector<chisel_view_column_info> columns,
	vector<string> applies_to,
	string filter,
	string drilldown_target,
	bool use_defaults,
	bool is_root,
	vector<chisel_view_action_info> actions,
	bool drilldown_increase_depth,
	string spectro_type,
	bool propagate_filter)
{
	m_id = id;
	m_name = name;
	m_description = description;
	m_does_groupby = false;
	m_type = type;
	m_tags = tags;
	m_tips = tips;
	m_columns = columns;
	m_drilldown_target = drilldown_target;
	m_is_root = is_root;
	m_applies_to = applies_to;
	m_drilldown_increase_depth = drilldown_increase_depth;
	m_spectro_type = spectro_type;
	m_propagate_filter = propagate_filter;

	m_use_defaults = use_defaults;

	//
	// Make sure the keys go at the beginning
	//
	move_key_to_front(TEF_IS_GROUPBY_KEY);
	move_key_to_front(TEF_IS_KEY);

	//
	// Determine the sorting and grouping columns
	//
	set_sorting_col();

	m_filter = filter;
	m_valid = true;
	m_actions = actions;
	// init the array for hotkeys for sorting columns
	set_col_sorting_hotkeys();
}

void chisel_view_info::set_col_sorting_hotkeys()
{
	const char shift_number_keys [] = {'!', '@', '#', '$', '%', '^', '&', '*', '('};
	uint32_t size = sizeof(shift_number_keys) / sizeof(shift_number_keys[0]);
	for(uint32_t i=0; i<size; i++)
	{
		m_col_sort_hotkeys.push_back(shift_number_keys[i]);
	}
	max_col_sort_hotkeys = m_col_sort_hotkeys.size();
}

void chisel_view_info::set_sorting_col()
{
	m_n_sorting_cols = 0;

	for(uint32_t j = 0; j < m_columns.size(); j++)
	{
		if((m_columns[j].m_flags & TEF_IS_SORT_COLUMN) != 0)
		{
			m_sortingcol = j;
			m_n_sorting_cols++;
		}

		if((m_columns[j].m_flags & TEF_IS_GROUPBY_KEY) != 0)
		{
			m_does_groupby = true;
		}
	}

	if(m_does_groupby)
	{
		m_sortingcol--;
	}

	if(m_n_sorting_cols == 0)
	{
		if(m_does_groupby)
		{
			m_sortingcol = 2;
		}
		else
		{
			m_sortingcol = 1;
		}
	}
	else if(m_n_sorting_cols > 1)
	{
		throw sinsp_exception("view format error: more than one sorting column");
	}

	if(m_sortingcol < 0)
	{
		ASSERT(false);
		throw sinsp_exception("view sorting column configuration error");
	}
}

void chisel_view_info::apply_tag(string tag)
{
	for(auto it = m_columns.begin(); it != m_columns.end();)
	{
		bool found = false;

		if(it->m_tags.size() != 0)
		{
			for(string t : it->m_tags)
			{
				if(t == tag)
				{
					found = true;
					break;
				}
			}

			if(!found)
			{
				it = m_columns.erase(it);
				continue;
			}
		}

		++it;
	}

	//
	// Make sure to recalculate the sorting and grouping columns, which could change
	// if we remove columns.
	//
	set_sorting_col();
}

void chisel_view_info::get_col_names_and_sizes(vector<string>* colnames, vector<int32_t>* colsizes)
{
	if(m_type == viewtype::T_LIST)
	{
		colsizes->push_back(-1);
		colnames->push_back("");
	}

	for(auto fit : m_columns)
	{
		if(m_does_groupby)
		{
			if((fit.m_flags & TEF_IS_KEY) != 0)
			{
				continue;
			}

			if((fit.m_flags & TEF_IS_GROUPBY_KEY) != 0)
			{
				colsizes->insert(colsizes->begin(), fit.m_colsize);
				colnames->insert(colnames->begin(), fit.m_name);
				continue;
			}
		}

		colsizes->push_back(fit.m_colsize);
		colnames->push_back(fit.m_name);
	}
}

void chisel_view_info::move_key_to_front(uint32_t keyflag)
{
	for(uint32_t j = 0; j < m_columns.size(); j++)
	{
		if((m_columns[j].m_flags & keyflag) != 0)
		{
			chisel_view_column_info ci = m_columns[j];

			m_columns.erase(m_columns.begin() +j);
			m_columns.insert(m_columns.begin(), ci);
			return;
		}
	}
}

chisel_view_column_info* chisel_view_info::get_key()
{
	for(uint32_t j = 0; j < m_columns.size(); j++)
	{
		if((m_columns[j].m_flags & TEF_IS_GROUPBY_KEY) != 0)
		{
			return &m_columns[j];
		}
	}

	for(uint32_t j = 0; j < m_columns.size(); j++)
	{
		if((m_columns[j].m_flags & TEF_IS_KEY) != 0)
		{
			return &m_columns[j];
		}
	}

	// The *must* be a key
	return NULL;
}

string chisel_view_info::get_filter(uint32_t depth) const
{
	if(m_filter.find("%depth+1") != string::npos)
	{
		string res = m_filter;
		replace_in_place(res, "%depth+1", to_string(depth + 1));
		replace_in_place(res, "%depth + 1", to_string(depth + 1));
		return res;
	}
	else if(m_filter.find("%depth-1") != string::npos)
	{
		string res = m_filter;
		replace_in_place(res, "%depth-1", to_string(depth - 1));
		replace_in_place(res, "%depth - 1", to_string(depth - 1));
		return res;
	}
	else if(m_filter.find("%depth") != string::npos)
	{
		string res = m_filter;
		replace_in_place(res, "%depth", to_string(depth));
		return res;
	}
	else
	{
		return m_filter;
	}
}

///////////////////////////////////////////////////////////////////////////////
// chisel_view_manager implementation
///////////////////////////////////////////////////////////////////////////////
void chisel_view_manager::add(chisel_view_info* vinfo)
{
	m_views.push_back(*vinfo);
}

struct view_cmp
{
	bool operator()(const chisel_view_info& src, const chisel_view_info& dst)
	{
		return src.m_name < dst.m_name;
	}
};

void chisel_view_manager::sort_views()
{
	view_cmp cc;

	//
	// Sort the list alphabetically
	//
	sort(m_views.begin(),
		m_views.end(),
		cc);

	//
	// Print the view list for debugging purposes
	//
	//for(uint32_t j = 0; j < m_views.size(); j++)
	//{
	//	libsinsp_logger()->format("> %d) %s", j, m_views[j].m_name.c_str());
	//}
}

vector<chisel_view_info>* chisel_view_manager::get_views()
{
	sort_views();
	return &m_views;
}

uint32_t chisel_view_manager::get_selected_view()
{
	sort_views();

	if(m_selected_view_id != "")
	{
		for(uint32_t j = 0; j < m_views.size(); j++)
		{
			if(m_views[j].m_id == m_selected_view_id)
			{
				return j;
			}
		}

		if(m_selected_view_id == "echo")
		{
			return VIEW_ID_SPY;
		}
		else if(m_selected_view_id == "dig")
		{
			return VIEW_ID_DIG;
		}
	}
	else
	{
		for(uint32_t j = 0; j < m_views.size(); j++)
		{
			if(m_views[j].m_is_root)
			{
				return j;
			}
		}
	}

	throw sinsp_exception("view " + m_selected_view_id + " not found");
	return 0;
}

void chisel_view_manager::set_selected_view(string viewid)
{
	m_selected_view_id = viewid;
}
