require "degu/renum"

module Degu
  module HasEnum
    def self.included(modul)
      modul.extend(ClassMethods)
    end

    module ClassMethods

       # Use like this
       #
       #   class Furniture
       #     has_enum :colors, :column_name => :custom_color_type
       #   end
      def has_enum(enum_name, options={})

        enum_column = options.key?(:column_name) ? options[:column_name].to_s : "#{enum_name}_type"

        self.send("validate", "#{enum_column}_check_for_valid_type_of_enum".to_sym)

        # throws a NameError if Enum Class doesn't exists
        enum_class = options.key?(:class_name) ? options[:class_name].to_s.constantize : enum_name.to_s.camelize.constantize

        # Enum must be a Renum::EnumeratedValue Enum
        raise ArgumentError, "expected Renum::EnumeratedValue" unless enum_class.superclass == Renum::EnumeratedValue

        if respond_to?(:after_save)
          define_method("reset_enum_changed") do
            @enum_changed = false
          end
          after_save :reset_enum_changed
        end

        define_method("#{enum_name}") do
          begin
            return self[enum_column].present? ? enum_class[self[enum_column]] : nil
          rescue NameError => e
            return nil
          end
        end

        define_method("#{enum_column}=") do |enum_literal|
          unless enum_literal == self[enum_column]
            self[enum_column] = enum_literal
            @enum_changed = true
          end
        end

        define_method("#{enum_name}=") do |enum_to_set|
          old_value = self[enum_column]
          enum_resolved = enum_class[enum_to_set]
          if enum_to_set.to_s.strip.empty?
            self[enum_column] = nil
          elsif enum_resolved
            column_type =
              if self.class.respond_to?(:columns_hash) and
                column_definition = self.class.columns_hash[enum_column]
              then
                column_definition.type
              end
            self[enum_column] =
              case column_type
              when :integer
                enum_resolved.index
              else
                enum_resolved.name
              end
          else
            raise ArgumentError, "could not resolve #{enum_to_set.inspect}"
          end
          @enum_changed ||= self[enum_column] != old_value
        end

        define_method("#{enum_name}_has_changed?") do
          !!@enum_changed
        end

        enum_class.each do |enum|
          define_method("#{enum_name}_#{enum.underscored_name}?") do
            __send__(enum_name.to_sym) == enum
          end
        end

        define_method("#{enum_column}_check_for_valid_type_of_enum") do
          return true if self[enum_column].nil? || self[enum_column].to_s.empty?
          unless enum_class[self[enum_column]].present?
            self.errors.add(enum_column.to_sym, "Wrong type '#{self[enum_column]}' for enum '#{enum_name}'")
            return false
          end
          return true
        end
      end

    end
  end
end
