require 'digest/md5'
require File.dirname(__FILE__) + '/database'
require File.dirname(__FILE__) + '/persistence/active_model_compliance'
require File.dirname(__FILE__) + '/persistence/properties'
require File.dirname(__FILE__) + '/persistence/magic_timestamps'
require File.dirname(__FILE__) + '/persistence/callbacks'
require File.dirname(__FILE__) + '/persistence/json'
require File.dirname(__FILE__) + '/persistence/dirty_attributes'
require File.dirname(__FILE__) + '/persistence/deep_dirty_attributes'
require File.dirname(__FILE__) + '/persistence/attachments'
require File.dirname(__FILE__) + '/persistence/type_caster'
require File.dirname(__FILE__) + '/persistence/revisions'
require File.dirname(__FILE__) + '/forbidden_attributes_protection'
require File.dirname(__FILE__) + '/view/custom_views'
require File.dirname(__FILE__) + '/view/lists'
require File.dirname(__FILE__) + '/view/view_query'


module CouchPotato
  module Persistence

    def self.included(base) #:nodoc:
      base.send :include, Properties, Callbacks, Json, CouchPotato::View::CustomViews,
        CouchPotato::View::Lists
      base.send :include, DirtyAttributes, Attachments
      base.send :include, MagicTimestamps, ActiveModelCompliance,
        ForbiddenAttributesProtection, Revisions
      base.send :include, Validation
      base.class_eval do
        attr_accessor :_id, :_rev, :_deleted, :database
        alias_method :id, :_id
        alias_method :id=, :_id=

        def self.inherited(child)
          super
          CouchPotato.models << child
        end
      end

      CouchPotato.models << base
    end


    # initialize a new instance of the model optionally passing it a hash of attributes.
    # the attributes have to be declared using the #property method.
    # the new model will be yielded to an optionally given block.
    #
    # example:
    #   class Book
    #     include CouchPotato::Persistence
    #     property :title
    #   end
    #   book = Book.new :title => 'Time to Relax'
    #
    #   OR
    #
    #   book = Book.new do |b|
    #     b.title = 'Time to Relax'
    #   end
    #   book.title # => 'Time to Relax'
    def initialize(attributes = {})
      if attributes
        @skip_dirty_tracking = true
        self.attributes = attributes
        @skip_dirty_tracking = false
      end
      yield self if block_given?
    end

    # assign multiple attributes at once.
    # the attributes have to be declared using the #property method
    #
    # example:
    #   class Book
    #     include CouchPotato::Persistence
    #     property :title
    #     property :year
    #   end
    #   book = Book.new
    #   book.attributes = {'title' => 'Time to Relax', 'year' => 2009}
    #   book.title # => 'Time to Relax'
    #   book.year # => 2009
    def attributes=(hash)
      hash.each do |attribute, value|
        self.send "#{attribute}=", value
      end
    end

    # returns all of a model's attributes that have been defined using the #property method as a Hash
    #
    # example:
    #   class Book
    #     include CouchPotato::Persistence
    #     property :title
    #     property :year
    #   end
    #   book = Book.new :year => 2009
    #   book.attributes # => {'title' => nil, 'year' => 2009}
    def attributes
      self.class.properties.inject(ActiveSupport::HashWithIndifferentAccess.new) do |res, property|
        property.value(res, self)
        res
      end
    end

    def []=(attribute, value)
      send "#{attribute}=", value
    end

    def [](attribute)
      send(attribute)
    end

    def has_key?(key)
      attributes.has_key?(key)
    end

    # returns true if a  model hasn't been saved yet, false otherwise
    def new?
      _rev.nil?
    end
    alias_method :new_record?, :new?

    # returns the document id
    # this is used by rails to construct URLs
    # can be overridden to for example use slugs for URLs instead if ids
    def to_param
      _id
    end

    def ==(other) #:nodoc:
      super || (self.class == other.class && self._id.present? && self._id == other._id)
    end

    def eql?(other)
      self == other
    end

    def hash
      _id.hash * (_id.hash.to_s.size ** 10) + _rev.hash
    end

    # Returns a reloaded instance. Does not touch the original instance.
    def reload
      database.load id
    end

    def inspect
      attributes_as_string = attributes.map {|attribute, value| "#{attribute}: #{value.inspect}"}.join(", ")
      %Q{#<#{self.class} _id: "#{_id}", _rev: "#{_rev}", #{attributes_as_string}>}
    end
  end
end
