--[[--------------------------< C S 1 _ C H A R T S >----------------------------------------------------------

Calls Module:Chart to display various related category counts.  Data (the categories) are listed directly by name
(without name space) in Module:CS1 charts/data or (for cs1|2 categories) may be extracted from Module:Citation/CS1/Configuration

Bar charts are rendered left to right in the order listed; pie charts are descending-sorted by count and then
ascending-sorted by category name.  The number of 'bars' displayed in a bar chart is limited to 64; the number
of 'slices; displayed in a pie chart is limited to 26.

]]

require('Module:No globals');
local getArgs = require ('Module:Arguments').getArgs;
local data = mw.loadData ('Moduli:CS1_charts/data');

local page_title = mw.title.getCurrentTitle().prefixedText;						-- namespace and name


--[[--------------------------< S U B C A T _ D A T A _ F E T C H >--------------------------------------------

return the total number of pages, files, and subcats in a named category's (<cat>) subcategories; when no subcats
listed in <subcats_t>, returns 0

]]

local function subcat_data_fetch (cat)
	local page_count = 0;

	if data.subcats_t[cat] then													-- if this category has listed subcats
		local pages_in_cat_t = {}; 
		for i, subcat in ipairs (data.subcats_t[cat]) do						-- for each subcat
			pages_in_cat_t = mw.site.stats.pagesInCategory (subcat, '*');		-- get the various counts and 
			page_count = page_count + pages_in_cat_t.files + pages_in_cat_t.pages + pages_in_cat_t.subcats;	-- tally
		end
	end
	
	return page_count;
end


