local cfg = mw.loadData('Module:Quran/Configuration')
local gdata = mw.loadData('Module:Quran/data general')
local quran_data
local quran_norm
local aya_symbol_template = cfg.default.aya_symbol_template
local sour
local quran_shape = cfg.default.shape
local string = mw.ustring
local text = mw.text
local strspace = "[ " .. string.char(160) .. "]"
local lua_data = {Othmani = true, text = true,  KFGQPC = true}

local function eastern_numbers(thenumber)
	local str_number = tostring(thenumber)
	local ret_str = ''	
	for i=1,#str_number do
		ret_str = ret_str .. string.char(0x0660 + tonumber(string.sub(str_number,i,i)))
	end
	return ret_str
end

local function shape_numbers(thenumber)
	local str_number = tostring(thenumber)
	local numbers_shape = cfg.presentation.numbers_shape
	if numbers_shape == "" then
		return str_number
	end
	local ret_str = ''
	local c_n
	for i=1,#str_number do
		c_n = tonumber(string.sub(str_number,i,i))+1
		ret_str = ret_str .. string.sub(numbers_shape, c_n,c_n)
	end
	return ret_str
end

local gtonumber = tonumber

local function tonumber(str)
	if not str then
		return nil
	end
	local thenumber = gtonumber(str) or mw.language.getContentLanguage():parseFormattedNumber(str)
	if not thenumber then
		return nil
	end
	return math.floor(thenumber)
end

local function set_quran_shape(in_shape)
	if in_shape then
		for k, shapelist in pairs(cfg.shape_aliases) do
			for _, shapevalue in ipairs(shapelist) do
				if in_shape == shapevalue then
					quran_shape = k
					return
				end
			end
		end
	end
end

local function soura_count(soura_num)
	return gdata.aya_count[gdata.shape_def[quran_shape] and gdata.shape_def[quran_shape].aya_count or 1][soura_num]
end

local function quran_replaces(intext)
	local gsub = mw.ustring.gsub
	local char = mw.ustring.char

	if quran_shape == "KFGQPC" or quran_shape == "Warsh"  then
		intext = gsub(intext,'آ', 'آ')
	end

	if quran_shape == "Warsh" or quran_shape == "Qaloun" or quran_shape == "Douri" or quran_shape == "Sousi" or quran_shape == "Bazzi" or quran_shape == "Qunbul" then
		intext = gsub(intext,char(160) .. "َ۟", '&nbsp;۟&#xfeff;<span style="position: relative;top: -0.25em;left: -0.02em;">َ</span>') --اَ۟لِهَةٗ آية
	end

	if quran_shape == "Warsh" or quran_shape == "Qaloun" or quran_shape == "Bazzi" or quran_shape == "Qunbul" then
		intext = gsub(intext,char(160, 1648), '<span style="position: relative;top: -0.5em;letter-spacing: -0.12em;">&nbsp;ٰ</span>') -- ءَا۬ ٰمَنتُم
	end

	if quran_shape == "Warsh" or quran_shape == "Qaloun" then
		intext = gsub(intext, "ۓ", '&#x6D2;&#x654;')
		intext = gsub(intext, "ا۬"  .. char(65279, 1614), 'ا۬&#xfeff;<span style="position: relative;top: -0.8em;left: -0.05em;">َ</span>') --فتحة فوق سكون
		intext = gsub(intext, "ا۪"  .. char(847, 1616), 'ا۪&#x34f;<span style="position: relative;top: 0.05em;">ِ</span>') --فتحة فوق سكون
	end

	if quran_shape == "Warsh"  then
		--
		-- intext = gsub(intext, char(65279, 1619), '&#xFEFF;<span style="position: relative;top: -0.72em;left: 0.3em;">ٓ</span>') --المدة بعد لام ألف
		intext = gsub(intext, "و۬"  .. char(65279, 1614), 'و۬&#xfeff;<span style="position: relative;top: -0.5em;left: 0.075em;">َ</span>')
		intext = gsub(intext, "ي۬"  .. char(65279, 1614), 'ي۬ـ<span style="position: relative;top: -0.07em;left: 0.25em;">َ</span>')
	end

	if quran_shape == "Douri" or quran_shape == "Sousi" or quran_shape == "Bazzi" or quran_shape == "Qunbul" then
		intext = gsub(intext, "ا۪"  .. char(847, 1616), 'ا۪&#x34f;<span style="position: relative;top: 0.1em;left: -0.05em;">ِ</span>') --فتحة فوق سكون
		intext = gsub(intext, "ا۬"  .. char(847, 1614), 'ا۬&#x34f;<span style="position: relative;top: -0.92em;">َ</span>') --فتحة فوق سكون
	end

	if quran_shape == "Douri" or quran_shape == "Sousi"  then
		intext = gsub(intext, char(160, 1648), '<span style="position: relative;left: 0.05em;top: -0.085em;letter-spacing: -0.12em;">&nbsp;ٰ</span>') -- ءَا۬ ٰمَنتُم
	end

	return intext
