• Ἀβαῖσι (1)
  • ἀγέλαις (1)
  • ἀγκάλαις (1)
  • ἀγόνοις (1)
  • ἀγοραῖσι (1)
  • ἀγρίαις (1)
  • ἀγρίοις (1)
  • ἀγροῖς (1)
  • ἀγρονόμοις (1)
  • αἱματηραῖς (1)
  • αἰώραισιν (1)
  • ἀλαοῖς (1)
  • ἀλαστόροισιν (1)
  • ἀλγύνοις (1)
  • ἀλλήλοισιν (2)
  • ἄλλοις (2)
  • ἄλλοισι (1)
  • ἄλλοισιν (2)
  • ἀλλοτρίοις (1)
  • ἀμαυραῖς (1)
  • ἀμφιδεξίοις (1)
  • ἀνθρώποις (3)
  • ἀνθρώποισι (3)
  • ἀνθρώποισιν (4)
  • ἀνοίξῃς (1)
  • ἀνοσίοις (1)
  • ἄντροις (1)
  • ἀξιοῖς (1)
  • ἀπειλαῖς (1)
  • ἀργοῖς (1)
  • ἀρίστοις (1)
  • ἀῤῥήτοις (1)
  • ἀρτάναισι (1)
  • ἀρχαίοις (1)
  • ἀρχαῖς (2)
  • ἄρχοις (1)
  • ἀστοῖς (4)
  • ἀστοῖσι (1)
  • ἀστοῖσιν (1)
  • ἄστροις (1)
  • ἀσχάλλοις (1)
  • ἄταις (1)
  • ἀτιμάσῃς (1)
  • αὐταῖς (1)
  • αὑταῖς (1)
  • αὐτοῖς (6)
  • αὐτοῖσι (1)
  • αὐτοῖσιν (1)
  • ἀφωνήτοις (1)
  • βάθροισι (1)
  • βασιλείοισιν (1)
  • βεβήλοις (1)
  • βουνόμοις (1)
  • βροτοῖς (2)
  • βροτοῖσιν (1)
  • βωμοῖσι (2)
  • γάμοις (1)
  • γάμοισιν (1)
  • γνώμαις (1)
  • γοναῖσιν (1)
  • γόοις (1)
  • γόοισιν (1)
  • δαμούχοις (1)
  • δείπνοις (1)
  • δηλοῖς (2)
  • δημόταις (1)
  • δημούχοις (1)
  • δικαίοις (1)
  • διπλαῖς (1)
  • διπλοῖς (2)
  • διπλοῖσι (1)
  • δισσοῖσι (1)
  • δμωαῖς (1)
  • δμωαῖσι (1)
  • δόλοισιν (1)
  • δόμοις (8)
  • δόμοισι (2)
  • δόμοισιν (1)
  • δόξῃς (1)
  • δυσπνόοις (1)
  • ἐγκάρποις (1)
  • εἴποις (2)
  • ἐκδείξῃς (1)
  • ἐκείνοισιν (1)
  • ἐκμάθῃς (1)
  • ἕλοις (1)
  • ἐμαῖς (4)
  • ἐμοῖς (5)
  • ἐμοῖσι (1)
  • ἐμοῖσιν (1)
  • ἐμπέδοις (1)
  • ἐμπείροις (1)
  • ἐμπείροισι (1)
  • ἐξείπῃς (1)
  • ἐξετάσῃς (1)
  • ἐξεύροις (1)
  • ἐξισώσῃς (1)
  • ἐπαίνοις (1)
  • ἐπεντέλλοις (1)
  • ἐπιῤῥόθοις (1)
  • ἐπίσχῃς (1)
  • ἐπῳδαῖς (1)
  • ἔργοις (3)
  • ἔργοισιν (2)
  • ἑτέραις (1)
  • εὐχαῖσι (1)
  • ἐχθροῖς (1)
  • ἐχθροῖσιν (1)
  • θαλλοῖς (1)
  • θαλλοῖσιν (1)
  • θάνῃς (1)
  • θεαῖς (2)
  • θέλῃς (2)
  • θέλοις (2)
  • θεοῖς (7)
  • θεοῖσι (4)
  • θήβαις (1)
  • Θήβαις (3)
  • Θήβαισιν (1)
  • θνητοῖς (1)
  • Θρῄσσαισιν (1)
  • θρόνοις (2)
  • θυέλλαισιν (1)
  • ἴδοις (3)
  • ἱκτηρίοις (1)
  • ἵπποισιν (1)
  • ἱπποκόμοις (2)
  • ἰσοθέοις (1)
  • Καδμείοις (2)
  • Καδμείοισι (2)
  • κακοῖς (9)
  • κείνοις (4)
  • κερτομίοις (2)
  • κηρύξῃς (1)
  • κλάδοισιν (1)
  • κλεινοῖς (1)
  • κομίζοις (1)
  • Κρεοντείοις (1)
  • κρωσσοῖς (1)
  • κτάνῃς (1)
  • κύκλοις (1)
  • Λαβδακίδαις (2)
  • λάβῃς (2)
  • λάβοις (3)
  • λέγῃς (1)
  • λέγοις (3)
  • λιταῖς (2)
  • λόγοις (4)
  • λόγοισι (2)
  • λόγοισιν (1)
  • λόγχαις (2)
  • λουτροῖς (1)
  • λύπαισι (1)
  • μάθῃς (1)
  • μάθοις (1)
  • μαλακαῖς (1)
  • μανίαις (1)
  • ματεύσῃς (1)
  • μεγάλαισιν (1)
  • μέλλῃς (1)
  • μηχαναῖς (1)
  • μισθοῖσιν (1)
  • μόλῃς (1)
  • μόνοις (4)
  • μόχθοις (1)
  • μυχοῖς (1)
  • ναπαίαις (1)
  • νεαραῖσι (1)
  • νεκροῖς (2)
  • νεοτόμοισι (1)
  • νόμοις (3)
  • νόμοισι (1)
  • νόμοισιν (1)
  • νόσοις (3)
  • ξυμφοραῖς (1)
  • ὁδοῖς (3)
  • οἰκείοις (1)
  • οἰκείοισιν (1)
  • οἴκοις (1)
  • οἴκοισιν (1)
  • οἵοις (1)
  • οἰωνοῖς (1)
  • ὀλβίοις (1)
  • ὄμβροις (1)
  • ὀνειδίζοις (1)
  • ὀργαῖς (1)
  • ὀρείοις (1)
  • ὀρθοῖς (1)
  • ὅσοις (1)
  • ὄσσοισι (1)
  • ὅτοισι (1)
  • ὀφθαλμοῖς (2)
  • παγκλαύτοις (1)
  • παῖς (7)
  • πανάρχοις (1)
  • παννυχίοις (1)
  • παρειαῖς (1)
  • παρελθούσαις (1)
  • παρῇς (1)
  • παρθένοις (1)
  • πατρῴαις (1)
  • περιβρυχίοισιν (1)
  • περιίδῃς (1)
  • περιπόλοις (1)
  • περιτελλομέναις (1)
  • πιστοῖσι (1)
  • πλεκταῖσιν (2)
  • πλευραῖς (1)
  • ποίμναις (1)
  • ποιμνίοις (1)
  • ποίοις (1)
  • ποίοισι (1)
  • πολλοῖς (3)
  • πολλοῖσι (1)
  • πολλοῖσιν (1)
  • πολυξένοις (1)
  • πομποῖσιν (1)
  • πόνοις (2)
  • ποντίαις (1)
  • πόροις (1)
  • προδείξῃς (1)
  • προμηνύσῃς (1)
  • προπέσῃς (1)
  • προσβάλῃς (1)
  • προσίδοις (1)
  • προύχοις (1)
  • πρώταισι (1)
  • πρώταισιν (1)
  • Πυθίαις (1)
  • πύλαις (2)
  • πύλαισι (1)
  • πώλοισιν (1)
  • ῥείθροισι (1)
  • ῥιμφαρμάτοις (1)
  • ῥιπαῖς (1)
  • σαῖς (3)
  • σαῖσιν (1)
  • σεμναῖσι (1)
  • σμικροῖς (1)
  • σοῖς (6)
  • σοῖσιν (2)
  • σπανιστοῖς (1)
  • σπείραισι (1)
  • στεναγμοῖς (1)
  • στερηθῇς (1)
  • στεροπαῖς (1)
  • στήσῃς (1)
  • συμφοραῖς (1)
  • συντυχίαις (1)
  • ταις (1)
  • ταῖς (13)
  • ταῖσι (2)
  • τάφοις (1)
  • τάφοισι (1)
  • τέκνοις (1)
  • τέκνοισι (2)
  • τηλεπόροις (1)
  • τιμαῖς (2)
  • τοιούτοισιν (1)
  • τοῖς (64)
  • τοῖσι (6)
  • τοῖσιν (7)
  • τόκοισιν (1)
  • τόποισι (1)
  • τούτοις (3)
  • τούτοισι (1)
  • τούτοισιν (1)
  • τριπλαῖς (3)
  • τρισπόνδοισι (1)
  • τρόποισι (1)
  • τροφαῖς (1)
  • τυφλοῖσι (1)
  • τύχαις (1)
  • τύχοις (1)
  • ὑπερτάτοις (1)
  • ὑπτίοις (1)
  • φίλοις (2)
  • φίλοισιν (1)
  • φιλτάτοις (1)
  • Φινείδαις (1)
  • φόβοισι (1)
  • φοινίαισι (1)
  • φοναῖς (3)
  • φονώσαισιν (1)
  • φυγαῖσιν (1)
  • χαλκοδέτοις (1)
  • χαλκοῖς (1)
  • χἀτέροις (1)
  • χειμάῤῥοις (1)
  • χηλαῖσιν (1)
  • χλωραῖς (1)
  • χοαῖσι (1)
  • χοροῖς (1)
  • χρησμοῖσιν (1)
  • χώροις (1)
  • ψόγοισι (1)
  • ψυχροῖσι (1)
  • ὥραις (1)
  • ᾽μβαίνῃς (1)
  • ᾽φευρεθῇς (1)

