module Yaks
  class Runner
    include Util
    include Anima.new(:object, :config, :options)
    include Adamantium::Flat
    extend Forwardable

    def_delegators :config,        :policy, :default_format, :format_options_hash,
                                   :primitivize, :serializers
    def_delegators :format_class,  :media_type, :format_name

    def call
      Pipeline.new(steps).insert_hooks(hooks).call(object, env)
    end

    def read
      Pipeline.new([[:parse, serializer.inverse], [:format, formatter.inverse]]).insert_hooks(hooks).call(object, env)
    end

    def map
      Pipeline.new([[:map, mapper]]).insert_hooks(hooks).call(object, env)
    end

    def context
      {
        policy: policy,
        env: env,
        mapper_stack: []
      }.merge(slice_hash(options, :item_mapper))
    end
    memoize :context, freezer: :flat

    def env
      options.fetch(:env, {})
    end
    memoize :env, freezer: :noop

    # @return [Class]
    def format_class
      Format.by_accept_header(env['HTTP_ACCEPT']) {
        Format.by_name(options.fetch(:format) { default_format })
      }
    end
    memoize :format_class, freezer: :noop

    def steps
      [[ :map, mapper ],
       [ :format, formatter ],
       [ :primitivize, primitivizer],
       [ :serialize, serializer ]]
    end
    memoize :steps

    def mapper
      options.fetch(:mapper) do
        policy.derive_mapper_from_object(object)
      end.new(context)
    end
    memoize :mapper, freezer: :noop

    def formatter
      format_class.new(format_options_hash[format_name])
    end
    memoize :formatter, freezer: :noop

    def primitivizer
      proc do |input|
        if format_class.serializer.equal? :json
          primitivize.call(input)
        else
          input
        end
      end
    end
    memoize :primitivizer

    def serializer
      serializers.fetch(format_class.serializer)
    end
    memoize :serializer, freezer: :noop

    def hooks
      config.hooks + options.fetch(:hooks, [])
    end

  end
end