end

local function substitute( msg, args )
	return args and mw.message.newRawMessage( msg, args ):plain() or msg;
end

local function error_comment(msg, args )
	return substitute( cfg.presentation.error, {mw.getCurrentFrame():getParent():getTitle() , substitute( msg, args )} );
end

local function some_aya(souranum,ayanum,start_word,end_word)
	if not start_word and not end_word then
		return quran_data[souranum][ayanum],true
	end
	local fullaya = true
	local ayaText = ' ' .. quran_data[souranum][ayanum] .. ' '
	local f,l,wb
	local fnorm=1
	local norm_ayaText
	if start_word then
		f=string.find(ayaText,' ' .. start_word .. ' ',1,true)
		if not f then
			if not quran_norm then
				quran_norm = mw.loadData('Module:Quran/data norm')
			end
			norm_ayaText=' ' .. quran_norm[souranum][ayanum] .. ' '
			f=string.find(string.gsub(norm_ayaText, string.char(160),' '), ' ' .. start_word .. ' ',1,true)
			fnorm=f
			if not f then
				return error_comment(cfg.msgs.from_word_err)
			end
			if quran_shape ~= 'text' and string.sub(norm_ayaText,f,f)==string.char(160) then
				ff=1
				tmp=1
				while tmp do
					tmp = string.find(string.sub(norm_ayaText,1,f),' ',ff+1,true)
					if tmp then
						ff=tmp
					end
				end
				f=ff
			end
			_, wb = string.gsub(string.sub(norm_ayaText,1,f-1),strspace," ")
			f=1
			for i=1,wb do
				f=string.find(ayaText," ",f+1)
			end
		end
	end
	f=f or 1
	if end_word then
		
		l=string.find(ayaText,' ' .. end_word .. ' ',f,true)
		if l then
			l=l + end_word:len()
		else
			if not quran_norm then
				quran_norm = mw.loadData('Module:Quran/data norm')
			end
			norm_ayaText=' ' .. quran_norm[souranum][ayanum] .. ' '
			l = string.find(string.gsub(norm_ayaText,string.char(160),' '),' ' .. end_word .. ' ',fnorm,true)
			if not l then
				return error_comment(cfg.msgs.to_word_err)
			end
			l=l + string.len(end_word)
			_, wb =string.gsub(string.sub(norm_ayaText,1,l-1),strspace," ")
			l=1
			for i=1,wb do
				l=string.find(ayaText, " ",l+1)
			end
		end
		if l<#ayaText then
			fullaya=false
		end
	end
	return text.trim(string.sub(ayaText,f,l)),fullaya
end

