#### 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/.>
require "math"
require "big"

####
#### Extensions to the standard library.
####

# A convenience macro that is the as doing `(thing & flag) != 0`, but slightly
# cleaner looking.
macro bitflag?(thing, flag)
  (({{thing}} & {{flag}}) != 0)
end

struct Time
  UNIVERSAL_TIME_OFFSET = 2_208_988_800

  # Returns the number of seconds since the beginning of the year 1900.
  #
  # [See the Common Lisp documentation on Universal Time](http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_u.htm#universal_time)
  def toUniversal : Int64
    self.to_unix + UNIVERSAL_TIME_OFFSET
  end

  # Creates a new `Time` instance from the given universal time.
  #
  # [See the Common Lisp documentation on Universal Time](http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_u.htm#universal_time)
  def self.universal(seconds : Int64) : Time
    self.unix(universalToUnix(seconds))
  end

  # Converts a universal time to a Unix time.
  #
  # [See the Common Lisp documentation on Universal Time](http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_u.htm#universal_time)
  def self.universalToUnix(sec : Int64) : Int64
    sec - UNIVERSAL_TIME_OFFSET
  end

  # Converts a Unix time to a Universal time.
  #
  # [See the Common Lisp documentation on Universal Time](http://www.lispworks.com/documentation/HyperSpec/Body/26_glo_u.htm#universal_time)
  def self.unixToUniversal(sec : Int64) : Int64
    sec + UNIVERSAL_TIME_OFFSET
  end
end

class ::Array(T)
  # Returns a new `Slice` that contains the same elements as this array.
  def toSlice : Slice(T)
    ret = Slice(T).new(self.size)
    self.to_unsafe.copy_to(ret.to_unsafe, self.size)
    ret
  end
end

struct ::Slice(T)
  # Returns a new `Array` that contains the same elements as this slice.
  def toArray : Array(T)
    Array(T).new(self.size) do |i|
      self.[i]
    end
  end

  # Similar to `realloc()` in C.  This returns a new `Slice` with the new
  # requested size.  The contents of `self` are copied over to the new `Slice`.
  # If the new `Slice` is larger than `self`, then the new elements are set to
  # `default`.  If the new `Slice` is smaller, then only as many items as will
  # fit are copied over.
  #
  # Internally, this is equivalent to either using `Slice#[range]` when the new
  # size is smaller, `Slice#dup` when the sizes are the same, and
  # `Slice#initialize` + `Slice#copy_from` when the new size is larger.
  def realloc(newSize : Int32, default : T) : self
    if newSize < self.size
      # Just return a new slice
      self[0...newSize]
    elsif newSize == self.size
      # Copy
      self.dup
    else
      # Larger than self
      ret = Slice(T).new(newSize, default)
      ret.copy_from(self)
      ret
    end
  end
end

