Документация
local p = {}

local function First_less_Second(a, b)  
    local LenA = mw.ustring.len(a)
    local LenB = mw.ustring.len(b)
    for i = 1, (LenA < LenB) and LenA or LenB do
        if mw.ustring.codepoint(a, i, i) ~= mw.ustring.codepoint(b, i, i) then
            return mw.ustring.codepoint(a, i, i) < mw.ustring.codepoint(b, i, i) 
        end
    end
    return LenA < LenB
end  

local function internalFormatNumber(a_number, a_decimalMark, a_groupMark, a_groupMinLength, a_groupOnlyIntegerPart)
    -- find the decimal point
    local decimalPosition = mw.ustring.find(a_number, ".", 1, true);
    local needsGrouping = false;
    local DIGIT_GROUPING_SIZE = 3
    if (not decimalPosition) then
        -- no decimal point - integer number
		decimalPosition = mw.ustring.len(a_number) + 1;
		if (decimalPosition > a_groupMinLength) then
			needsGrouping = true;
		end
	else
		-- decimal point present
		if ((decimalPosition > a_groupMinLength) or (((mw.ustring.len(a_number) - decimalPosition) > DIGIT_GROUPING_SIZE) and (not a_groupOnlyIntegerPart))) then
			needsGrouping = true;
		end
		-- replace the decimal point
		a_number = mw.ustring.sub(a_number, 1, decimalPosition - 1) .. a_decimalMark .. mw.ustring.sub(a_number, decimalPosition + 1);
	end
	if (needsGrouping and (decimalPosition > DIGIT_GROUPING_SIZE + 1)) then
		-- grouping of integer part necessary
		local i = decimalPosition - DIGIT_GROUPING_SIZE;
		while (i > 1) do
			-- group the integer part
			a_number = mw.ustring.sub(a_number, 1, i - 1) .. a_groupMark .. mw.ustring.sub(a_number, i);
			decimalPosition = decimalPosition + mw.ustring.len(a_groupMark);
			i = i - DIGIT_GROUPING_SIZE;
		end
	end
	-- skip to the end of the new decimal mark (in case it is more than one char)
	decimalPosition = decimalPosition + mw.ustring.len(a_decimalMark) - 1;
	if (a_groupOnlyIntegerPart) then
		needsGrouping = false;
	end
	if (needsGrouping and ((mw.ustring.len(a_number) - decimalPosition) > DIGIT_GROUPING_SIZE)) then
		-- grouping of fractional part necessary
		-- using negative numbers (index from the end of the string)
		local i = decimalPosition - mw.ustring.len(a_number) + DIGIT_GROUPING_SIZE;
		while (i <= -1) do
			-- group the fractional part
			a_number = mw.ustring.sub(a_number, 1, i - 1) .. a_groupMark .. mw.ustring.sub(a_number, i);
			i = i + DIGIT_GROUPING_SIZE;
		end
	end
	return a_number;
end

-- from de:Modul:FormatNum
function formatNum(number)    
	if (number) then
        number = tostring(number)
		format = {decimalMark = ",", groupMark = "&nbsp;", groupMinLength = 5, groupOnlyIntegerPart = true}
		-- lua can parse the number (first check passed) and format entry found
		local sign = mw.ustring.sub(number, 1, 1);
		if ((sign == "+") or (sign == "-")) then
			-- remove sign from number, add it later again
			number = mw.ustring.sub(number, 2);
		else
			-- was not a sign
			sign = "";
		end
		if (mw.ustring.sub(number, 1, 1) == ".") then
			-- number begins with "." -> add a 0 to the beginning
			number = "0" .. number;
		else
			if (mw.ustring.sub(number, -1) == ".") then
				-- number ends with "." -> remove it
				number = mw.ustring.sub(number, 1, -2);
			end
		end
		if ((number == mw.ustring.match(number, "^%d+$")) or (number == mw.ustring.match(number, "^%d+%.%d+$"))) then
			-- number has valid format (only digits or digits.digits) -> format it and add sign (if any) again
			number = sign .. internalFormatNumber(number, format.decimalMark, format.groupMark, format.groupMinLength, format.groupOnlyIntegerPart);
		else
			-- number has no valid format -> undo all modifications
			number = a_frame.args["number"];
		end
	end	   
	return number;