local function argument_wrapper(arg)
	local nilargs = {}
	return setmetatable({},
	{
		__index = function ( tbl, k )
			local v = rawget(tbl,k)
			if v then
				return v
			elseif nilargs[k] then
				return nil
			end
			local list = cfg.aliases[k];
			for _,arglist in ipairs(arg) do
				if type( list ) == 'table' then
					for _, alias_key in ipairs( list ) do
						if arglist[alias_key] then
							v = arglist[alias_key]
							break;
						end
					end
				elseif list ~= nil then
					v = arglist[list]
				end

				if v then
					break;
				end
			end
			if v == nil then
				nilargs[k] = true
			elseif string.len(v) == 0 then
				nilargs[k] = true
				v = nil
			else
				rawset( tbl, k, v )
			end
			return v
		end,
	});
end

local function soura_number( str_soura )
	for i=1,114 do
		if sour[i].name == str_soura then
			return i
		end
	end

	for i=1,114 do
		for _, v in ipairs(sour[i].search) do
			if v == str_soura then
				return i
			end
		end
	end

	return nil
end

local function arg2soura_num(arg_soura)
	local soura_num = tonumber(arg_soura)
	
	if soura_num and (soura_num < 1 or soura_num > 114) then
		return  0, error_comment(cfg.msgs.soura_num_err)
	end
	
	if not soura_num then
		soura_num = soura_number(arg_soura)
		if not soura_num then
			return 0, error_comment(cfg.msgs.soura_name_err)
		end
	end
	return soura_num, ""
end

local function load_data()
	if lua_data[quran_shape] then
		quran_data =  mw.loadData('Module:Quran/data ' .. quran_shape)
	else
		quran_data = mw.loadJsonData('Module:Quran/data ' .. quran_shape .. '.json')
	end
end

local function aya_number(aya_num, frame)
	if aya_symbol_template then
		if cfg.presentation[aya_symbol_template] then
			return substitute(cfg.presentation[aya_symbol_template], shape_numbers(aya_num))
		else
			return frame:expandTemplate{ title = aya_symbol_template, args = { aya_num } }
		end
	else
		return '&#x06DD;' .. eastern_numbers(aya_num)
	end
end