local export = {}

-- local word_limit = 200

local m_fun = require "Module:fun"
local m_table = require "Module:table"

local decompose = mw.ustring.toNFD

local U = mw.ustring.char
local acute = U(0x301)
local grave = U(0x300)
local circumflex = U(0x342)
local diacritic = "[\204-\205][\128-\191]"
local UTF8_char = "[%z\1-\127\194-\244][\128-\191]*"

-- excluding first line of Greek and Coptic block: ͰͱͲͳʹ͵Ͷͷͺͻͼͽ;Ϳ
local basic_Greek = "[\206-\207][\128-\191]"

local semicolon = "·"

local function count_table()
	return setmetatable(
		{},
		{
			__index = function(self, key)
				self[key] = 0
				return 0
			end
		})
end

local function match_to_array(str, patt, filter_func, process_func)
	local array = {}
	local i = 0
	for match in str:gmatch(patt) do
		if filter_func(match) then
			i = i + 1
			--[[
			if i == word_limit then
				break
			end
			--]]
			array[i] = process_func(match)
		end
	end
	return array
end

local function count_matches(str, patt, filter_func, process_func, count_map)
	local count_map = count_map or count_table()
	local i = 0
	for match in str:gmatch(patt) do
		if filter_func(match) then
			i = i + 1
			---[[
			if i == word_limit then
				break
			end
			--]]
			match = process_func(match)
			count_map[match] = count_map[match] + 1
		end
	end
	return count_map
