module GenericRadix
  # def symbols to return a string describing your alphabet
  # def transform to transform the string before and after conversion

  @int = 0
  @str = ""

  # Alphabet length
  def radix
    @radix ||= symbols.length
  end
  module_function :radix

  # Does the passed string contain only valid symbols for this alphabet?
  def symbols_valid?(number)
    number = transform(number) if respond_to?(:transform)
    ( number.split(//) - symbols.split(//) ).empty?
  end
  module_function :symbols_valid?

  # Convert an arbitrary-size integer into a string using the defined alphabet
  def itos(number)
    number = number.to_i
    return symbols[0, 1] if number.zero?
    res = ""
    until number.zero?
      index = number % radix
      number = number / radix
      res = symbols[index, 1] + res
    end
    res = transform(res) if respond_to?(:transform)
    res
  end
  module_function :itos

  # Convert a string using the defined alphabet into an arbitrary-size integer
  def stoi(number)
    number = transform(number) if respond_to?(:transform)
    number = number.dup.to_s.split(//)
    res = 0
    until number.length.zero?
      char = number.shift
      index = symbols.index(char)
      res *= radix
      res += index
    end
    res
  end
  module_function :stoi

  # Instance initializer
  def initialize(number = 0)
    if number.is_a?(Fixnum) || number.is_a?(Bignum)
      @int = number.to_i
      @str = itos(@int)
    elsif symbols_valid?(number.to_s)
      number = transform(number) if respond_to?(:transform)
      @str = number.to_s
      @int = stoi(@str)
    else
      raise "Not an Integer or a radix #{radix} string"
    end
  end

  def to_i
    @int
  end

  def to_s
    @str
  end

  def zero?
    @int.zero?
  end
end

# Sample classes:

# Binary
class Base2
  def symbols
    "01"
  end
  include GenericRadix
end

# Octal
class Base8
  def symbols
    "01234567"
  end
  include GenericRadix
end

# Hexadecimal
class Base16
  def symbols
    "0123456789ABCDEF"
  end
  # Transform the string before and after translation
  def transform(string)
    string.upcase
  end
  include GenericRadix
end

# Octagesimal
class Base80
  def symbols
    "!()*,-.0123456789:;@ABCDEFGHIJKLMNOPQRSTUVWXYZ[]^_abcdefghijklmnopqrstuvwxyz{|}~"
  end
  include GenericRadix
end

# 8-bit
class Base256
  def symbols
    ( Range.new(0,255).map {|ord| ord.chr }.join )
  end
  include GenericRadix
end