-- For Google Code In 2018 - Lua Task 7 - Date formatting

local p={}

p.valid=function(day,month,year)
	day,month,year=tonumber(day),tonumber(month),tonumber(year)
	local lengths={31,0,31,30,31,30,31,31,30,31,30,31}
	local long=0
	if year==nil then return false end
	if type(year)~='number' or math.floor(year)~=year or year<1 then return false end
	if month==nil then return true end
	if type(month)~='number' or math.floor(month)~=month or month<1 or month>12 then return false end
	if day==nil then return true end
	if type(day)~='number' or math.floor(day)~=day or day<1 then return false end
	if month==2 then
		if year%400==0 then long=29 
		elseif year%100==0 then long=28
		elseif year%4==0 then long=29
		else long=28 end
	else
		long=lengths[month]
	end
	return day<=long
end

p.testvalid=function(frame)
	return p.valid(frame.args.day,frame.args.month,frame.args.year)
end

p.convert=function(frame)
	local indate=frame.args.date
	local day,month,year,era,inform,circa,outdate=nil
	local monthregex={"[Jj]an","[Ff]eb","[Mm]ar","[Aa]pr","[Mm]ay","[Jj]un","[Jj]ul","[Aa]ug","[Ss]ep","[Oo]ct","[Nn]ov","[Dd]ec"}
	local monthnames={"January ","February ","March ","April ","May ","June ","July ","August ","September ","October ","November ","December "}
	
	--parse indate -> inform, day, month, year; then check if valid
	--first try: parse as some iso-like format
	local num1,num2,num3,era=string.match(indate,"(%d+)%D+(%d+)%D+(%d+)")
	if p.valid(num3,num2,num1) then day,month,year,inform=num3,num2,num1,"iso" else
	if p.valid(num1,num2,num3) then day,month,year,inform=num1,num2,num3,"iso" else
	if p.valid(num2,num1,num3) then day,month,year,inform=num2,num1,num3,"iso" else
	if p.valid(num3,num1,num2) then day,month,year,inform=num3,num1,num2,"iso" else
	if p.valid(num1,num3,num2) then day,month,year,inform=num1,num3,num2,"iso" else
	if p.valid(num2,num3,num1) then day,month,year,inform=num2,num3,num1,"iso" else
	if num1~=nil and num2~=nil and num3~=nil then return "Invalid entry" end
	
	--second try: parse as some dmy-like format
	local num4,num5,mon1=nil
	for i=1,12 do
		num4,num5=string.match(indate,"(%d+)%D+"..monthregex[i].."%D+(%d+)")
		if num4~=nil then 
			mon1=i
			break 
		end
	end
	if p.valid(num4,mon1,num5) then day,month,year,inform=num4,mon1,num5,"dmy" else
	if p.valid(num5,mon1,num4) then day,month,year,inform=num5,mon1,num4,"dmy" else
	if num4~=nil and mon1~=nil and num5~=nil then return "Invalid entry" end
	
	--third try: parse as some mdy-like format
	local num6,num7,mon2=nil
	for i=1,12 do
		num6,num7=string.match(indate,monthregex[i].."%D+(%d+)%D+(%d+)")
		if num6~=nil then 
			mon2=i
			break 
		end
	end
	if p.valid(num6,mon2,num7) then day,month,year,inform=num6,mon2,num7,"mdy" else
	if p.valid(num7,mon2,num6) then day,month,year,inform=num7,mon2,num6,"mdy" else
	if num6~=nil and mon2~=nil and num7~=nil then return "Invalid entry" end
	
	--fourth try: parse as month,year-like format
	local num8,mon3=nil
	for i=1,12 do
		num8=string.match(indate,monthregex[i].."%D+(%d+)")
		if num8~=nil then 
			mon3=i
			break 
		end
	end
	if p.valid(nil,mon3,num8) then month,year,inform=mon3,num8,"month and year" else
	if mon3~=nil and num8~=nil then return "Invalid entry" end
	
	--(the unmentioned case: day and month)
	local num9,mon4=nil
	for i=1,12 do
		num9=string.match(indate,"(%d+)%D+"..monthregex[i])
		if num9~=nil then 
			mon4=i
			break 
		end
	end
	if mon4~=nil and p.valid(num9,mon4,2016) then day,month,inform=num9,mon4,"day and month" else
	if p.valid(nil,mon4,num9) then month,year,inform=mon4,num9,"month and year" else
	if mon4~=nil and num9~=nil then return "Invalid entry" end
	
	--fifth try: parse as year
	if string.match(indate,"(%d+)%D*[^% %d]%D*(%d+)") then return "Invalid entry" end
	local num0=string.match("A"..indate,".*%D(%d+)")
	if p.valid(nil,nil,num0) then year,inform=num0,"year" else
	return "Invalid entry"
	end end end end end end end end end end end end end end -- ends from all the elses
	
	--get era
	if string.find(indate,"BCE") then era=" BCE"
	elseif string.find(indate,"BC") then era=" BC"
	elseif string.find(indate,"CE") then era=" CE"
	elseif string.find(indate,"AD") then era=" AD"
	else era="" end
	
	--get uncertainty
	if string.match(indate,"around") then circa="circa "
	elseif string.match(indate,"uncertain") then circa="circa " 
	elseif string.match(indate,"about") then circa="circa " 
	elseif string.match(indate,"approx") then circa="circa "
	elseif string.match(indate,"circa") then circa="circa "
	elseif string.match(indate,"ca%.") then circa="circa " 
	else circa="" end
	
	--convert output date to input format -> outdate
	local outform=frame.args.format or inform
	day,month,year=tonumber(day),tonumber(month),tonumber(year)
	outdate = ""
	if outform=="iso" then
		if era==" BC" or era==" BCE" then
			circa=circa.."-"
			year=year-1
		end
		outdate = string.format("%04d-%02d-%02d",year,month,day)
	end
	if outform=="dmy" then
		outdate = day.." "..monthnames[month]..year..era
	end
	if outform=="mdy" then
		outdate = monthnames[month]..day..", "..year..era
	end
	if outform=="month and year" then
		outdate = monthnames[month]..year..era
	end
	if outform=="day and month" then
		outdate = day.." "..monthnames[month]
	end
	if outform=="year" then
		outdate = year..era
	end
	outdate = circa..outdate
	return outdate
end

return p