end

local replacements = {
	[grave] = acute,
	["["] = "",
	["]"] = "",
	["'"] = "’",
	["\""] = "",
	["("] = "",
	[")"] = "",
	[","] = "",
	[semicolon] = "",
	["."] = "",
	["«"] = "",
	["»"] = "",
	
}

local function contains_nonASCII(text)
	return text:find("[\128-\255]") -- and true or false
end

local found_accent
local process_word = m_fun.memoize(function (word)
	found_accent = false
	
	return decompose(word)
		:gsub(UTF8_char, replacements)
		:gsub("’’", "")
		
		-- Remove all but the first accent
		-- Ἀθηναίοισί -> Ἀθηναίοισι
		:gsub(
			diacritic,
			function (diacritic)
				if diacritic == acute or diacritic == circumflex then
					if found_accent then
						return ""
					end
					
					found_accent = true
					return diacritic
				end
			end)
end)

-- No macrons or breves in Odyssey text.
local function make_entry_name(word)
	return word:gsub("’", "'")
end

local function link(text)
	return '<span class="polytonic" lang="grc">[[' .. make_entry_name(text)
		.. '#Ancient Greek|' .. text .. ']]</span>'
end

local function tag(text)
	return '<span lang="grc">' .. text .. '</span>'
end

local function count(array)
	local count_map = count_table()
	for _, item in ipairs(array) do
		count_map[item] = count_map[item] + 1
	end
	return count_map
end

local function process_count(count, word)
	return "* " .. word .. " (" .. count .. ")"
end

local ugsub		= mw.ustring.gsub
local ulower	= mw.ustring.lower
local decompose = mw.ustring.toNFD
local remove_diacritics = m_fun.memoize(function (word)
	return decompose(ulower(word)):gsub(diacritic, "")
end)