end

local byte = string.byte
local gmatch = string.gmatch
-- Forward references --
local tab = {  -- tab[i][j] = xor(i-1, j-1)
  {0, 1, 2, 3, 4, 5, 6, 7, },
  {1, 0, 3, 2, 5, 4, 7, 6, },
  {2, 3, 0, 1, 6, 7, 4, 5, },
  {3, 2, 1, 0, 7, 6, 5, 4, },
  {4, 5, 6, 7, 0, 1, 2, 3, },
  {5, 4, 7, 6, 1, 0, 3, 2, },
  {6, 7, 4, 5, 2, 3, 0, 1, },
  {7, 6, 5, 4, 3, 2, 1, 0, },
}
 
local function bxor (a,b)
  local res, c = 0, 1
  while a > 0 and b > 0 do
    local a2, b2 = a%8, b%8;
    res = res + tab[a2+1][b2+1]*c
    a,b,c = (a-a2)/8 , (b-b2)/8, c*8
  end
  res = res + a*c + b*c 
  return res
end
--local function band (a,b) return ((a+b) - bxor(a,b))/2 end
-- другая реализация band(hash * 16777619, 2^32 - 1)
local function hack(hash)
	local mas={ 0x80000000,  0xC0000000,  0x60000000,  0x30000000,  0x98000000,  0x4C000000,  0x26000000,  0x93000000,  0xC9800000,  0x64C00000,  0x32600000,  0x19300000,  0xC980000,  0x64C0000,  0x3260000,  0x1930000,  0xC98000,  0x64C000,  0x326000,  0x193000,  0xC9800,  0x64C00,  0x32600,  0x19300,  0x8000C980,  0x400064C0,  0x20003260,  0x10001930,  0x8000C98,  0x400064C,  0x2000326,  0x1000193, };
	local result ,i, step = 0, 1, 0x80000000;
	while (hash>0) do
		if hash >= step then
			result  = result + mas[i];
			hash = hash - step;
		end
		i , step = i+1 , step/2;
	end
	while (result>=0x100000000) do result = result - 0x100000000; end
	return result
end
 
function p.FNV32_1A (str)
local hash = 2166136261
for char in gmatch(str,".") do
hash = bxor(hash, byte(char))
hash = hack(hash) --hash = band(hash * 16777619, 2^32 - 1)
end
return hash
end
local i=0;
function p.hash(str)
	str = p.FNV32_1A(str);
	return str;
end
-- извращаемся
function decrypt(data)
	local i,j=4,2; local result = {data,}
	while data[i]~=nil do 
		result[j] = { data[i] , data[i+1] , data[i+2] };
		i = i + 3 ;j=j+1;
	end
	return result
end

function mypair(t, k)
	if(k==nil) then k=0 end
	local v = t[k+1]
	if(v~=nil) then	return k+1,v end
	return nil,nil
end
function my__pairs(t)
		--mw.log("__pairs работает")
		return mypair,t, nil
