# encoding: utf-8
begin
  require 'fog'
rescue LoadError
  raise "You don't have the 'fog' gem installed. The 'aws', 'aws-s3' and 'right_aws' gems are no longer supported."
end

module CarrierWave
  module Storage

    ##
    # Uploads things to Amazon S3 using the "fog" gem.
    # You'll need to specify the access_key_id, secret_access_key and bucket.
    #
    #     CarrierWave.configure do |config|
    #       config.s3_access_key_id = "xxxxxx"
    #       config.s3_secret_access_key = "xxxxxx"
    #       config.s3_bucket = "my_bucket_name"
    #     end
    #
    # You can set the access policy for the uploaded files:
    #
    #     CarrierWave.configure do |config|
    #       config.s3_access_policy = :public_read
    #     end
    #
    # The default is :public_read. For more options see:
    #
    # http://docs.amazonwebservices.com/AmazonS3/latest/RESTAccessPolicy.html#RESTCannedAccessPolicies
    #
    # The following access policies are available:
    #
    # [:private]              No one else has any access rights.
    # [:public_read]          The anonymous principal is granted READ access.
    #                         If this policy is used on an object, it can be read from a
    #                         browser with no authentication.
    # [:public_read_write]    The anonymous principal is granted READ and WRITE access.
    # [:authenticated_read]   Any principal authenticated as a registered Amazon S3 user
    #                         is granted READ access.
    #
    # You can change the generated url to a cnamed domain by setting the cnamed config:
    #
    #     CarrierWave.configure do |config|
    #       config.s3_cnamed = true
    #       config.s3_bucket = 'bucketname.domain.tld'
    #     end
    #
    # Now the resulting url will be
    #
    #     http://bucketname.domain.tld/path/to/file
    #
    # instead of
    #
    #     http://bucketname.domain.tld.s3.amazonaws.com/path/to/file
    #
    # You can specify a region. US Standard "us-east-1" is the default.
    #
    #     CarrierWave.configure do |config|
    #       config.s3_region = 'eu-west-1'
    #     end
    #
    # Available options are defined in Fog Storage[http://github.com/geemus/fog/blob/master/lib/fog/aws/storage.rb]
    #
    #     'eu-west-1' => 's3-eu-west-1.amazonaws.com'
    #     'us-east-1' => 's3.amazonaws.com'
    #     'ap-southeast-1' => 's3-ap-southeast-1.amazonaws.com'
    #     'us-west-1' => 's3-us-west-1.amazonaws.com'
    #
    class S3 < Abstract

      class File

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

        ##
        # Returns the current path of the file on S3
        #
        # === Returns
        #
        # [String] A path
        #
        def path
          @path
        end

        ##
        # Reads the contents of the file from S3
        #
        # === Returns
        #
        # [String] contents of the file
        #
        def read
          result = connection.get_object(bucket, @path)
          @headers = result.headers
          result.body
        end

        ##
        # Remove the file from Amazon S3
        #
        def delete
          connection.delete_object(bucket, @path)
        end

        ##
        # Returns the url on Amazon's S3 service
        #
        # === Returns
        #
        # [String] file's url
        #
        def url
          if access_policy == :authenticated_read
            authenticated_url
          else
            public_url
          end
        end

        def public_url
          scheme = use_ssl? ? 'https' : 'http'
          if cnamed?
            ["#{scheme}://#{bucket}", path].compact.join('/')
          else
            ["#{scheme}://#{bucket}.s3.amazonaws.com", path].compact.join('/')
          end
        end

        def authenticated_url
          connection.get_object_https_url(bucket, path, Time.now + authentication_timeout)
        end

        def store(file)
          content_type ||= file.content_type # this might cause problems if content type changes between read and upload (unlikely)
          connection.put_object(bucket, path, file.read,
            {
              'x-amz-acl' => access_policy.to_s.gsub('_', '-'),
              'Content-Type' => content_type
            }.merge(@uploader.s3_headers)
          )
        end

        def content_type
          headers["Content-Type"]
        end

        def content_type=(type)
          headers["Content-Type"] = type
        end

        def size
           headers['Content-Length'].to_i
        end

        # Headers returned from file retrieval
        def headers
          @headers ||= begin
            connection.head_object(bucket, @path).headers
          rescue Excon::Errors::NotFound # Don't die, just return no headers
            {}
          end
        end

      private

        def use_ssl?
          @uploader.s3_use_ssl
        end

        def cnamed?
          @uploader.s3_cnamed
        end

        def access_policy
          @uploader.s3_access_policy
        end

        def bucket
          @uploader.s3_bucket
        end

        def authentication_timeout
          @uploader.s3_authentication_timeout
        end

        def connection
          @base.connection
        end

      end

      ##
      # Store the file on S3
      #
      # === Parameters
      #
      # [file (CarrierWave::SanitizedFile)] the file to store
      #
      # === Returns
      #
      # [CarrierWave::Storage::S3::File] the stored file
      #
      def store!(file)
        f = CarrierWave::Storage::S3::File.new(uploader, self, uploader.store_path)
        f.store(file)
        f
      end

      # Do something to retrieve the file
      #
      # @param [String] identifier uniquely identifies the file
      #
      # [identifier (String)] uniquely identifies the file
      #
      # === Returns
      #
      # [CarrierWave::Storage::S3::File] the stored file
      #
      def retrieve!(identifier)
        CarrierWave::Storage::S3::File.new(uploader, self, uploader.store_path(identifier))
      end

      def connection
        @connection ||= ::Fog::Storage.new(
          :aws_access_key_id      => uploader.s3_access_key_id,
          :aws_secret_access_key  => uploader.s3_secret_access_key,
          :provider               => 'AWS',
          :region                 => uploader.s3_region
        )
      end

    end # S3
  end # Storage
end # CarrierWave