--[[--------------------------< B A R _ C H A R T _ C R E A T E >----------------------------------------------

creates a bar chart of up to 64 categories; empty categories are not included in the chart.  Display order (left-
to-right) of the categories in the rendered chart is established in the data sequence tables in ~/data (first-
to-last).

{{#invoke:CS1 charts|bar_chart_create|maint}}									-- cs1|2 maintenance categories
{{#invoke:CS1 charts|bar_chart_create|error}}									-- cs1|2 error categories

]]

local function bar_chart_create (frame)
	local page_counts_t = {};
	local subcat_counts_t = {};
	local cat_names_t = {};
	local bar_chart_x_legends;
	
	local cats_t = data.keyword_cat_map_t[frame.args[1]];
	local cats_t_count;
	local x_legend = data.keyword_x_legends_map_t[frame.args[1]];
	bar_chart_x_legends = (page_title == ('Kategoria:' .. x_legend)) and x_legend or table.concat ({'[[:Kategoria:', x_legend, '|', x_legend, ']]'});

	for i, cat in ipairs (cats_t) do
		local pages_in_cat_t = {};
		cat_names_t[i] = cat;													-- save a copy of the category name
		
		pages_in_cat_t = mw.site.stats.pagesInCategory (cat, '*');				-- get the table of counts
		if 0 ~= subcat_data_fetch (cat) then
			page_counts_t[i] = pages_in_cat_t.files + pages_in_cat_t.pages + subcat_data_fetch (cat);	-- don't include pages_in_cat_t.subcats in tally
			subcat_counts_t[i] = pages_in_cat_t.subcats;						-- but remember for later annotation
		else
			page_counts_t[i] = pages_in_cat_t.files + pages_in_cat_t.pages;		-- there are no subcats so don't bother
		end
		cats_t_count = i;
	end
	
	local out = {'chart', 'bar chart'};											-- init for #invoke parser function call with Module:Chart and function barChart()
	out.delimiter = data.bar_chart_delimiter;									-- |delimiter=
	out['units suffix'] = data.bar_chart_units_suffix;							-- |units suffix=
	
	local tail;
	local group_names_t = {};
	local colors_t = {};

	local j = 0;																-- indexer for cats that that are not empty
	for i, v in ipairs (cats_t) do
		if 0 ~= page_counts_t[i] then
			j = j + 1;															-- bump the indexer
			out['group ' .. j] = page_counts_t[i];								-- add |group 1= to |group n= pararameters
	
			if subcat_counts_t[i] then											-- if this cat has subcats
				tail = table.concat ({' faqe nga ', subcat_counts_t[i], ' nënkategori'})	-- modify the group name tail
			else																-- here when no subcats
				tail = ' faqe';													-- standard group name tail
			end
	
			out['tooltip ' .. j] = table.concat ({cat_names_t[i], ' ', page_counts_t[i], tail});	-- add |tooltip 1= to |tooltip n= pararameters
			out['links ' .. j] = table.concat ({':Kategoria:', cat_names_t[i]});	-- add |links 1= to |links n= pararameters
			table.insert (group_names_t, table.concat ({'[[:Kategoria:', cat_names_t[i], '|', cat_names_t[i], ']] ', page_counts_t[i], tail}));
			if 64 == j then
				break;
			end
		end
	end

	out['group names'] = table.concat (group_names_t, data.bar_chart_delimiter);	-- add |group names= parameter

	for i, color in ipairs (data.chart_colors_t) do								-- make a local table for concatenation; necessary because <data.chart_colors_t> is a metatable
		colors_t[i] = color;
		if i == j then															-- no more than we need
			break;
		end
	end

	out['colors'] = table.concat (colors_t, data.bar_chart_delimiter, 1, j);	-- add |colors= parameter
	out['x legends'] = table.concat ({bar_chart_x_legends, ' (', #page_counts_t-j, ' nga ', cats_t_count, ' kategori bosh janë fshehur)'});	-- add |x legends=

	return frame:callParserFunction ('#invoke', out);							-- {{#invoke:chart|bar chart|args...}}
end


--[[--------------------------< P I E _ C H A R T _ C R E A T E >----------------------------------------------

creates a pie chart of up to 26 slices; empty categories are not included in the chart.  Display order of the
categories in the rendered chart is by  descending count and then by ascending category name (largest-to-smallest
slice)

{{#invoke:CS1 charts|pie_chart_create|lang}}									-- cs1|2 language properites categories
{{#invoke:CS1 charts|pie_chart_create|script}}									-- cs1|2 script-language properties categories

]]

local function pie_chart_create (frame)
	local args_t = getArgs (frame);
	local raw = {};																-- count, legend, and category extracted from category
	local slices = {};															-- formatted output suitable for [[Module:Chart]] |slices= parameter
	local link = true;															-- slices are linked
	local delimiter = ';';														-- default is ':' but catagory names have colons so use semicolon

	local cats_t = data.keyword_cat_map_t[frame.args[1]];

	for _, cat in ipairs (cats_t) do											-- spin through category names and construct raw data for chart
		local t = {}
		table.insert (t, mw.site.stats.pagesInCategory (cat, 'pages'));
		table.insert (t, cat and cat:match (args_t.pattern or '.*') or cat);	-- extract legend; use cat name if pattern not provided
		table.insert (raw, t);													-- save this
	end

	if 0==#raw then
		return string.format ('(%s%s%s%s%s)', -1, delimiter, 'Gabim: Grafiku nuk ka asnjë pjesë të përcaktuar', delimiter, '#d33');
	end

	for i, v in ipairs (raw) do													-- look for duplicate names
		for j=i+1, #raw do
			if raw[i][2] == raw[j][2] then
				return string.format ('(%s%s%s %s%s%s)', -1, delimiter, 'Gabim: Grafiku ka disa pjesë me emra të njëjta', raw[i][2], delimiter, '#d33');
			end
		end
	end

	local function comp (a, b)													-- used in following table.sort()
		if a[1] == b[1] then													-- when same do
			return a[2] < b[2];													-- ascending alpha sort on name
		end
		return tonumber (a[1]) > tonumber(b[1]);								-- descending sort
	end
	
	table.sort (raw, comp);														-- descending sort

	local non_empty_count = 0;													-- count of categories with at least one page
	local empty_count = 0;
	local other_pages_tally = 0;												-- tally of pages not included in the first 25 slices

	for i, t in ipairs (raw) do
		if 26 > i and 0 ~= t[1] then											-- slices 1 - 25 separately in the chart (as long as they have something in them)
			local slice_link = table.concat ({'[[:Kategoria:', t[2], '|', t[2], ']]'});
			table.insert (slices, table.concat ({
				'(',
				t[1],															-- count
				delimiter,
				slice_link,
				delimiter,
				data.chart_colors_t[i],											-- color for this slice
				delimiter,
				slice_link,
				')'
				}));
		elseif 0 ~= t[1] then													-- would-be slices 26+
			non_empty_count = non_empty_count + 1;								-- count the number of non-empty cats
			if t[1] then														-- in case t[1] is nil for whatever reason; shouldn't be
				other_pages_tally = other_pages_tally + t[1];					-- sum the number of pages in these non-empty cats
			end
		else
			empty_count = empty_count + 1;										-- count the number of empty cats
		end
	end

	if 0 == #slices then														-- nothing in slices{}
		return string.format ('(%s%s%s%s%s)', -1, delimiter, 'Gabim: Vlera e të gjitha pjesëve të përcaktuara është 0', delimiter, '#d33');
	end

	if 0 ~= non_empty_count or 0 ~= empty_count then							-- 26th slice
		table.insert (slices, string.format ('(%s%s%s kategori të tjera + %s kategori bosh)', other_pages_tally, delimiter, non_empty_count, empty_count));
	end

	local out = {'chart', 'pie chart'};											-- init for #invoke parser function call with Module:Chart and function pieChart()
	out['delimiter'] = ';';
	out['units suffix'] = '_faqe';
	out['percent'] = 'true';
	out['slices'] = table.concat (slices, '\n');

	local label = data.keyword_label_map_t[frame.args[1]];						-- create label for pie chart
	local pie_chart_label = (page_title == ('Kategoria:' .. label)) and label or table.concat ({'[[:Kategoria:', label, '|', label, ']]'});	-- don't self link

	local render_t = {};														-- add label above pie chart
	table.insert (render_t, '<div style="max-width:300px; text-align:center"><span style="font-size:130%">');	-- center label above pie chart
	table.insert (render_t, pie_chart_label);									-- the label
	table.insert (render_t, '</span><div style="text-align:left">');			-- and more of the centering markup
	table.insert (render_t, frame:callParserFunction ('#invoke', out));			-- render the chart
	table.insert (render_t, '</div></div>');									-- and the last of the centering markup
	return table.concat (render_t);												-- make a big string and done
end


--[[--------------------------< C A T _ L I S T S _ C H E C K >------------------------------------------------

Create lists of keys that are in one of Moduli:Citation/CS1/Configuration or Moduli:CS1 charts/data.

Keys for category names that are assembled on-the-fly appear only in ~/data and are never available in ~/Configuration.

For keys available in ~/Configuration but not in ~/data, the list includes the category name.  It is not possible
to go the other way because ~/data does not know the category names.

{{#invoke:CS1 charts|cat_list_check}}

]]

local function cat_list_check (frame)
	local error_conditions_t = mw.loadData ('Moduli:Citation/CS1/Configuration').error_conditions;
	local data_err_cats_t = {};
	local data_maint_cats_t = {};
	
	for _, index in ipairs (data.error_cats_order_t) do
		data_err_cats_t[index] = true;
	end
	
	for _, index in ipairs (data.maint_cats_order_t) do
		data_maint_cats_t[index] = true;
	end

----------																		-- see if we can find data.error_cats_order_t keys in ~/Configuration error_conditions table
	local in_data_only_t = {};													-- holds list of error and maint keys not found in ~/Configuration
	for index, _ in pairs (data_err_cats_t) do
		if not error_conditions_t[index] then									-- when data key not found
			table.insert (in_data_only_t, '\t' .. index);						-- add to our list with leading tab
		end
	end
																				-- now see if we can find data.maint_cats_order_t keys in ~/Configuration error_conditions table
	for index, _ in pairs (data_maint_cats_t) do
		if not error_conditions_t[index] then									-- when data key not found
			table.insert (in_data_only_t, '\t' .. index);						-- add to our list with leading tab
		end
	end

----------																		-- now see if we can find  ~/Configuration error_conditions keys in ~/data tables
	local in_cfg_only_t = {};													-- holds list of error and maint keys not found in ~/data
	for index, v_t in pairs (error_conditions_t) do
		if index:find ('err_', 1, true) then									-- do 'error' keys first
			if not data_err_cats_t[index] then									-- when config key not found
				table.insert (in_cfg_only_t, table.concat ({					-- add to our list
					'\t',														-- leading tag character
					index,														-- the key
					string.rep ('\t', 10),										-- a string of tab character
					'-- ',														-- comment token
					v_t.category,												-- and category name
				}));
			end
		else																	-- here for maint_ keys
			if not data_maint_cats_t[index] then								-- when config key not found
				table.insert (in_cfg_only_t, table.concat ({					-- add to our list
					'\t',														-- leading tag character
					index,														-- the key
					string.rep ('\t', 10),										-- a string of tab character
					'-- ',														-- comment token
					v_t.category,												-- and category name
				}));
			end
		end
	end

	local out_t = {};															-- final output goes here
	table.sort (in_data_only_t);												-- ascending sorts
	table.sort (in_cfg_only_t);

	table.insert (out_t, '<pre>');												-- open; wrap in <pre>...</pre> tags so tab characters are collapsed by browsers
	if 0 < #in_cfg_only_t then													-- if the table is not empty
		table.insert (out_t, 'error and maintenance category keys not found in [[Moduli:CS1 charts/data]]:');	-- add header at top of list
		table.insert (out_t, table.concat (in_cfg_only_t, '\n'));				-- add the list
		table.insert (out_t, '\n');												-- add an extra newline
	end
	if 0 < #in_data_only_t then													-- if the table is not empty
		table.insert (out_t, 'error and maintenance category keys not found in [[Moduli:Citation/CS1/Configuration]]:');	-- add header at top of list
		table.insert (out_t, table.concat (in_data_only_t, '\n'));				-- add the list
	end
	table.insert (out_t, '</pre>');												-- close
	return table.concat (out_t, '\n\n');										-- concatenate into a big string and done
end


--[[--------------------------< E X P O R T S >----------------------------------------------------------------
]]

return {
	bar_chart_create = bar_chart_create,
	pie_chart_create = pie_chart_create,
	
	cat_list_check = cat_list_check,
	}