module RunbyPace
  # Represents a human-readable time in the format MM:ss
  class PaceTime
    attr_reader :time_s, :minutes_part, :seconds_part

    def initialize(time)
      if time.is_a?(String) || time.is_a?(Symbol)
        init_from_string time
      elsif time.is_a?(PaceTime)
        init_from_clone time
      elsif time.is_a?(Hash)
        init_from_hash time
      end
    end

    # @param [numeric] total_seconds
    def self.from_seconds(total_seconds)
      minutes = total_seconds.abs.to_i / 60
      seconds = total_seconds.abs.to_i % 60
      PaceTime.new format("#{'%02d'}:#{'%02d'}", minutes, seconds)
    end

    # @param [numeric] total_minutes
    def self.from_minutes(total_minutes)
      from_seconds(total_minutes * 60.0)
    end

    def self.parse(str)
      time = str.to_s.strip.chomp
      is_negative = false

      if time[0] == '-'
        is_negative = true
        time = time[1..-1]
      end

      if time.match(/^\d?\d:\d\d$/)
        parts = time.split(':')
        minutes_part = parts[0].to_i
        seconds_part = parts[1].to_i
      elsif time.match(/^\d+$/)
        minutes_part = time.to_i
        seconds_part = 0
      elsif time.match(/^\d+[,\. ]\d+$/)
        parts = time.split(/[,\. ]/)
        minutes_part = parts[0].to_i
        seconds_part = (parts[1].to_i / 10.0 * 60).to_i
      else
        raise 'Invalid time format'
      end

      raise 'Minutes must be less than 100' if minutes_part > 99
      raise 'Seconds must be less than 60' if seconds_part > 59
      if is_negative
        minutes_part *= -1
        seconds_part *= -1
      end
      time_formatted = "#{minutes_part.to_s.rjust(2, '0')}:#{seconds_part.to_s.rjust(2, '0')}"

      PaceTime.new(time_s: time_formatted, minutes_part: minutes_part, seconds_part: seconds_part)
    end

    def self.try_parse(str, is_five_k = false)
      time, error_message, warning_message = nil
      begin
        time = parse str
      rescue StandardError => ex
        error_message = "#{ex.message} (#{str})"
      end

      # Break out these sanity checks into their own class if we add any more.
      if !time.nil? && is_five_k
        if time.minutes_part < 14 then warning_message = '5K times of less than 14:00 are unlikely' end
        if time.total_seconds > (42 * 60) then warning_message = '5K times of greater than 42:00 are not fully supported' end
      end

      { time: time, error: error_message, warning: warning_message }
    end

    def to_s
      @time_s
    end

    def total_seconds
      @minutes_part * 60 + @seconds_part
    end

    def total_minutes
      @minutes_part + (@seconds_part / 60.0)
    end

    # @param [PaceTime] other
    def -(other)
      if other.is_a?(PaceTime)
        PaceTime.from_seconds(total_seconds - other.total_seconds)
      end
    end

    # @param [PaceTime] other
    def +(other)
      if other.is_a?(PaceTime)
        PaceTime.from_seconds(total_seconds + other.total_seconds)
      end
    end

    def ==(other)
      if other.is_a?(PaceTime)
        total_seconds == other.total_seconds
      elsif other.is_a?(String)
        @time_s == other
      end
    end

    def almost_equals?(other_time, tolerance_time = '00:01')
      if other_time.is_a?(String)
        other_time = PaceTime.new(other_time)
      end
      tolerance = PaceTime.new(tolerance_time)
      self >= (other_time - tolerance) && self <= (other_time + tolerance)
    end

    def >(other)
      if other.is_a?(PaceTime)
        total_seconds > other.total_seconds
      end
    end

    def >=(other)
      if other.is_a?(PaceTime)
        total_seconds >= other.total_seconds
      end
    end

    def <(other)
      if other.is_a?(PaceTime)
        total_seconds < other.total_seconds
      end
    end

    def <=(other)
      if other.is_a?(PaceTime)
        total_seconds <= other.total_seconds
      end
    end

    private

    # @param [Hash] params
    def init_from_hash(params = {})
      @time_s = params.fetch :time_s, '00:00'
      @minutes_part = params.fetch :minutes_part, 0.0
      @seconds_part = params.fetch :seconds_part, 0.0
    end

    # @param [PaceTime] time
    def init_from_clone(time)
      @time_s = time.time_s
      @minutes_part = time.minutes_part
      @seconds_part = time.seconds_part
    end

    # @param [String] time
    def init_from_string(time)
      init_from_clone PaceTime.parse time
    end
  end
end