require 'dragonfly'
require 'dragonfly/serializer'
require 'dragonfly/utils'
require 'dragonfly/model/attachment'

module Dragonfly
  module Model
    module ClassMethods

      def dragonfly_attachment_classes
        @dragonfly_attachment_classes ||= begin
          parent_class = ancestors.select{|a| a.is_a?(Class) }[1]
          if parent_class.respond_to?(:dragonfly_attachment_classes)
            parent_class.dragonfly_attachment_classes.map do |klass|
              new_dragonfly_attachment_class(klass.attribute, klass.app, klass.config_block)
            end
          else
            []
          end
        end
      end

      private

      def dragonfly_accessor(attribute, opts={}, &config_block)
        app = case opts[:app]
        when Symbol, nil then Dragonfly.app(opts[:app])
        else opts[:app]
        end

        # Add callbacks
        before_save :save_dragonfly_attachments if respond_to?(:before_save)
        case
        when respond_to?(:after_commit)
          after_commit :destroy_dragonfly_attachments, on: :destroy
        when respond_to?(:after_destroy)
          after_destroy :destroy_dragonfly_attachments
        end

        # Register the new attribute
        dragonfly_attachment_classes << new_dragonfly_attachment_class(attribute, app, config_block)

        # Define an anonymous module for all of the attribute-specific instance
        # methods.
        instance_methods = Module.new do
          # Define the setter for the attribute
          define_method "#{attribute}=" do |value|
            dragonfly_attachments[attribute].assign(value)
          end

          # Define the getter for the attribute
          define_method attribute do
            dragonfly_attachments[attribute].to_value
          end

          # Define the xxx_stored? method
          define_method "#{attribute}_stored?" do
            dragonfly_attachments[attribute].stored?
          end

          # Define the xxx_changed? method
          define_method "#{attribute}_changed?" do
            dragonfly_attachments[attribute].changed?
          end

          # Define the URL setter
          define_method "#{attribute}_url=" do |url|
            unless Utils.blank?(url)
              dragonfly_attachments[attribute].assign(app.fetch_url(url))
            end
          end

          # Define the URL getter
          define_method "#{attribute}_url" do
            nil
          end

          # Define the remove setter
          define_method "remove_#{attribute}=" do |value|
            unless [0, "0", false, "false", "", nil].include?(value)
              dragonfly_attachments[attribute].assign(nil)
              instance_variable_set("@remove_#{attribute}", true)
            end
          end

          # Define the remove getter
          attr_reader "remove_#{attribute}"

          # Define the retained setter
          define_method "retained_#{attribute}=" do |string|
            unless Utils.blank?(string)
              begin
                dragonfly_attachments[attribute].retained_attrs = Serializer.json_b64_decode(string)
              rescue Serializer::BadString
                Dragonfly.warn("couldn't update attachment with serialized retained_#{attribute} string #{string.inspect}")
              end
            end
            dragonfly_attachments[attribute].should_retain = true
            dragonfly_attachments[attribute].retain!
            string
          end

          # Define the retained getter
          define_method "retained_#{attribute}" do
            attrs = dragonfly_attachments[attribute].retained_attrs
            Serializer.json_b64_encode(attrs) if attrs
          end
        end

        include instance_methods
      end

      def new_dragonfly_attachment_class(attribute, app, config_block)
        Class.new(Attachment).init(self, attribute, app, config_block)
      end

    end
  end
end