abstract class ::IO
  # Temporarily changes the stream position to `gotoHere`, then yields.  This
  # automatically returns to the position before calling this method before it
  # returns.
  #
  # This only works for types that can get and set their position.
  def withExcursion(gotoHere, &)
    oldPos = self.pos
    self.pos = gotoHere
    yield
    self.pos = oldPos
  end

  # Yields, then automatically returns to the position before calling this
  # method before it returns.
  #
  # This only works for types that can get and set their position.
  def withExcursion(&)
    oldPos = self.pos
    yield
    self.pos = oldPos
  end

  # Reads *count* bytes into a `::Bytes` and returns the new `::Bytes`.  If
  # *count* bytes could not be read, this will return a `::Bytes` that is equal
  # in length to the number of bytes actually read.
  def self.readBytes(io : IO, count : Int) : Bytes
    raise ::ArgumentError.new("Bad byte count") if count < 0
    ret = Bytes.new(count)
    numRead = io.read(ret)
    if numRead == count
      ret
    else
      ret[0...numRead]
    end
  end

  # :ditto:
  @[AlwaysInline]
  def readBytes(count : Int) : Bytes
    IO.readBytes(self, count)
  end

  # Reads a 24-bit signed little-endian integer from `io`.
  @[AlwaysInline]
  def self.readInt24(io : IO) : Int32
    b1 = io.read_byte.try &.to_i32! || raise IO::EOFError.new("Could not read the first byte of a 24-bit int")
    b2 = io.read_byte.try &.to_i32! || raise IO::EOFError.new("Could not read the second byte of a 24-bit int")
    b3 = io.read_byte.try &.to_i32! || raise IO::EOFError.new("Could not read the third byte of a 24-bit int")
    ret : Int32 = b3 << 16 | b2 << 8 | b1
    ret |= ~0x7FFFFF if ret & 0x800000 != 0
    ret
  end

  # Reads a 24-bit signed big-endian integer from `io`.
  @[AlwaysInline]
  def self.readInt24BE(io : IO) : Int32
    b3 = io.read_byte.try &.to_i32! || raise IO::EOFError.new("Could not read the first byte of a 24-bit int")
    b2 = io.read_byte.try &.to_i32! || raise IO::EOFError.new("Could not read the second byte of a 24-bit int")
    b1 = io.read_byte.try &.to_i32! || raise IO::EOFError.new("Could not read the third byte of a 24-bit int")
    ret : Int32 = b3 << 16 | b2 << 8 | b1
    ret |= ~0x7FFFFF if ret & 0x800000 != 0
    ret
  end

  # Reads a 24-bit signed little-endian integer from `io`.
  @[AlwaysInline]
  def readInt24
    IO.readInt24(self)
  end

  # Reads a 24-bit signed big-endian integer from `io`.
  @[AlwaysInline]
  def readInt24BE
    IO.readInt24BE(self)
  end

  # Writes `data` as a 24-bit signed little-endian integer.  This does not check
  # to see if `data` is within the range of a 24-bit integer and always writes
  # it as 24-bits.
  @[AlwaysInline]
  def writeInt24(data : Int32)
    write_byte((data & 0x0000FF).to_u8!)
    write_byte(((data & 0x00FF00) >> 8).to_u8!)
    write_byte(((data & 0xFF0000) >> 16).to_u8!)
  end

  # Writes `data` as a 24-bit signed big-endian integer.  This does not check to
  # see if `data` is within the range of a 24-bit integer and always writes it
  # as 24-bits.
  @[AlwaysInline]
  def writeInt24BE(data : Int32)
    write_byte(((data & 0xFF0000) >> 16).to_u8!)
    write_byte(((data & 0x00FF00) >> 8).to_u8!)
    write_byte((data & 0x0000FF).to_u8!)
  end

  ###
  ### Convenience functions.
  ###
  ### These can all be accomplished with the standard library, but these names
  ### are a bit shorter to type.
  ###

  # Convenience method to read a signed 8-bit byte from `io`.  If the byte
  # cannot be read, this will raise an `IO::EOFError`.
  #
  # This is the same as calling `io.read_byte.try &.to_i8! || raise IO::EOFError.new`,
  # just shorter.
  @[AlwaysInline]
  def self.readInt8(io : IO) : Int8
    io.read_byte.try &.to_i8! || raise IO::EOFError.new
  end

  # Convenience method to read a signed 8-bit byte.  If the byte cannot be read,
  # this will raise an `IO::EOFError`.
  #
  # This is the same as calling `io.read_byte.try &.to_i8! || raise IO::EOFError.new`,
  # just shorter.
  @[AlwaysInline]
  def readInt8
    IO.readInt8(self)
  end

  # Convenience method to write a signed 8-bit byte to `io`.
  #
  # This is the same as calling `io.write_byte(value.to_u8!)`, just shorter.
  @[AlwaysInline]
  def self.writeInt8(io : IO, value : Int8) : Nil
    io.write_byte(value.to_u8!)
  end

  # Convenience method to write a signed 8-bit byte.
  #
  # This is the same as calling `io.write_byte(value.to_u8!)`, just shorter.
  @[AlwaysInline]
  def writeInt8(value : Int8) : Nil
    self.write_byte(value.to_u8!)
  end

  # Convenience method to read an unsigned 8-bit byte from `io`.  If the byte
  # cannot be read, this will raise an `IO::EOFError`.
  #
  # This is the same as calling `io.read_byte || raise IO::EOFError.new`, just
  # shorter.
  @[AlwaysInline]
  def self.readUInt8(io : IO) : UInt8
    io.read_byte || raise IO::EOFError.new
  end

  # Convenience method to read an unsigned 8-bit byte.  If the byte cannot be
  # read, this will raise an `IO::EOFError`.
  #
  # This is the same as calling `io.read_byte || raise IO::EOFError.new`, just
  # shorter.
  @[AlwaysInline]
  def readUInt8
    IO.readUInt8(self)
  end

  # Convenience method to write an unsigned 8-bit byte to `io`.
  #
  # This is the same as calling `io.write_byte(value)` and is included for
  # consistency.
  @[AlwaysInline]
  def self.writeUInt8(io : IO, value : UInt8) : Nil
    io.write_byte(value)
  end

  # Convenience method to write an unsigned 8-bit byte.
  #
  # This is the same as calling `io.write_byte(value)` and is included
  # for consistency.
  @[AlwaysInline]
  def writeUInt8(value : UInt8) : Nil
    self.write_byte(value)
  end

  {% begin %}
    {% classes = [Int16, UInt16, Int32, UInt32, Int64, UInt64, Int128, UInt128] %}
    {% bits = [16, 16, 32, 32, 64, 64, 128, 128] %}
    {% for i in 0...classes.size %}
      # Convenience method to read a signed {{bits[i]}}-bit little-endian integer from `io`.
      #
      # This is the same as calling `io.read_bytes({{classes[i]}}, IO::ByteFormat::LittleEndian)`,
      # just shorter.
      @[AlwaysInline]
      def self.read{{classes[i].id}}(io : IO) : {{classes[i].id}}
        io.read_bytes({{classes[i].id}}, IO::ByteFormat::LittleEndian)
      end

      # Convenience method to read a signed {{bits[i]}}-bit big-endian integer from `io`.
      #
      # This is the same as calling `io.read_bytes({{classes[i].id}}, IO::ByteFormat::BigEndian)`,
      # just shorter.
      @[AlwaysInline]
      def self.read{{classes[i].id}}BE(io : IO) : {{classes[i].id}}
        io.read_bytes({{classes[i].id}}, IO::ByteFormat::BigEndian)
      end

      # Convenience method to read a signed {{bits[i]}}-bit little-endian integer.
      #
      # This is the same as calling `read_bytes({{classes[i].id}}, IO::ByteFormat::LittleEndian)`,
      # just shorter.
      @[AlwaysInline]
      def read{{classes[i].id}}
        IO.read{{classes[i].id}}(self)
      end

      # Convenience method to read a signed {{bits[i]}}-bit big-endian integer.
      #
      # This is the same as calling `read_bytes({{classes[i].id}}, IO::ByteFormat::BigEndian)`,
      # just shorter.
      @[AlwaysInline]
      def read{{classes[i].id}}BE
        IO.read{{classes[i].id}}BE(self)
      end

      # Convenience method to write a signed {{bits[i]}}-bit little-endian integer.  This returns `self.
      #
      # This is the same as calling `write_bytes(value, IO::ByteFormat::LittleEndian)`,
      # just shorter.
      @[AlwaysInline]
      def write{{classes[i].id}}(value : {{classes[i].id}}) : self
        self.write_bytes(value, IO::ByteFormat::LittleEndian)
        self
      end

      # Convenience method to write a signed {{bits[i]}}-bit big-endian integer.  This returns `self.
      #
      # This is the same as calling `write_bytes(value, IO::ByteFormat::BigEndian)`,
      # just shorter.
      @[AlwaysInline]
      def write{{classes[i].id}}BE(value : {{classes[i].id}}) : self
        self.write_bytes(value, IO::ByteFormat::BigEndian)
        self
      end
    {% end %}
  {% end %}

  # Convenience method to write a signed 32-bit little-endian float.  This returns `self.
  #
  # This is the same as calling `write_bytes(value, IO::ByteFormat::LittleEndian)`,
  # just shorter.
  @[AlwaysInline]
  def writeFloat32(value : Float32) : self
    self.write_bytes(value, IO::ByteFormat::LittleEndian)
    self
  end

  # Convenience method to write a signed 64-bit little-endian float.  This returns `self.
  #
  # This is the same as calling `write_bytes(value, IO::ByteFormat::LittleEndian)`,
  # just shorter.
  @[AlwaysInline]
  def writeFloat64(value : Float64) : self
    self.write_bytes(value, IO::ByteFormat::LittleEndian)
    self
  end

  # Convenience method to write a signed 32-bit big-endian float.  This returns `self.
  #
  # This is the same as calling `write_bytes(value, IO::ByteFormat::BigEndian)`,
  # just shorter.
  @[AlwaysInline]
  def writeFloat32BE(value : Float32) : self
    self.write_bytes(value, IO::ByteFormat::BigEndian)
    self
  end

  # Convenience method to write a signed 64-bit big-endian float.  This returns `self.
  #
  # This is the same as calling `write_bytes(value, IO::ByteFormat::BigEndian)`,
  # just shorter.
  @[AlwaysInline]
  def writeFloat64BE(value : Float64) : self
    self.write_bytes(value, IO::ByteFormat::BigEndian)
    self
  end


  # Convenience method to read a 32-bit little-endian floating point number from `io`.
  #
  # This is the same as calling `io.read_bytes(Float32, IO::ByteFormat::LittleEndian)`,
  # just shorter.
  @[AlwaysInline]
  def self.readFloat32(io : IO) : Float32
    io.read_bytes(Float32, IO::ByteFormat::LittleEndian)
  end

  # Convenience method to read a 32-bit big-endian floating point number from `io`.
  #
  # This is the same as calling `io.read_bytes(Float32, IO::ByteFormat::BigEndian)`,
  # just shorter.
  @[AlwaysInline]
  def self.readFloat32BE(io : IO) : Float32
    io.read_bytes(Float32, IO::ByteFormat::BigEndian)
  end

  # Convenience method to read a 32-bit little-endian floating point number.
  #
  # This is the same as calling `io.read_bytes(Float32, IO::ByteFormat::LittleEndian)`,
  # just shorter.
  @[AlwaysInline]
  def readFloat32
    IO.readFloat32(self)
  end

  # Convenience method to read a 32-bit big-endian floating point number.
  #
  # This is the same as calling `io.read_bytes(Float32, IO::ByteFormat::BigEndian)`,
  # just shorter.
  @[AlwaysInline]
  def readFloat32BE
    IO.readFloat32BE(self)
  end

  # Convenience method to read a 64-bit little-endian floating point number from `io`.
  #
  # This is the same as calling `io.read_bytes(Float64, IO::ByteFormat::LittleEndian)`,
  # just shorter.
  @[AlwaysInline]
  def self.readFloat64(io : IO) : Float64
    io.read_bytes(Float64, IO::ByteFormat::LittleEndian)
  end

  # Convenience method to read a 64-bit big-endian floating point number from `io`.
  #
  # This is the same as calling `io.read_bytes(Float64, IO::ByteFormat::BigEndian)`,
  # just shorter.
  @[AlwaysInline]
  def self.readFloat64BE(io : IO) : Float64
    io.read_bytes(Float64, IO::ByteFormat::LittleEndian)
  end

  # Convenience method to read a 64-bit little-endian floating point number.
  #
  # This is the same as calling `io.read_bytes(Float64, IO::ByteFormat::LittleEndian)`,
  # just shorter.
  @[AlwaysInline]
  def readFloat64
    IO.readFloat64(self)
  end

  # Convenience method to read a 64-bit big-endian floating point number.
  #
  # This is the same as calling `io.read_bytes(Float64, IO::ByteFormat::BigEndian)`,
  # just shorter.
  @[AlwaysInline]
  def readFloat64BE
    IO.readFloat64BE(self)
  end
