#### RemiAudio
#### Copyright (C) 2022-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 Affero 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 Affero General Public
#### License for more details.
####
#### You should have received a copy of the GNU Affero General Public License
#### along with this program.  If not, see <https://www.gnu.org/licenses/>.

module RemiAudio::Codecs
  module G117
    extend self

    RemiAudio::Codecs::G117.initLawTables()

    # :nodoc:
    LINEAR_CODE_BIAS = 0x84

    # :nodoc:
    @@muLawTable : Array(UInt8) = [] of UInt8
    @@aLawTable : Array(UInt8) = [] of UInt8

    # Converts a mu-Law sample to a signed 16-bit integer sample.
    def muLawToInt16(uval : UInt8) : Int16
      # Taken from pcm_tablegen.h in ffmpeg
      notUval : UInt8 = ~uval
      foo : Int32 = ((uval & 0xf) << 3).to_i32! + LINEAR_CODE_BIAS
      foo = foo << ((notUval & 0x70) >> 4)

      if (notUval & 0x80) != 0
        (LINEAR_CODE_BIAS - foo).to_i16!
      else
        (foo - LINEAR_CODE_BIAS).to_i16!
      end
    end

    # Converts an a-Law sample to a signed 16-bit integer sample.
    def aLawToInt16(aval : UInt8) : Int16
      # Taken from pcm_tablegen.h in ffmpeg
      seg : Int16 = 0
      aval = aval ^ 0x55
      foo : Int16 = (aval & 0xf).to_i16!
      seg = ((aval & 0x70) >> 4).to_i16!
      foo = if seg != 0
              (foo + foo + 1 + 32) << (seg + 2)
            else
              (foo + foo + 1) << 3
            end

      if (aval & 0x80) != 0
        foo
      else
        -foo
      end
    end

    # :nodoc:
    protected def makeLawTable(mask : UInt8, reverseFn : Proc(UInt8, Int16)) : Array(UInt8)
      # Taken from pcm_tablegen.h in ffmpeg
      j : Int32 = 1
      ret : Array(UInt8) = Array.new(16384, 0u8)
      ret[8192] = mask

      127u8.times do |i|
        v1 : Int32 = reverseFn.call((i ^ mask).to_u8!).to_i32!
        v2 : Int32 = reverseFn.call(((i + 1) ^ mask).to_u8!).to_i32!
        v  : Int32 = (v1 + v2 + 4) >> 3

        while j < v
          ret[8192 - j] = i ^ (mask ^ 0x80)
          ret[8192 + j] = i ^ mask
          j += 1
        end
      end

      while j < 8192
        ret[8192 - j] = 127u8 ^ (mask ^ 0x80)
        ret[8192 + j] = 127u8 ^ mask
        j += 1
      end

      ret[0] = ret[1]
      ret
    end

    # :nodoc:
    def self.initLawTables
      @@muLawTable = makeLawTable(0xff, ->muLawToInt16(UInt8))
      @@aLawTable = makeLawTable(0xd5, ->aLawToInt16(UInt8))
    end

    # Converts a 16-bit signed integer sample into a mu-Law sample.
    @[AlwaysInline]
    def int16ToMuLaw(sample : Int16) : UInt8
      @@muLawTable[(sample.to_i32! + 32768) >> 2]
    end

    # Converts an array of 16-bit signed integer samples into an array of mu-Law samples.
    def int16ToMuLaw(samples : Array(Int16)) : Array(UInt8)
      samples.map { |smp| int16ToMuLaw(smp) }
    end

    # Converts a 16-bit signed integer sample into a a-Law sample.
    @[AlwaysInline]
    def int16ToALaw(sample : Int16) : UInt8
      @@aLawTable[(sample.to_i32! + 32768) >> 2]
    end

    # Converts an array of 16-bit signed integer samples into an array of a-Law samples.
    def int16ToMuLaw(samples : Array(Int16)) : Array(UInt8)
      samples.map { |smp| int16ToALaw(smp) }
    end

    # Converts an array of mu-Law samples into an array of 16-bit signed integer samples.
    def muLawToInt16(samples : Array(Int16)) : Array(UInt8)
      samples.map { |smp| muLawToInt16(smp) }
    end

    # Converts an array of ma-Law samples into an array of 16-bit signed integer samples.
    def aLawToInt16(samples : Array(Int16)) : Array(UInt8)
      samples.map { |smp| aLawToInt16(smp) }
    end
  end
end
