#
# Copyright:: Copyright (c) 2012 Opscode, Inc.
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

require 'fileutils'
require 'uber-s3'
require 'omnibus/fetchers'

module Omnibus


  module SoftwareS3URLs

    class InsufficientSpecification < ArgumentError
    end

    def config
      Omnibus.config
    end

    def url_for(software)
      "http://#{config.s3_bucket}.s3.amazonaws.com/#{key_for_package(software)}"
    end

    private

    def key_for_package(package)
      package.name     or raise InsufficientSpecification, "Software must have a name to cache it in S3 (#{package.inspect})"
      package.version  or raise InsufficientSpecification, "Software must set a version to cache it in S3 (#{package.inspect})"
      package.checksum or raise InsufficientSpecification, "Software must specify a checksum (md5) to cache it in S3 (#{package.inspect})"
      "#{package.name}-#{package.version}-#{package.checksum}"
    end

  end

  class S3Cache

    include SoftwareS3URLs

    def initialize
      unless config.s3_bucket && config.s3_access_key && config.s3_secret_key
        raise InvalidS3Configuration.new(config.s3_bucket, config.s3_access_key, config.s3_secret_key)
      end
      @client = UberS3.new(
        :access_key         => config.s3_access_key,
        :secret_access_key  => config.s3_secret_key,
        :bucket             => config.s3_bucket,
        :adaper             => :net_http
      )
    end

    def log(msg)
      puts "[S3 Cacher] #{msg}"
    end

    def config
      Omnibus.config
    end

    def list
      existing_keys = list_by_key
      tarball_software.select {|s| existing_keys.include?(key_for_package(s))}
    end

    def list_by_key
      bucket.objects('/').map(&:key)
    end

    def missing
      already_cached = list_by_key
      tarball_software.delete_if {|s| already_cached.include?(key_for_package(s))}
    end

    def tarball_software
      Omnibus.library.select {|s| s.source && s.source.key?(:url)}
    end

    def populate
      missing.each do |software|
        fetch(software)

        key = key_for_package(software)
        content = IO.read(software.project_file)

        log "Uploading #{software.project_file} as #{config.s3_bucket}/#{key}"
        @client.store(key, content, :access => :public_read, :content_md5 => software.checksum)
      end
    end

    def fetch_missing
      missing.each do |software|
        fetch(software)
      end
    end

    private

    def ensure_cache_dir
      FileUtils.mkdir_p(config.cache_dir)
    end

    def fetch(software)
      log "Fetching #{software.name}"
      fetcher = Fetcher.without_caching_for(software)
      if fetcher.fetch_required?
        fetcher.download
        fetcher.verify_checksum!
      else
        log "Cached copy up to date, skipping."
      end
    end

    def bucket
      @bucket ||= begin
        b = UberS3::Bucket.new(@client, @client.bucket)
        # creating the bucket is idempotent, make sure it's created:
        @client.connection.put("/")
        b
      end
    end

  end
end