-- Return the word minus Greek characters, which hopefully is a sequence of diacritics.
local diacritic_sortkey = m_fun.memoize(function(word)
	return decompose(word):gsub(basic_Greek, "")
end)

local function count_comp_gen(count)
	return function(word1, word2)
		local count1, count2 = count[word1], count[word2]
		if count1 == count2 then
			return remove_diacritics(word1) < remove_diacritics(word2)
		else
			return count1 > count2
		end
	end
end

local function get_words_per_count(count)
	local result = count_table()
	for word, count in pairs(count) do
		result[count] = result[count] + 1
	end
	
	return result
end

local function get_Greek_words(title)
	return match_to_array(
		mw.title.new(title)
			:getContent()
			:gsub("{{[^}]+}}", "")
			:gsub("<([^>]+) ?[^>]*>[^<]+</%1>", "")
			:gsub("<[^>]+>", ""),
		"%S+",
		contains_nonASCII,
		process_word)
end

local function and_combine(func1, func2)
	return function (...)
		return func1(...) and func2(...)
	end
end

local function get_processed_page_text(full_page_name)
	local title = mw.title.new(full_page_name)
	if title.isRedirect then
		title = title.redirectTarget
	end
	
	return assert(title:getContent(), "Could not get content for '" .. tostring(full_page_name) .. "'.")
		:gsub("{{[^}]+}}", "")
		:gsub("<([^>]+) ?[^>]*>[^<]+</%1>", "")
		:gsub("<[^>]+>", "")
end

local function count_Greek_words(title, filter)
	filter = filter and and_combine(contains_nonASCII, filter) or contains_nonASCII
	
	if type(title) == "table" then
		local count_map = count_table()
		
		for _, title in ipairs(title) do
			count_matches(
				get_processed_page_text(title),
				"%S+",
				filter,
				process_word,
				count_map)
		end
		
		return setmetatable(count_map, nil)
	else
		return setmetatable(
			count_matches(
				get_processed_page_text(title),
				"%S+",
				filter,
				process_word),
			nil)
	end
end

function export.show(frame)
	return table.concat(
		m_fun.mapIter(
			function (words_with_count, count)
				return "* " .. words_with_count .. " word"
					.. (words_with_count == 1 and " was" or "s were")
					.. " found " .. count .. " time" .. (count == 1 and "" or "s")
			end,
			pairs(
				get_words_per_count(
					count_Greek_words("Ιστορίαι (Ηροδότου)/Κλειώ")))),
		"\n")
end

local function gmatch_page_content(page, pattern)
	return mw.title.new(page):getContent():gmatch(pattern)
end

local function get_titles(author)
	local titles = {}
	if author == "Thucydides" then
		for subpage in gmatch_page_content("Ιστορία του Πελοποννησιακού Πολέμου", "%[%[(/[^|]+)") do
			table.insert(titles, "Ιστορία του Πελοποννησιακού Πολέμου" .. subpage)
		end
	elseif author == "Herodotus" then
		for subpage in gmatch_page_content("Ιστορίαι (Ηροδότου)", "%[%[(/[^|]+)") do
			table.insert(titles, "Ιστορίαι (Ηροδότου)" .. subpage)
		end
	elseif author == "Sophocles" then
		for page in gmatch_page_content("Συγγραφέας:Σοφοκλής", "%*%s*%[%[([^|%]]+)") do
			table.insert(titles, page)
		end
	else
		error("No method for getting titles of author '" .. tostring(author) .. "'.")
	end
		
	return titles
end

function export.show_wordlist(frame)
	--[[
	local endings = m_fun.map(
		function(ending)
			return ending .. "$"
		end,
		{ "εω", "εως", "εων" })
	]]
	local titles = get_titles "Sophocles"
	
	local ufind = mw.ustring.find
	local count_map = count_Greek_words(
		titles,
		function (word)
			return ufind(word, "[αο][ιῖ][σς]ι?ν?$") or ufind(word, "[ῃῇ][σς]ι?ν?$")
		end)
	
	return table.concat(
		m_fun.mapIter(
			process_count,
			m_table.sortedPairs(
				count_map,
				function (word1, word2)
					local without_diacritics1, without_diacritics2 =
						remove_diacritics(word1), remove_diacritics(word2)
					if without_diacritics1 ~= without_diacritics2 then
						return without_diacritics1 < without_diacritics2
					else
						return diacritic_sortkey(word1) < diacritic_sortkey(word2)
					end
				end)),
		"\n")
end

return export