-- ***********************************************************************
--
-- Module to display the King James Bible
-- Copyright 2021 by Sean Conner.
--
-- This program is free software: you can redistribute it and/or modify it
-- under the terms of the GNU General Public License as published by the
-- Free Software Foundation, either version 3 of the License, or (at your
-- option) any later version.
--
-- This program is distributed in the hope that it will be useful, but
-- WITHOUT ANY WARRANTY; without even the implied warranty of
-- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
-- Public License for more details.
--
-- You should have received a copy of the GNU General Public License along
-- with this program.  If not, see <http://www.gnu.org/licenses/>.
--
-- Comments, questions and criticisms can be sent to: [email protected]
-- ***********************************************************************
-- luacheck: globals CONF tumbler init bookend read_entry entries
-- luacheck: globals filename day_titles
-- luacheck: ignore 611

local fsys     = require "org.conman.fsys"
local date     = require "org.conman.date"
local ENTITIES = require "org.conman.const.entity"
local lpeg     = require "lpeg"
local io       = require "io"
local os       = require "os"
local utf8     = require "utf8"
local string   = require "string"
local math     = require "math"
local table    = require "table"

local loadfile     = loadfile
local require      = require
local setmetatable = setmetatable
local type         = type

local _ENV = {}

tumbler = require "org.conman.app.mod_blog.tumbler"
CONF    =
{
 require = setmetatable(
       {},
       {
         __index = function(s) return s end,
         __call  = function(s) return s end,
       }
 ),
}

-- ***********************************************************************

local function date_cmp(d1,d2)
 local rc
 rc = d1.year  - d2.year  if rc ~= 0 then return rc end
 rc = d1.month - d2.month if rc ~= 0 then return rc end
 rc = d1.day   - d2.day   if rc ~= 0 then return rc end

 return d1.part - d2.part
end

-- ***********************************************************************

local function swap_endpoints(range)
 local start = {}
 local stop  = {}

 start.year  = range.stop.year
 start.month = range.stop.month
 stop.year   = range.start.year
 stop.month  = range.start.month

 if range.ustart == 'month' then
   stop.day  = 31
   stop.part = 23
 elseif range.ustart == 'day' then
   stop.day  = range.start.day
   stop.part = 23
 elseif range.ustart == 'part' then
   stop.day  = range.start.day
   stop.part = range.start.part
 elseif range.ustart == 'year' then
   assert(false)
 end

 if range.ustop == 'month' then
   start.day  = 1
   start.part = 1
 elseif range.ustop == 'day' then
   start.day  = range.stop.day
   start.part = 1
 elseif range.ustop == 'part' then
   start.day  = range.stop.day
   start.part = range.stop.part
 elseif range.ustop == 'year' then
   assert(false)
 end

 range.start = stop
 range.stop  = start
end

-- ***********************************************************************

local function when_to_filename(w)
 return string.format("%s/%4d/%02d/%02d/%d",CONF.basedir,w.year,w.month,w.day,w.part)
end

-- ***********************************************************************

local function when_to_meta(w,meta)
 return string.format("%s/%4d/%02d/%02d/%s",CONF.basedir,w.year,w.month,w.day,meta)
end

-- ***********************************************************************

local parse_date = lpeg.Ct(
   lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"year")  * lpeg.P"/"
 * lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"month") * lpeg.P"/"
 * lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"day")   * lpeg.P"."
 * lpeg.Cg(lpeg.R"09"^1 / math.tointeger,"part")
)

-- ***********************************************************************

local deent do
 local char = lpeg.P"&#" * lpeg.C(lpeg.R"09"^1)             * lpeg.P";" / utf8.char
            + lpeg.P"&"  * lpeg.C(lpeg.R("az","AZ","09")^1) * lpeg.P";" / ENTITIES
            + lpeg.S" \t\r\n"^1 / " "
            + lpeg.P(1)
 deent      = lpeg.Cs(char^0)
end

-- ***********************************************************************

function init(config)
 config = config or os.getenv("BLOG_CONFIG")
 if not config then return end
 local f = loadfile(config,"t",CONF)
 if not f then return end
 f()
 return CONF
end

-- ***********************************************************************

function bookend(which)
 local file = io.open(CONF.basedir .. "/." .. which)
 local line = file:read("l")
 local when = parse_date:match(line)
 file:close()
 return when
end

-- ***********************************************************************

function read_entry(when)
 local function get_meta(meta)
   local f = io.open(when_to_meta(when,meta),"r")
   local l = 0
   local line

   if not f then
     return ""
   end

   repeat
     line = f:read("l")
     l    = l + 1
   until l == when.part
   f:close()
   return line and deent:match(line) or ""
 end

 local filename = when_to_filename(when)
 if fsys.access(filename,"r") then
   local entry     = {}
   entry.when      = { year = when.year , month = when.month , day = when.day , part = when.part }
   entry.title     = get_meta("titles")
   entry.class     = get_meta("class")
   entry.author    = get_meta("authors")
   entry.status    = get_meta("status")
   entry.adtag     = get_meta("adtag")
   entry._filename = filename

   local f = io.open(filename,"r")
   entry.body = f:read("a")
   f:close()
   return entry
 end
end

-- ***********************************************************************

function entries(range)
 if type(range) == 'string' then
   range = tumbler.new(range,bookend "first", bookend "last")
 end

 if date_cmp(range.start,range.stop) <= 0 then
   local function nup(state)
     if date_cmp(state.start,state.stop) > 0 then
       return
     end

     local entry = read_entry(state.start)
     if entry then
       state.start.part = state.start.part + 1
       return entry
     end

     state.start.part = 1
     state.start.day  = state.start.day + 1
     if state.start.day > date.daysinmonth(state.start) then
       state.start.day = 1
       state.start.month = state.start.month + 1
       if state.start.month == 13 then
         state.start.month = 1
         state.start.year = state.start.year + 1
       end
     end

     return nup(state)
   end

   return nup,range

 else
   swap_endpoints(range)
   range.start.part = range.start.part + 1

   local function ndown(state)
     state.start.part = state.start.part - 1
     if state.start.part == 0 then
       state.start.part = 23
       state.start.day = state.start.day - 1
       if state.start.day == 0 then
         state.start.day = 31
         state.start.month = state.start.month - 1
         if state.start.month == 0 then
           state.start.month = 12
           state.start.year = state.start.year - 1
         end
       end
     end

     if date_cmp(state.start,state.stop) < 0 then
       return
     end

     local entry = read_entry(state.start)
     if entry then
       return entry
     end

     return ndown(state)
   end

   return ndown,range
 end
end

-- ***********************************************************************

function filename(tuple)
 return string.format(
       "%s/%04d/%02d/%02d/%s",
       CONF.basedir,
       tuple.start.year,
       tuple.start.month,
       tuple.start.day,
       tuple.filename
 )
end

-- ***********************************************************************

function day_titles(when)
 local res      = {}
 local filename = when_to_meta(when,"titles")

 if filename and fsys.access(filename,"r") then
   for title in io.lines(filename) do
     table.insert(res,deent:match(title))
   end
 end

 return res
end

-- ***********************************************************************

return _ENV