local function get_ayat(soura, start_aya, end_aya)
	local ret_text	= ''
	for aya_num = start_aya, end_aya do
		ret_text = ret_text .. quran_data[soura][aya_num] .. aya_number(aya_num)
	end
	return string.sub(ret_text,1,#ret_text -1)	
end

local function aya(frame)
	local A = argument_wrapper({frame.args, frame:getParent().args})
	local soura =  tonumber(A[1] or A.soura or A.s)
	local aya =  tonumber(A.from_aya or A.a)
	local ret = ''
	if soura> 114 or soura<1 then
		return error_comment(cfg.msgs.soura_num_err)
	end
	
	if A.shape then
		set_quran_shape(A.shape)
	end
	local soura_data = gdata.sour[soura]
	local aya_count = soura_count(soura)
	if aya <1 or aya > aya_count then
		return error_comment(cfg.msgs.from_aya_err,{soura_data.name,aya_count}) 
	end
	load_data()
	local ret = quran_data[soura][aya]
	if quran_shape == "KFGQPC" then
		ret= string.gsub(ret,'آ', '&#x627;&#x653;')
	end
	return ret
end

local function search(A, frame)
	local soura= A[1] or A.soura
	local start_soura
	local ret = ''
	sour = gdata.sour
	if soura then
		start_soura, errmsg = arg2soura_num(soura)
		if start_soura == 0 then
			return errmsg
		end
	end
	quran_norm = mw.loadData('Module:Quran/data norm')
	start_soura = start_soura or 1
	for soura_num=start_soura,114 do
		for aya_num=1,sour[soura_num].ayacount do
			if quran_norm[soura_num][aya_num]:gsub("\160"," "):find(A.search) then
				if not quran_data then load_data() end
				ret = ret .. "*" .. substitute(cfg.presentation.quran, { quran_shape, quran_data[soura_num][aya_num] .. ' ' .. aya_number(aya_num,frame)}) .. substitute(cfg.presentation.cite_quran, { sour[soura_num].name, ':' .. aya_num}) .. "\n"
				if A.viewtemplate and A.viewtemplate=="1" or A.viewtemplate=="نعم" then
					ret = ret .. substitute("<pre>{{قرآن|$1|$2}}</pre>",{soura_num,aya_num}) .. "\n"
				end
			end
		end
	end
	if #ret==0 then
		return error_comment("لا نتائج")
	else
		return ret
	end
end

local function ayat(frame)
	local A = argument_wrapper({frame:getParent().args, frame.args})
	
	if A.shape and string.len(A.shape)>0 then
		set_quran_shape(A.shape)
		if quran_shape=='Text' then
			strspace = "[ " .. string.char(160) .. "]"
		else
			strspace = " "
		end
	end

	if A.aya_template and (cfg.presentation[A.aya_template] or mw.title.new("template:" .. A.aya_template).exists) then
		aya_symbol_template = A.aya_template
	end
	
	if A.search and string.len(A.search)>0 then
		return search(A,frame)	
	end

	sour = gdata.sour
	local ret_text = ""

	if A.from_aya and string.len(A.from_aya)> 0 then
		local soura_num, soura, from_aya, to_aya, errmsg
		soura_num, errmsg = arg2soura_num(A[1] or A.soura)
		if soura_num == 0 then
			return errmsg
		end

		soura = sour[soura_num]
		local aya_count = soura_count(soura_num)
		from_aya =  tonumber(A.from_aya)
		if not from_aya or from_aya>aya_count or from_aya<1 then
			return error_comment(cfg.msgs.from_aya_err,{soura.name,aya_count}) 
		end
		local arg_aya_add = A.addition_ayat
		if arg_aya_add then
			to_aya = from_aya + (tonumber(arg_aya_add) or 0)
		elseif A.to_aya then
			to_aya = tonumber(A.to_aya)
		else
			to_aya = from_aya
		end

		if to_aya > aya_count or to_aya<1 then
			return error_comment(cfg.msgs.to_aya_err, {soura.name,aya_count})
		end

		load_data()
		for aya_num = from_aya, to_aya do
			local fullaya=true
			if (aya_num==from_aya or aya_num == to_aya) and (A.from_word or A.to_word) then
				local temp_text
				temp_text, fullaya = some_aya(soura_num,aya_num, 
					(aya_num==from_aya and A.from_word or nil), 
					(aya_num == to_aya and A.to_word or nil))
				if temp_text:find("error") then
					return temp_text
				end
				ret_text = ret_text .. temp_text
			else
				ret_text = ret_text .. quran_data[soura_num][aya_num]
			end
			
			if fullaya then
				ret_text = ret_text .. ' ' .. aya_number(aya_num, frame) .. ' '
			end
		end
		
		ret_text = quran_replaces(ret_text)
		
		return substitute(cfg.presentation.quran, { quran_shape, text.trim(ret_text) }) .. substitute(cfg.presentation.cite_quran, { soura.name, ':' .. from_aya .. ((to_aya>from_aya) and ("–" .. to_aya) or '')})
	
	else
		ret_text = substitute(cfg.presentation.quran, { quran_shape, A[1] or A.user_text })
		if A.s and string.len(A.s)>0 then
			local soura_num, errmsg = arg2soura_num(A.s)
			if soura_num == 0 then
				return errmsg
			end
			local soura=sour[soura_num]
			ret_text = ret_text .. substitute(cfg.presentation.cite_quran, { soura.name, (A.a and (":" .. A.a) or "")})
		end
		return ret_text
	end
end

local function number_of_soura( frame )	
	local idx = tonumber(frame.args[1])
	if idx and idx>=1 and idx <=114 then
		return idx
	end
	sour = gdata.sour
	return soura_number(frame.args[1])
end


local function soura_name( frame )
	sour = gdata.sour
	local idx = arg2soura_num(frame.args[1]) + (tonumber(frame.args[2]) or 0)
	if not idx or idx<1 or idx > 114 then
		return nil
	end
	return sour[idx].name
end

local function aya_count( frame )
	local idx = tonumber(frame.args[1])
	if not idx or idx<1 or idx > 114 then
		return nil
	end
	local argshape = frame:getParent().args[2] or frame.args[2]
	if argshape then
		set_quran_shape(argshape)
	end
	return soura_count(idx)
end




local function ws_soura(frame)
	quran_shape = "Othmani"
	local A = argument_wrapper({frame:getParent().args, frame.args})

	if A.shape and string.len(A.shape)>0 then
		set_quran_shape(A.shape)
		if quran_shape=='Text' then
			strspace = "[ " .. string.char(160) .. "]"
		else
			strspace = " "
		end
	end

	if quran_shape == "DKhatt" then
		aya_symbol_template = nil
	end
	local soura_num, soura,hzb, soura_hzb, errmsg

	sour = gdata.sour
	local ret_text = ""
	soura_num, errmsg = arg2soura_num(frame.args[1])
	if soura_num == 0 then
		return errmsg
	end
	hzb = gdata.ahzab[gdata.shape_def[quran_shape] and gdata.shape_def[quran_shape].hzb or 1]
	soura_hzb = hzb and hzb[soura_num] or nil
	local hzb_counter

	-- if soura_hzb and hzb.addition then
	-- 	for _,v in ipairs(soura_hzb) do
	-- 		hzb_counter = v
	-- 		break
	-- 	end
	-- end

	load_data()
	local aya_num = 1
	local bsmala = gdata.bsmala[quran_shape]
	-- إضافة البسملة لغير الفاتحة أو التوبة
	if not (soura_num == 9) then
		if soura_num==1 and bsmala.s1aya then
			ret_text = '<center>' .. bsmala[bsmala.dif[soura_num] or 1] .. '&nbsp;' .. aya_number(aya_num, frame) .. '</center>'
			aya_num = 2
		else
			ret_text = '<center>' .. bsmala[bsmala.dif[soura_num] or 1] .. '</center>'
		end
	end

	while quran_data[soura_num][aya_num] do
		local hzb_text = ""
		local aya_text = quran_data[soura_num][aya_num]
		local hzbargs
		if soura_hzb then
			if soura_hzb[aya_num] or (hzb.addition and string.find(aya_text, "۞")) then
				hzb_counter = soura_hzb[aya_num] or (hzb_counter + hzb.addition)
				hzbargs = {
					math.floor(hzb_counter/ (hzb.hparts *  2) + 1),
					math.floor(hzb_counter/hzb.hparts) + 1,
				}
				local hzb_msg = ""
				local hl = 3
				if hzb_counter % (2 * hzb.hparts) == 0 then
					hzb_msg = 'الجزء&nbsp;'  .. hzbargs[1] .. "، الحزب&nbsp;" ..  hzbargs[2]
					hl=2
				else
					hzb_msg = mw.message.newRawMessage (hzb.fra[hzb_counter% hzb.hparts], hzbargs):plain()
				end

				if aya_num>1 then
					hzb_text = '<span class="start-of-rub" title="' .. hzb_msg .. '">۞</span>'
				end

				hzb_text = hzb_text .. '<span class="hzb-fra">' .. hzb_msg  .. '</span>&nbsp;'
			end
		end

		if hzb_text ~="" then
			local r = 0
			if aya_num > 1 then
				aya_text, r =string.gsub(aya_text, "۞ ", hzb_text)
			end
			if r==0 then
				aya_text = hzb_text .. aya_text
			end
		end

		if  string.sub(aya_text,-1) == '۩' then
			aya_text = aya_text .. '<span class="hzb-fra">سجدة</span>'
		end
		ret_text = ret_text .. aya_text .. '&nbsp;' .. aya_number(aya_num, frame) .. ' '
		aya_num = aya_num + 1
	end

	ret_text= quran_replaces(ret_text)
	return '<div class="soura-block quran-' .. quran_shape .. '">' ..  text.trim(ret_text) .. '</div>'
end

local function ws_soura_header(frame)
	quran_shape = "Othmani"
	local A = argument_wrapper({frame:getParent().args, frame.args})
	local basepage = mw.title.getCurrentTitle().basePageTitle.fullText
	local bsae2shape = {
		["القرآن الكريم (بالرسم الإملائي)"] = "text",
		["القرآن الكريم (حفص)"] = "Othmani",
		["القرآن الكريم (حفص، المدينة النبوية)"] = "KFGQPC",
		["القرآن الكريم (ورش)"] = "Warsh",
		["القرآن الكريم (قالون)"] = "Qaloun",
		["القرآن الكريم (شعبة)"] = "Shuba",
		["القرآن الكريم (الدوري)"] = "Douri",
		["القرآن الكريم (السوسي)"] = "Sousi",
		["القرآن الكريم (البزي)"] = "Bazzi",
		["القرآن الكريم (قنبل)"] = "Qunbul"
	}
	local argshape = frame:getParent().args[2] or frame.args[2] or bsae2shape[basepage]
	if argshape then
		set_quran_shape(argshape)
	end
	sour = gdata.sour
	local suraname = frame.args[1] or mw.ustring.gsub(mw.title.getCurrentTitle().subpageText,'سورة ','')
	local soura_num, errmsg = arg2soura_num(suraname)
	if soura_num == 0 then
		return errmsg
	end
	local args={}
	if soura_num >1 then
		args["سابق"] = '[[' .. mw.title.getCurrentTitle().rootPageTitle.fullText .. '/سورة ' .. sour[soura_num-1].name ..'|سورة ' .. sour[soura_num-1].name ..']]'
	end
	args["عنوان"] = "[[" .. mw.title.getCurrentTitle().basePageTitle.fullText .. "|القرآن الكريم]]"
	args["عنوان فرعي"] = gdata.shape_desc[quran_shape]
	args["باب"]="سورة " .. sour[soura_num].name
	if soura_num <114 then
		args["تالي"] = '[[' .. mw.title.getCurrentTitle().rootPageTitle.fullText .. '/سورة ' .. sour[soura_num+1].name ..'|سورة ' .. sour[soura_num+1].name ..']]'
	end
	args["ملاحظات"] = 'آياتها ' .. soura_count(soura_num) .. "، يتوفر نص السورة أيضًا [[القرآن الكريم (توضيح)/سورة "  .. sour[soura_num].name .. "|بروايات أخرى]]"
-- 	local ret_text = [[<div class="ws-noexport">
-- {| class="headertemplate" id="headertemplate"
-- |-
-- | class="header_backlink" |]]
-- 	if soura_num >1 then
-- 		ret_text = ret_text .. '→ [[' .. mw.title.getCurrentTitle().rootPageTitle.fullText .. '/سورة ' .. sour[soura_num-1].name ..'|' .. 'سورة ' .. sour[soura_num-1].name ..']]'
-- 	end
-- 	ret_text = ret_text .. "\n| class=\"header_title\" | '''[[" .. mw.title.getCurrentTitle().basePageTitle.fullText .. "|القرآن الكريم]]'''\n\nسورة " .. sour[soura_num].name .. '<br>آياتها ' .. soura_count(soura_num)
-- 	ret_text = ret_text .. '\n| class="header_forelink" |'
-- 		ret_text = ret_text .. '[[' .. mw.title.getCurrentTitle().rootPageTitle.fullText .. '/سورة ' .. sour[soura_num+1].name ..'|' .. 'سورة ' .. sour[soura_num+1].name ..']] ←'
-- 	end
-- 	ret_text = ret_text .. '\n|}\n{| class="header_notes"\n|-\n|' .. gdata.shape_desc[quran_shape] .. "، يتوفر نص السورة أيضًا [[القرآن الكريم (توضيح)/سورة "  .. sour[soura_num].name .. "|بروايات أخرى]]\n|}</div>"
	return frame:expandTemplate{ title = "ترويسة", args=args }
end

local function test()
	return "OK"
end


return {
	aya = aya,
	ayat=ayat,
	number_of_soura = number_of_soura,
	soura_name = soura_name,
	aya_count = aya_count,
	ws_soura = ws_soura,
	ws_soura_header = ws_soura_header,
	test = test
}