module CouchPotato
  module Persistence
    module PropertyMethods
      private

      def load_attribute_from_document(name)
        if _document.has_key?(name.to_sym) || _document.has_key?(name.to_s)
          property = self.class.properties.find{|property| property.name == name}
          @skip_dirty_tracking = true
          value = property.build(self, _document)
          @skip_dirty_tracking = false
          value
        end
      end
    end

    class SimpleProperty  #:nodoc:
      attr_accessor :name, :type

      def initialize(owner_clazz, name, options = {})
        self.name = name
        self.type = options[:type]
        @type_caster = TypeCaster.new
        owner_clazz.send :include, PropertyMethods unless owner_clazz.ancestors.include?(PropertyMethods)

        define_accessors accessors_module_for(owner_clazz), name, options
      end

      def build(object, json)
        value = json[name.to_s].nil? ? json[name.to_sym] : json[name.to_s]
        object.send "#{name}=", value
      end

      def dirty?(object)
        object.send("#{name}_changed?")
      end

      def serialize(json, object)
        json[name] = object.send name
      end
      alias :value :serialize

      private

      def accessors_module_for(clazz)
        module_name = "#{clazz.name.to_s.gsub('::', '__')}AccessorMethods"
        unless clazz.const_defined?(module_name)
          accessors_module = clazz.const_set(module_name, Module.new)
          clazz.send(:include, accessors_module)
        end
        clazz.const_get(module_name)
      end

      def define_accessors(base, name, options)
        base.class_eval do
          define_method "#{name}" do
            load_attribute_from_document(name) unless instance_variable_defined?("@#{name}")
            value = instance_variable_get("@#{name}")
            if value.nil? && !options[:default].nil?
              default = if options[:default].respond_to?(:call)
                options[:default].call
              else
                clone_attribute(options[:default])
              end
              self.instance_variable_set("@#{name}", default)
              default
            else
              value
            end
          end

          define_method "#{name}=" do |value|
            typecasted_value = type_caster.cast(value, options[:type])
            send("#{name}_will_change!") unless @skip_dirty_tracking || typecasted_value == send(name)
            self.instance_variable_set("@#{name}", typecasted_value)
          end

          define_method "#{name}?" do
            !self.send(name).nil? && !self.send(name).try(:blank?)
          end
        end
      end
    end
  end
end
