#### libremiliacr
#### Copyright(C) 2020-2024 Remilia Scarlet <remilia@posteo.jp>
####
#### This program is free software: you can redistribute it and/or modify
#### it under the terms of the GNU General Public License as published
#### 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/.>

# A 64-bit Cyclic Redundancy Check.
class RemiLib::Digest::Crc64
  # The polynomial as defined in ECMA 182.
  ECMA_POLY = 0xC96C5795D7870F42_u64

  # The polynomial as defined in ISO 3309.
  ISO_POLY = 0xD800000000000000_u64

  # The current checksum.  This is updated each time `#update` is called.
  getter crc : UInt64 = 0

  # The polynomial that this instance is currently using.
  getter polynomial : UInt64 = ECMA_POLY

  # CRC table.
  @table : Slice(UInt64) = Slice(UInt64).new(0)

  # Creates a new `Crc64` instance.
  def initialize(poly : UInt64 = ECMA_POLY)
    self.polynomial = poly
  end

  # Updates the checksum with *value*.  Returns the new checksum.
  @[AlwaysInline]
  def update(value : UInt8) : UInt64
    @crc = ~@crc
    @crc = @table.unsafe_fetch(value ^ (@crc & 0xFF)) ^ (@crc >> 8)
    @crc = ~@crc
  end

  # Updates the checksum with the bytes in *buf*.  Returns the new checksum.
  @[AlwaysInline]
  def update(buf : Bytes|Array(UInt8)) : UInt64
    @crc = ~@crc
    buf.each do |byte|
      @crc = @table.unsafe_fetch(byte ^ (@crc & 0xFF)) ^ (@crc >> 8)
    end
    @crc = ~@crc
  end

  # Resets this instance to the initial state.
  def reset : Nil
    @crc = 0
  end

  # Changes the polynomial that is used.  **NOTE**: This resets this instance to
  # the initial state as well.
  def polynomial=(@polynomial : UInt64) : Nil
    reset
    @table = Slice(UInt64).new(256) do |i|
      val : UInt64 = i.to_u64!
      8.times do |_|
        if bitflag?(val, 1)
          val = (val >> 1) ^ @polynomial
        else
          val = val >> 1
        end
      end
      val
    end
  end
end
