module Domain
  module ClassMethods
    %w(byte integer clock12 clock24 signed_byte signed_integer).each do |word|
      define_method(word.to_sym) do |name|
        register_new_variable word.to_sym, name
      end
    end

    private

    attr_reader :domain_storage

    def register_new_variable(type, name)
      @domain_storage ||= Hash.new { |k, v| k[v] = [] }
      @domain_storage[type] << name
    end
  end # module Domain::ClassMethods

  #
  # -------------------------------------
  #

  module InstanceMethods
    def initialize
      super

      hsh = self.class.instance_variable_get('@domain_storage')
      return if hsh.nil?

      hsh.each_pair do |type, methods|
        signed, base = case type
                       when :byte
                         [false, 256]
                       when :signed_byte
                         [true, 256]
                       when :integer
                         [false, 65_536]
                       when :signed_integer
                         [true, 65_536]
                       when :clock12
                         [false, 12]
                       when :clock24
                         [false, 24]
                       end

        methods.each do |m|
          warn "method '#{m}' already defined!" if respond_to?(m) || respond_to?("#{m}=")
          reg_read(m, base, signed)
          reg_write(m, base)
        end
      end

      self.class.instance_eval { remove_instance_variable '@domain_storage' }
      undef reg_read, reg_write
    end

    def reg_read(attr, base = nil, signed = false)
      self.class.class_eval do
        if signed
          define_method(attr) do
            c = instance_variable_get("@#{attr}")
            c > (base / 2) ? c - base : c
          end
        else
          define_method(attr) { instance_variable_get "@#{attr}" }
        end
      end
    end

    def reg_write(attr, base)
      self.class.class_eval do
        define_method("#{attr}=") do |other|
          raise TypeError, " #{other} is not integer!" unless other.integer?
          instance_variable_set "@#{attr}", other % base
        end
      end
    end

    class DomainError < StandardError; end
    class TypeError < DomainError; end
  end # module Domain::InstanceMethods

  #
  # -------------------------------------
  #

  module Integers
    def self.included(obj)
      obj.include InstanceMethods
      obj.extend ClassMethods
    end
  end # module Domain::Integers
end # module Domain

#
# -------------------------------------
#

class Yoba
  include Domain::Integers

  byte :b
  clock12 :cl12

end

d = Yoba.new
d.b = 1
d.b += 255
puts d.b


c = Yoba.new
puts c.b

c.cl12  = 5
c.cl12 += 10
puts c.cl12