end
function p.getModule2( num )
	local frame=mw.getCurrentFrame()
	local modul=frame.args[num]
	if(modul==nil) then
		modul=mw.loadData("Модуль:Песочница/Туча/Модуль:Statistical/"..num)  --mw.log(modul)
		if(modul==nil) then return nil end
		if(modul[1]~=nil) then -- новый формат модуля
			local t = modul
			modul={} --mw.log("начало приключений")
			local meta4 = {}
			function meta4.__index(op, key)
				local result = rawget(op,key) -- проверяем не запрашивали ли таблицу ранее
				if(result==nil) then
					if(t==nil or t[key]==nil) then return nil end
					result = {}
					local i=t[key] --mw.log("запросили запись ",key)
					local meta2 = {}
					meta2.__pairs=my__pairs
					function meta2.__index(op, key)
						if(key==nil or type(key)~="number" or key<1) then return nil end
						local result = rawget(op,key) -- проверяем не запрашивали ли таблицу ранее
						if(result==nil) then
							local index=i+(key-1)*3 --mw.log("mas ",i," ",key)
							if(t[index]==nil) then return nil end
							result = {}
							local meta3 = {}
							function meta3.__index(op, key)
								if(key==nil or type(key)~="number" or key<1 or key>3) then return nil end
								--mw.log("mas[",index+key-1,"]=",t[index+key-1])
								return t[index+key-1]
							end
							setmetatable(result, meta3)
							if(result~=nil) then rawset(op,key,result) end
						end
						return result
					end
					setmetatable(result, meta2)
					if(result~=nil) then rawset(op,key,result) end
				end
				return result
			end
			setmetatable(modul, meta4)
		end
		frame.args[num] = modul
	end
	return modul;
end

local tableregions = nil
local function regions()
	if(tableregions==nil) then
		tableregions = require("Модуль:Песочница/Туча/Модуль:Statistical/Regions")
	end
	return tableregions
end

