require 'aws/s3'

module CarrierWave
  module Storage
    class AWS < Abstract
      def self.connection_cache
        @connection_cache ||= {}
      end

      def store!(file)
        File.new(uploader, connection, uploader.store_path).tap do |aws_file|
          aws_file.store(file)
        end
      end

      def retrieve!(identifier)
        File.new(uploader, connection, uploader.store_path(identifier))
      end

      def connection
        @connection ||= begin
          credentials = uploader.aws_credentials
          self.class.connection_cache[credentials] ||= ::AWS::S3.new(credentials)
        end
      end

      class File
        attr_reader :uploader, :connection, :path

        def initialize(uploader, connection, path)
          @uploader   = uploader
          @connection = connection
          @path       = path
        end

        def attributes
          file.attributes
        end

        def content_type
          @content_type || file.content_type
        end

        def content_type=(new_content_type)
          @content_type = new_content_type
        end

        def delete
          file.delete
        end

        def extension
          path.split('.').last
        end

        def read
          file.read
        end

        def size
          file.content_length
        end

        def exists?
          !!file
        end

        def store(new_file)
          aws_file = new_file.to_file

          @file = bucket.objects[path].write(aws_file, {
            acl: uploader.aws_acl,
            content_type: new_file.content_type
          }.merge(uploader.aws_attributes || {}))

          aws_file.close unless aws_file.closed?

          true
        end

        def authenticated_url
          file.url_for(:read, expires: uploader.aws_authenticated_url_expiration).to_s
        end

        def public_url
          file.public_url.to_s
        end

        def url(options = {})
          if uploader.aws_acl != :public_read
            authenticated_url
          else
            public_url
          end
        end

        def filename(options = {})
          if file_url = url(options)
            file_url.gsub(/.*\/(.*?$)/, '\1')
          end
        end

        private

        def bucket
          @bucket ||= connection.buckets[uploader.aws_bucket]
        end

        def file
          @file ||= bucket.objects[path]
        end
      end
    end
  end
end