require "social_avatar_proxy/config"
require "social_avatar_proxy/facebook_avatar"
require "social_avatar_proxy/twitter_avatar"
require "social_avatar_proxy/routes"
require "social_avatar_proxy/timeout_error"
require "social_avatar_proxy/too_many_redirects_error"
require "rack"

module SocialAvatarProxy
  class App
    def self.call(env, options = {})
      new(options).call(env)
    end
    
    def self.routes(options = {})
      new(options).routes
    end
    
    attr_reader :options, :request
    
    def initialize(options = {})
      @options = {
        expires: 86400, # 1 day from now
        cache_control: {
          max_age: 86400, # store for 1 day, after that re-request
          max_stale: 86400, # allow cache to be a day stale
          public: true # allow proxy caching
        }
      }.merge(options)
    end
    
    def call(env)
      @request = Rack::Request.new(env)
      begin
        response.finish
      rescue TimeoutError => e
        timeout
      rescue TooManyRedirectsError => e
        bad_gateway
      end
    end
    
    def path_prefix
      (options[:path_prefix] || (request && request.env["SCRIPT_NAME"]) || "").gsub(/\/$/, "")
    end
    
    def response
      # ensure this is a valid avatar request
      unless request.path =~ /^#{path_prefix}\/(facebook|twitter)\/([\w\-\.]+)(\.(jpe?g|png|gif|bmp))?$/i
        return not_found
      end
      # load the avatar
      avatar = load_avatar($1, $2)
      # if it exists
      if avatar.exist?
        # render the avatar to the response
        Rack::Response.new do |response|
          # set the response data
          response.write(avatar.body)
          # set the response headers
          response = set_avatar_headers(response, avatar)
          response = set_caching_headers(response)
          # return the response
          response
        end
      # if the avatar doesn't exist
      else
        not_found
      end
    end
    
    def not_found
      Rack::Response.new([], 404) do |response|
        # if we have a custom default image
        if Config.default_image
          render_default_image(response)
        # without a default image
        else
          response.write "Not Found"
        end
      end
    end
    
    def timeout
      Rack::Response.new([], 504) do |response|
        # if we have a custom default image
        if Config.default_image
          render_default_image(response)
        # without a default image
        else
          response.write "Remote server timeout"
        end
      end
    end
    
    def bad_gateway
      Rack::Response.new([], 502) do |response|
        # if we have a custom default image
        if Config.default_image
          render_default_image(response)
        # without a default image
        else
          response.write "Bad response from remote server"
        end
      end
    end
    
    def routes
      Routes.new(self)
    end
    
    private
    def render_default_image(response)
      # render the image
      response.write(Config.default_image_data)
      # set the content type
      response["Content-Type"] = Config.default_image_content_type
      # set expiry
      response = set_caching_headers(response)
      # return the response
      response
    end
  
    def set_avatar_headers(response, avatar)
      # set the last modified header
      response["Last-Modified"] = avatar.last_modified
      # set the content type header
      response["Content-Type"] = avatar.content_type
      # return the response
      response
    end
    
    def set_caching_headers(response)
      # if we want to expire in a set time, calculate the header
      if options[:expires]
        response["Expires"] = (Time.now + options[:expires]).httpdate
      end
      # if we want to set cache control settings
      if cc = options[:cache_control]
        directives = []
        directives << "no-cache" if cc[:no_cache]
        directives << "max-stale=#{cc[:max_stale]}" if cc[:max_stale]
        directives << "max-age=#{cc[:max_age]}" if cc[:max_age]
        directives << (cc[:public] ? "public" : "private")
        response["Cache-Control"] = directives.join(", ")
      end
      # return the response
      response
    end
    
    def load_avatar(service, id)
      # titleize the service name
      service = service.gsub(/[\_\-]/, " ").gsub(/\b([a-z])/) do |match|
        match.upcase
      end
      # pass it onto the
      SocialAvatarProxy.const_get("#{service}Avatar").new(id)
    end
  end
end