# Issue here: https://github.com/ruby-grape/grape/issues/1028
# Solution: https://github.com/typhoeus/typhoeus/blob/master/lib/rack/typhoeus/middleware/params_decoder/helper.rb
#
# MIT License Paul Dix, David Balatero, Hans Hasselberg
#
module Locomotive
  module API
    module Middlewares

      class ParamsDecoderMiddleware

        def initialize(app)
          @app = app
        end

        def call(env)
          request = Rack::Request.new(env)
          decode(request.params).each_pair { |k, v| update_params request, k, v }
          @app.call(env)
        end

        private

        # Persist params change in environment. Extracted from:
        # https://github.com/rack/rack/blob/master/lib/rack/request.rb#L243
        def update_params(req, k, v)
          found = false
          if req.GET.has_key?(k)
            found = true
            req.GET[k] = v
          end
          if req.POST.has_key?(k)
            found = true
            req.POST[k] = v
          end
          unless found
            req.GET[k] = v
          end
        end

        def decode!(hash)
          return hash unless hash.is_a?(Hash)
          hash.each_pair do |key,value|
            if value.is_a?(Hash)
              decode!(value)
              hash[key] = convert(value)
            end
          end
          hash
        end

        def decode(hash)
          decode!(hash.dup)
        end

        # Checks if Hash is an Array encoded as a Hash.
        # Specifically will check for the Hash to have this
        # form: {'0' => v0, '1' => v1, .., 'n' => vN }
        #
        # @param hash [Hash]
        #
        # @return [Boolean] True if its a encoded Array, else false.
        def encoded?(hash)
          return false if hash.empty?
          if hash.keys.size > 1
            keys = hash.keys.map{|i| i.to_i if i.respond_to?(:to_i)}.sort
            keys == hash.keys.size.times.to_a
          else
            hash.keys.first =~ /0/
          end
        end

        # If the Hash is an array encoded by typhoeus an array is returned
        # else the self is returned
        #
        # @param hash [Hash] The Hash to convert into an Array.
        #
        # @return [Arraya/Hash]
        def convert(hash)
          if encoded?(hash)
            hash.sort{ |a, b| a[0].to_i <=> b[0].to_i }.map { |key, value| value }
          else
            hash
          end
        end

      end

    end
  end
end
