require 'filestream'
require 'vector'
require 'io'
local time = require 'kaltime'

local Eat = @record{
  kcal:     number,
  fat:      number,
  carb:     number,
  protein:  number,
  brand:    string,
  name:     string,
}

local Day = @record{
  date:   time.Time,
  goal:   number,
  burnt:  number,
  eats:   vector(Eat),
}

function Day:populated(): boolean
  return self.date ~= 0 and self.goal ~= 0
end

function Day:deficit(): number
  local eaten = 0
  for _, e in ipairs(self.eats) do
    eaten = eaten + e.kcal
  end
  return eaten - self.burnt
end

local function load_days(file: filestream): (vector(Day), string)
  local result: vector(Day)
  local day: Day
  local lineno = 0

  for line <close> in file:lines() do
    lineno = lineno + 1
    if line == '' then
      if day:populated() then result:push(day) end
      day = Day{}
      continue
    elseif line == 'd now' then
      day.date = time.Now()
      continue
    end
    ##[[local function try(pat)]] local ok, matches <close> = string.matchview(line, #[pat]#) ##[[end]]
    ##[[local function orfail(kind)]] if not ok then return {}, string.format('bad %s in line #%d: %s', #[kind]#, lineno, line) end ##[[end]]
    switch line[1] do
    case 'd'_u8 then
      ## try '^d %S+ (%d+ %S+ %d+)$' orfail 'date'
      local ok, epoch = time.Parse('%0d %b %Y', matches[1]) ## orfail 'date'
      day.date = epoch
    case 't'_u8 then
      ## try '^t %d+ / (%d+) %([-%d]+%) = %d+%%$' orfail 'calorie goal'
      day.goal = tonumber(matches[1])
    case 'b'_u8 then
      ## try '^b (%d+) = %d+%% %([-%d]+%)$' orfail 'calories burned'
      day.burnt = tonumber(matches[1])
    case 'T'_u8 then
      -- ignore
    case 'k'_u8 then
      ## try '^k +%d+%% +(%d+) +(%d+) +(%d+) +(%d+) +(%S+) "(.-)"$'
      if not ok then return {}, 'bad k line: ' .. line end
      day.eats:push({
        kcal    = tonumber(matches[1]),
        fat     = tonumber(matches[2]),
        carb    = tonumber(matches[3]),
        protein = tonumber(matches[4]),
        brand   = string.copy(matches[5]),
        name    = string.copy(matches[6]),
      })
    else
      return {}, 'bad line: ' .. line
    end
  end

  if day:populated() then result:push(day) end
  return result
end

local function dump_days(days: vector(Day)): string
  local buffer: [256]byte
  local tbuffer = (@string){data=&buffer[0], size=#buffer}
  local first = true
  for i, d in ipairs(days) do
    if first then first = false else print '' end
    local eaten   : number
    local left    = d.goal
    local fat     : number
    local carb    : number
    local protein : number
    for _, e in ipairs(d.eats) do
      eaten   = eaten   + e.kcal
      left    = left    - e.kcal
      fat     = fat     + e.fat
      carb    = carb    + e.carb
      protein = protein + e.protein
    end
    local daypct     = eaten / d.goal * 100
    local burnpct    = d.burnt / eaten * 100
    local proteinpct = protein / (fat + carb + protein) * 100
    if not time.Format(tbuffer, '%A, %d %b %Y', d.date) then return string.format('unable to format date: %lu', d.date) end

    io.writef('d %s\n', tbuffer)
    io.writef('t %.0f / %.0f (%.0f) = %.0f%%\n', eaten, d.goal, left, daypct)
    io.writef('b %.0f = %.0f%% (%.0f)\n', d.burnt, burnpct, d:deficit())
    io.writef('T %.0f:%.0f:%.0f = %.0f %.0f%%\n', fat, carb, protein, fat+carb+protein, proteinpct)

    for _, e in ipairs(d.eats) do
      local pct = e.kcal / d.goal * 100
      io.writef('k %3.0f%% %4.0f %3.0f %3.0f %3.0f %s "%s"\n', pct, e.kcal, e.fat, e.carb, e.protein, cstring(e.brand), cstring(e.name))
    end
  end
  io.flush()
  return ''
end

##[[local function ordie()]] if err ~= '' then io.stderr:print(err) return 1 end ##[[end]]
local diet <close>, err <close>, errcode = io.open 'diet'  ## ordie()
local days, err = load_days(diet)  ## ordie()
defer
  for _, d in ipairs(days) do
    for _, e in ipairs(d.eats) do
      e.brand:destroy()
      e.name:destroy()
    end
    d.eats:destroy()
  end
  days:destroy()
end
local err = dump_days(days)  ## ordie()