function p.GetStat(frame)

    local args = frame.args
    if(args==nil or args[1]==nil) then args = frame:getParent().args end
    local PlaceName = nil 
    if args ~= nil then PlaceName=args[1] end
    if PlaceName == nil then return "Введите название объекта АТД" end
    local function GetCheck()
		local check = args['check'] or '';
		check = not (check == '' or check == '0' or check == 'false')
    	return check;
	end
    PlaceName = mw.text.trim(PlaceName)
    local Region = args['Регион'] or ''
    
    local NumABC,ModulName = nil,nil
    if(Region~='') then 
    	ModulName=regions()[Region] or regions()[tonumber(Region)]
    	if(ModulName==nil) then
    		mw.log("'"..Region.."' - такого региона нет")
    		Region=''
    	end 
    end
	if(Region=='') then
    	for vskobkah in gmatch(PlaceName,"%((.+)%)") do
    		if(regions()[vskobkah]) then 
    			Region =vskobkah
    			ModulName=regions()[Region]
    		end
    		--mw.log("Модуль:Statistical: Найдено в скобках '" .. vskobkah.."' -".. (Region~='' and "" or " не") .. " регион");
    	end
	end
    if(Region=='') then
		-- ё = е , так как по соглашению их не различают при сортировке
		local PlaceEEE = mw.ustring.gsub (PlaceName, "ё", "е")
		PlaceEEE = mw.ustring.gsub (PlaceEEE, "Ё", "Е")
		PlaceEEE = mw.ustring.gsub (PlaceEEE, "-", "")
		local ABCmain = mw.loadData(" Модуль:Песочница/Туча/Модуль:Statistical/ABC0")
		for k, v in pairs(ABCmain) do 
			-- mw.ustring.lower для регистроНЕзависимого сравнения
			if First_less_Second(mw.ustring.lower(PlaceEEE), mw.ustring.lower(v)) then break else NumABC = k end
		end
	end
    
    local function FormatH()    
	    return p.hash(PlaceName)
	end
	local Format = mw.text.trim ( (args[2] or "Таблица"))

    local Place,pos=PlaceName,nil
	if(ModulName==nil and NumABC ~= nil) then
		local num = frame:expandTemplate{ title = "Участник:Туча/Население/ABC"..NumABC, args = { Place } }
		if num == '' then 
			Place = FormatH() 
			num = frame:expandTemplate{ title = "Участник:Туча/Население/ABC"..NumABC, args = { Place } }
		end
		num = tonumber(num)
		ModulName = num and regions()[num]
	end
	local RegionData = nil
	if(ModulName~=nil) then 
		RegionData = p.getModule2("RUS-"..ModulName)
    	if(pos==nil) then if RegionData[Place] == nil then pos = FormatH() else pos=Place end	end
	end
    local PlaceData = (RegionData and RegionData[pos]) or nil
    if PlaceData == nil then 
    	if GetCheck() then
    		return 0;
    	elseif Format == 'Хеш' or Format == 'х' then
    		return FormatH()
    	else
			return "#Н/Д"..frame:extensionTag{name = 'ref', content = PlaceName .." > Данные не обнаружены. Возможно страница переименовывалась. Проверьте справочник" ..  ((args.nocat and "") or "[[Категория:Википедия:Статьи с неправильными параметрами шаблона Население]]")} ;
    	end
	end
	if GetCheck() then return 1; end
	if (type(PlaceData[1])=="number") then PlaceData = decrypt(PlaceData) end
    
    local LastRecord = 0
    for k in pairs(PlaceData) do LastRecord = LastRecord + 1 end
    local NumRecord = LastRecord
    
	local function FormatY()    
	    return PlaceData[NumRecord][1]
	end

	local function FormatN()    
	    return PlaceData[NumRecord][2]
	end

	local function FormatS(SourceType)  
	    if PlaceData[NumRecord][3] == "" then
	        return ""
	    else
	        local Source1
	        local Source2 
	        if string.find(PlaceData[NumRecord][3],"%d+[A-Z]+")==1 then
	        	Source1 = "{{Население/" .. PlaceData[NumRecord][3].."}}"
	            Source2 = PlaceData[NumRecord][3]
	        else
	            Source1 = PlaceData[NumRecord][3]
	            Source2 = ""
	        end
	        if string.find(Source1, "http://")==1 then
	            Source1 = '['..Source1..']'
	        end
			if SourceType == "и" then
				return Source1
			end
	        if Source2 == "" then
	            return frame:callParserFunction{name = '#tag:ref', args = {Source1}}
	        else
	            return frame:callParserFunction{name = '#tag:ref', args = {Source1, name = Source2}}
	        end
	    end
	end        

	local function FormatF()    
	    return formatNum(PlaceData[NumRecord][2])    
	end

	local function FormatT()    
	    if NumRecord > 1 then
	        if PlaceData[NumRecord][2] > PlaceData[NumRecord - 1][2] then
	            return "<span style='color: #0c0; font-weight:bold; font-size: larger;'>↗</span>" 
	        elseif PlaceData[NumRecord][2] < PlaceData[NumRecord - 1][2] then
	            return "<span style='color: red; font-weight:bold; font-size: larger;'>↘</span>"  
	        else
	            return "<span style='color:#0AF;'>→</span>"  
	        end
	    else
	        return ""
	    end            
	end
	--mw.log("количество записей ",NumRecord)

    if Format == 'Год' or Format == 'г' then
        return FormatY()    
    elseif Format == 'Безформат' or Format == 'Число' or Format == 'ч'  then
        return FormatN()    
    elseif Format == 'Ссылка' or Format == 'с'  then
        return FormatS("с")    
    elseif Format == 'Источник' or Format == 'и'  then
        return FormatS("и")    
    elseif Format == 'Формат' or Format == 'ф'  then
        return FormatF()    
    elseif Format == 'Формат' or Format == 'фг'  then
        return FormatF().." ("..FormatY()..")"    
    elseif Format == 'Формат' or Format == 'фс'  then
        return FormatF()..FormatS()    
    elseif Format == 'ФорматСсылкаГод' or Format == 'фсг'  then
        return FormatF()..FormatS().." ("..FormatY()..")"
    elseif Format == 'Тренд' or Format == 'т'  then
        return FormatT()..FormatF()    
    elseif Format == 'Значение' or Format == 'ТрендСсылка' or Format == 'тс'  then
        return FormatT()..FormatF()..FormatS()
    elseif Format == 'ТрендСсылкаГод' or Format == 'тсг'  then
        return FormatT()..FormatF()..FormatS().." ("..FormatY()..")"
    elseif Format == 'Хеш' or Format == 'х'  then
        return FormatH()
	elseif Format == 'Диаграмма' or Format == 'д'  then
		local tempHeight = 370
		local tempWidth = 800
		local tempMod = math.fmod (LastRecord, 5)
		if LastRecord < 40 then 
			tempHeight = 200  + 170 * (LastRecord - 1) / 40
			tempWidth = 200 + 600 * (LastRecord - 1) / 40
		end
		local tempGroup = ""
		local tempTooltip = ""
		local tempLegend = ""
		for k in pairs(PlaceData) do 
			NumRecord = k
			tempGroup = tempGroup .. FormatN() .. ":"
			tempTooltip = tempTooltip .. FormatF() .. " (" .. FormatY() .. "):"
			if LastRecord < 5 or math.fmod (k, 5) == tempMod  then tempLegend = tempLegend .. FormatY() end
			tempLegend = tempLegend .. ":"
		end
		tempGroup = string.sub (tempGroup, 1, string.len (tempGroup)-1)
		tempTooltip = string.sub (tempTooltip, 1, string.len (tempTooltip)-1)
		tempLegend = string.sub (tempLegend, 1, string.len (tempLegend)-1)
    	local barChart = require('Модуль:Chart')['bar chart'];
		local Diagram = {
			['height'] = tempHeight,
			['width'] = tempWidth,
			['group 1'] = tempGroup,
			['tooltip 1'] = tempTooltip,
			['x legends'] = tempLegend,
			['group names'] = 'Численность населения',
			['default color'] = '#00CCFF'
			}    	
		local cframe = mw.getCurrentFrame();
		return barChart(cframe:newChild{ title=cframe.title, args = Diagram})
    else
        -- Формироание HTML-таблицы
        local HtmlBuilder = require('Модуль:HtmlBuilder')    
        local HTML = HtmlBuilder.create('table')
    
        local MaxData
        if args['Столбцов'] then
            Column = tonumber(args['Столбцов'])
        else
            MaxData = (PlaceData[1][2] > PlaceData[LastRecord][2]) and PlaceData[1][2] or PlaceData[LastRecord][2]
            if MaxData < PlaceData[math.floor((LastRecord + 1) / 2)][2] then MaxData = PlaceData[math.floor((LastRecord + 1) / 2)][2] end
            if MaxData < 10 then Column = 15 
            elseif MaxData < 100 then Column = 12 
            elseif MaxData < 1000 then Column = 12
            elseif MaxData < 10000 then Column = 12
            elseif MaxData < 100000 then Column = 10
            elseif MaxData < 1000000 then Column = 10
            elseif MaxData < 10000000 then Column = 8
            elseif MaxData < 100000000 then Column = 8
            else Column = 7 end
            if Column > LastRecord then Column = LastRecord end
        end

        if args['Оформление'] ~= nil then
            HTML.attr('class', args['Оформление'])
        else
            if Column > 7 then HTML.attr('class', 'wide') else HTML.attr('class', 'standard') end
        end

        local TempRow
        local NumRow = 0
        TempRow = HTML.tag('th').attr('colspan', Column).wikitext(args['Заголовок'] or 'Численность населения')
        for i = 1, math.ceil(LastRecord / Column) do
            TempRow = HTML.tag('tr').addClass("bright")
            for j = 1, Column do
				NumRecord = (i - 1) * Column + j
                if PlaceData[NumRecord] == nil then
                    TempRow.tag('th').wikitext("")
                else
                    TempRow.tag('th').wikitext(FormatY()..FormatS("с"))
                end
            end
        
            TempRow = HTML.tag('tr').attr('align', 'center')
            for j = 1, Column do
				NumRecord = (i - 1) * Column + j
                if PlaceData[NumRecord] == nil then
                    TempRow.tag('td').wikitext("")
                else
                    TempRow.tag('td').wikitext(FormatT()..FormatF())
                end
            end
        end            
        return tostring(HTML)
    end
    return 1, Format
end

function p.GetRegion(frame)
	local args = frame.args
    if(args==nil or args[1]==nil) then args = frame:getParent().args end
    local PlaceName = nil 
    if args ~= nil then PlaceName=args[1] end
    return PlaceName:match("%((%P+)%)")
end

return p