# nom – helps you lose weight by tracking your energy intake
# Copyright (C) 2014-2023  blinry <mail@blinry.org>
#
# 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 2
# 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, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

require "yaml"
require "fileutils"

module Nom
    class WeightDatabase
        def initialize(file)
            @interpolated = {}
            @weights = {}
            @moving_averages = {}

            FileUtils.touch(file)
            IO.readlines(file).each do |line|
                date, weight = line.split(" ", 2)
                date = Date.parse(date)
                @weights[date] = weight.to_f
                @interpolated[date] = false
            end
        end

        def interpolate_gaps!
            @weights.keys.each_cons(2) do |a, b|
                (a+1).upto(b-1) do |d|
                    @weights[d] = @weights[a] + (@weights[a]-@weights[b])/(a-b)*(d-a)
                    @interpolated[d] = true
                end
            end
        end

        def precompute_moving_average!(alpha, beta, goal, rate)
            trend = -rate/8

            @moving_averages[first] = at(first)
            (first+1).upto(last).each do |d|
                @moving_averages[d] = alpha*at(d) + (1-alpha)*(@moving_averages[d-1]+trend)
                trend = beta*(@moving_averages[d]-@moving_averages[d-1]) + (1-beta)*trend
            end
        end

        def predict_weights!(rate, goal, tail)
            d = (last)
            loop do
                if (@weights[d] - goal).abs < 0.1 and d > Date.today
                    tail -= 1
                end
                if tail == 0
                    break
                end

                d += 1
                prev_weight = @moving_averages[d-1] || @weights[d-1]
                @weights[d] = prev_weight+dampened_rate(prev_weight, goal, rate)/7.0
                @interpolated[d] = true
            end
        end

        def dampened_rate weight, goal, rate
            r = (goal-weight).to_f
            if r.abs > 1
                r/r.abs*rate
            else
                r*rate
            end
        end

        def real? date
            @weights[date] and not @interpolated[date]
        end

        def at date
            @weights[date]
        end

        def moving_average_at date
            @moving_averages[date]
        end

        def rate_at date, goal, rate
            if date > last_real
                dampened_rate(@weights[date], goal, rate)
            else
                dampened_rate(@moving_averages[date], goal, rate)
            end
        end

        def empty?
            @weights.empty?
        end

        def first
            @weights.keys.min
        end

        def last
            @weights.keys.max
        end

        def last_real
            @interpolated.select{|d, i| not i}.keys.max
        end

        def truncate date
            @weights.delete_if{|d, w| d < date}
        end

        def min
            @weights.values.min
        end

        def max
            @weights.values.max
        end

        def find_gap days
            gap = @weights.keys.reverse.each_cons(2).find{|a,b| a-b > days}
            if gap
                gap.reverse
            else
                nil
            end
        end
    end
end