end

module RemiLib
  # :nodoc:
  PRETTY_SIZE_SUFFIXES = [
    "Bytes", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"
  ]

  PRETTY_SIZE_SUFFIXES_SHORT = [
    "B", "K", "M", "G", "T", "P", "E", "Z", "Y"
  ]
end

struct ::Int
  # Similar to `Int#humanize_bytes`, except that this always uses
  # `Int::BinaryPrefixFormat::JEDEC` for the format, and formats numbers
  # slightly differently.
  #
  # If `alwaysShowAsBytes` is true, then this will always show the size as
  # bytes.
  #
  # `separator` is used for the decimal separator.  `delimiter` is used only
  # when `alwaysShowAsBytes` is true, and is used as the thousands delimiter.
  #
  # `padding` instances of `padChar` will be inserted to the left of the
  # formatted size if `padding` is positive.  If it's negative, then that many
  # instances of `padChar` will be inserted to the right of the formatted size.
  def prettySize(*, alwaysShowAsBytes : Bool = false, decimalPlaces : Int = 2, separator : Char = '.',
                 delimiter : Char = ',', padding : Int = 0, padChar : Char = ' ', shortSuffix : Bool = false) : String
    String.build do |str|
      self.prettySize(str, alwaysShowAsBytes: alwaysShowAsBytes, decimalPlaces: decimalPlaces, separator: separator,
                      delimiter: delimiter, padding: padding, padChar: padChar,
                      shortSuffix: shortSuffix)
    end
  end

  def prettySize(io : IO, *, alwaysShowAsBytes : Bool = false, decimalPlaces : Int = 2, separator : Char = '.',
                 delimiter : Char = ',', padding : Int = 0, padChar : Char = ' ', shortSuffix : Bool = false) : Nil
    if padding > 0
      padding.times { |_| io << padChar }
    end

    if alwaysShowAsBytes
      self.format(io, separator, delimiter, only_significant: false)
      io << (shortSuffix ? " B" : " Bytes")
    else
      mag : Int128 = if self > 0
                       Math.log(self, 1024).to_i128!
                     elsif self < 0
                       Math.log(self.abs, 1024).to_i128!
                     else
                       0i128
                     end
      humanSize : Float64 = if self > 0
                              self.to_i128! / (1u128 << (mag * 10))
                            elsif self < 0
                              -(self.abs.to_i128! / (1u128 << (mag * 10)))
                            else
                              0.0f64
                            end

      if mag == 0
        self.format(io, separator, delimiter, only_significant: false)
        io << (shortSuffix ? " B" : " Bytes")
      elsif decimalPlaces == 0
        humanSize.to_i.format(io, separator, delimiter, only_significant: true)
        io << ' ' << (shortSuffix ? RemiLib::PRETTY_SIZE_SUFFIXES_SHORT[mag] :
                                    RemiLib::PRETTY_SIZE_SUFFIXES[mag])
      else
        humanSize.format(io, separator, delimiter, decimalPlaces, only_significant: false)
        io << ' ' << (shortSuffix ? RemiLib::PRETTY_SIZE_SUFFIXES_SHORT[mag] :
                                    RemiLib::PRETTY_SIZE_SUFFIXES[mag])
      end
    end

    if padding < 0
      padding.abs.times { |_| io << padChar }
    end
  end

  ROMAN_NUMERALS = [
    {1000, "M"},
    {900, "CM"},
    {500, "D"},
    {400, "CD"},
    {100, "C"},
    {90, "XC"},
    {50, "L"},
    {40, "XL"},
    {10, "X"},
    {9, "IX"},
    {5, "V"},
    {4, "IV"},
    {1, "I"}]

  ENGLISH_NUMBERS = {
    0 => "zero",
    1 => "one",
    2 => "two",
    3 => "three",
    4 => "four",
    5 => "five",
    6 => "six",
    7 => "seven",
    8 => "eight",
    9 => "nine",
    10 => "ten",
    11 => "eleven",
    12 => "twelve",
    13 => "thirteen",
    14 => "fourteen",
    15 => "fifteen",
    16 => "sixteen",
    17 => "seventeen",
    18 => "eighteen",
    19 => "nineteen",
    20 => "twenty",
    30 => "thirty",
    40 => "forty",
    50 => "fifty",
    60 => "sixty",
    70 => "seventy",
    80 => "eighty",
    90 => "ninety"
  }.to_h

  # Converts this number to a Roman numeral.  Only values between 1
  # and 3999, inclusive, are supported, otherwise an `ArgumentError`
  # is raised.
  def toRoman : String
    unless self > 0 && self < 4000
      raise ArgumentError.new("Cannot represent number as a roman numeral: #{self}")
    end

    self.toRoman? || raise "Unexpected nil from #toRoman?"
  end

  # Converts this number to a Roman numeral.  Only values between 1
  # and 3999, inclusive, are supported, otherwise this returns `nil`.
  def toRoman? : String?
    # Adapted from https://github.com/jkfurtney/clformat
    unless self > 0 && self < 4000
      return nil
    end

    i = self
    String.build do |str|
      ROMAN_NUMERALS.each do |num|
        count = i // num[0]
        str << num[1] * count
        i -= num[0] * count
      end
    end
  end

  # :nodoc:
  ONE_DUODECILLION = BigInt.new("1000000000000000000000000000000000000000")

  # :nodoc:
  ONE_TREDECILLION = BigInt.new("1000000000000000000000000000000000000000000")

  # Converts a number into a string such that it appears as English words.  For
  # example, 100 would return `"one hundred"`.
  @[AlwaysInline]
  def positiveSpokenNumber : String
    Int.positiveSpokenNumber(self)
  end

  # :ditto:
  def self.positiveSpokenNumber(num) : String
    # Adapted from https://github.com/jkfurtney/clformat

    # Try to early out
    return ENGLISH_NUMBERS[num] if ENGLISH_NUMBERS[num]?

    raise "ENGLISH_NUMBERS check failed" if num <= 20

    rem = 0
    String.build do |str|
      {% begin %}
        case
        when num < 100
          str << positiveSpokenNumber((num // 10) * 10) << '-' << positiveSpokenNumber(num % 10)

          {% if @type != Int8 && @type != UInt8 %}
          when num < 1000
            str << positiveSpokenNumber(num // 100) << ' ' << "hundred "
            rem = num % 100

          when num < 1000000
            str << positiveSpokenNumber(num // 1000) << ' ' << "thousand"
            rem = num % 1000

          when num < 1000000000
            str << positiveSpokenNumber(num // 1000000) << ' ' << "million"
            rem = num % 1000000

          when num < 1000000000000
            str << positiveSpokenNumber(num // 1000000000) << ' ' << "billion"
            rem = num % 1000000000

            {% if @type != Int16 && @type != UInt16 %}
            when num < 1000000000000000
              str << positiveSpokenNumber(num // 1000000000000) << ' ' << "trillion"
              rem = num % 1000000000000

            when num < 1000000000000000000
              str << positiveSpokenNumber(num // 1000000000000000) << ' ' << "quadrillion"
              rem = num % 1000000000000000

            when num < 1000000000000000000000_i128
              str << positiveSpokenNumber(num // 1000000000000000000_i128) << ' ' << "quintillion"
              rem = num % 1000000000000000000_i128

              {% if @type != Int32 && @type != UInt32 %}
              when num < 1000000000000000000000000_i128
                str << positiveSpokenNumber(num // 1000000000000000000000_i128) << ' ' << "sextillion"
                rem = num % 1000000000000000000000_i128

              when num < 1000000000000000000000000000_i128
                str << positiveSpokenNumber(num // 1000000000000000000000000_i128) << ' ' << "septillion"
                rem = num % 1000000000000000000000000_i128

              when num < 1000000000000000000000000000000_i128
                str << positiveSpokenNumber(num // 1000000000000000000000000000_i128) << ' ' << "octillion"
                rem = num % 1000000000000000000000000000_i128

              when num < 1000000000000000000000000000000000_i128
                str << positiveSpokenNumber(num // 1000000000000000000000000000000_i128) << ' ' << "nonillion"
                rem = num % 1000000000000000000000000000000_i128

              when num < 1000000000000000000000000000000000000_i128
                str << positiveSpokenNumber(num // 1000000000000000000000000000000000_i128) << ' ' << "decillion"
                rem = num % 1000000000000000000000000000000000_i128

                {% if @type != Int64 && @type != UInt64 %}
                when num < ONE_DUODECILLION
                  str << positiveSpokenNumber(num // 1000000000000000000000000000000000000_i128) << ' ' << "undecillion"
                  rem = num % 1000000000000000000000000000000000000_i128

                  {% if BigFloat.has_method?(:to_i128) %}
                  when num < ONE_TREDECILLION
                    str << positiveSpokenNumber(num // ONE_DUODECILLION) << ' ' << "duodecillion"
                    rem = num % ONE_DUODECILLION
                  {% end %}

                {% end %}
              {% end %}
            {% end %}
          {% end %}
        else
          raise ArgumentError.new("Number cannot be represented as an English number")
        end
      {% end %}

      unless rem == 0
        str << (rem >= 100 ? ", " : "") << positiveSpokenNumber(rem)
      end
    end
  end

  def toSpoken : String
    {% begin %}
      {% if @type != UInt8 && @type != UInt16 && @type != UInt32 && @type != UInt64 && @type != UInt128 %}
        if self < 0
          return "minus #{Int.positiveSpokenNumber(-self)}"
        end
      {% end %}
    {% end %}

    Int.positiveSpokenNumber(self)
  end
end

{% begin %}
  {% classes = [::Int8, ::UInt8, ::Int16, ::UInt16, ::Int32, ::UInt32, ::Int64, ::UInt64, ::Int128, ::UInt128] %}
  {% bitSizes = [8, 8, 16, 16, 32, 32, 64, 64, 128, 128] %}
  {% for i in 0...classes.size %}
    struct {{classes[i]}}
      # Returns the number of zero bits in the number before a 1 bit is encountered.
      def numLeadingZeros : Int
        total = 0
        {{bitSizes[i] - 1}}.downto(0) do |i|
          if self.bit(i) == 0
            total += 1
          else
            break
          end
        end

        total
      end

      # Returns the number of one bits in the number before a 0 bit is encountered.
      def numLeadingOnes : Int
        total = 0
        {{bitSizes[i] - 1}}.downto(0) do |i|
          if self.bit(i) == 1
            total += 1
          else
            break
          end
        end

        total
      end
    end
  {% end %}
{% end %}

{% begin %}
  {% classes = [::Int8, ::UInt8, ::Int16, ::UInt16, ::Int32, ::UInt32, ::Int64, ::UInt64, ::Int128, ::UInt128] %}
  {% maskSuffixes = [:_u8, :_u8, :_u16, :_u16, :_u32, :_u32, :_u64, :_u64, :_u128, :_u128] %}
  {% retClasses = [Union(::Int8, ::UInt8), Union(::Int16, ::UInt16), Union(::Int32, ::UInt32),
                   Union(::Int64, ::UInt64), Union(::Int128, ::UInt128)] %}
  {% bitSizes = [8, 8, 16, 16, 32, 32, 64, 64, 128, 128] %}
  {% for i in 0...classes.size %}
    struct {{classes[i]}}
      # Converts this integer to a number that is `bits` bits wide.  If
      # `asUnsigned` is `true`, then the converted value is converted into an
      # unsigned integer of `bits` size, otherwise it is converted to a
      # twos-complement signed integer of `bits` size.
      #
      # `bits` must be at least 1.  If `bits` is greater than or equal to the
      # size of `self` in bits, then `self` is simply returned (possibly
      # converted to an unsigned version if `asUnsigned` is `true`).
      def asBitSize(bits : Int, asUnsigned : Bool = false) : {{retClasses[i // 2]}}
        # Check the requested bit size.
        if bits <= 0
          raise ArgumentError.new("Bad bit size for an integer of #{ {{bitSizes[i]}} } bits")
        elsif bits >= {{bitSizes[i]}}
          if asUnsigned
            return self.to_u{{bitSizes[i]}}!
          else
            return self.to_i{{bitSizes[i]}}!
          end
        end

        # (logand x (logior x (- (mask-field (byte 1 (1- bit-size)) x))))
        mask = ((2{{maskSuffixes[i].id}} << (bits - 1)) - 1)
        ret = self.to_u{{bitSizes[i]}}! & mask

        if asUnsigned
          ret
        else
          if ret.bit(bits - 1) == 0
            ret.to_i{{bitSizes[i]}}
          else
            retMask = -{{bitSizes[i]}}.to_u{{bitSizes[i]}}! & ~mask
            (ret | retMask).to_i{{bitSizes[i]}}!
          end
        end
      end
    end
  {% end %}
{% end %}

class File
  # Creates a file at *path* that is *size* bytes large, filled entirely with
  # zeros.
  def self.createEmpty(path : String|Path, size : Int) : Nil
    File.open(path, "wb") do |file|
      if size > 0
        file.seek((size - 1).to_i64, File::Seek::Set)
        file.write_byte(0)
        file.flush
      end
    end
  end
end

{% begin %}
  {% if compare_versions(Crystal::VERSION, "1.7.0") < 0 %}
    {% puts "Monkey patching byte_swap into the integer classes" %}

    {% classes = [::Int16, ::UInt16, ::Int32, ::UInt32, ::Int64, ::UInt64, ::Int128, ::UInt128] %}
    {% sizes = [2, 2, 4, 4, 8, 8, 16, 16] %}
    {% for klass in 0...classes.size %}
      struct {{classes[klass]}}
        # Swaps the bytes of self; a little-endian value becomes a big-endian
        # value, and vice-versa. The bit order within each byte is unchanged.
        def byte_swap : self
          {% for i in 0...sizes[klass] %}
            b{{i}} = (self & {{0xFF << (8 * i)}}) >> {{8 * i}}
          {% end %}

          {% for i in 0...sizes[klass] %}
            (b{{i}} << {{8 * ((sizes[klass] - 1) - i)}}) |
          {% end %}
          0
        end
      end
    {% end %}
  {% end %}
{% end %}
