module Bearcat
  class ApiArray
    include Enumerable

    attr_reader :status, :headers, :members

    def self.process_response(response, api_client)
      if response.body.is_a?(Array)
        ApiArray.new(response, api_client)
      elsif key = array_key(response)
        ApiArray.new(response, api_client, key)
      else
        response.body
      end
    end

    def initialize(response, api_client, array_key = nil)
      @api_client = api_client
      @array_key = array_key
      @page_count = 100
      case response.status
      when 200..206
        @members = process_body(response)
        @status = response.status
        @headers = response.headers
        @method = response.env[:method]
        init_pages(@headers['Link'])
      end
    end

    def [](i)
      @members[i]
    end

    def last
      @members.last
    end

    def each(&block)
      @members.each { |member| block.call(member) }
    end

    def pages?
      @link_hash['next'] || @link_hash['prev']
    end

    def next_page
      load_page('next')
    end

    def prev_page
      load_page('prev')
    end

    def first_page
      load_page('first')
    end

    def last_page
      load_page('last')
    end

    def each_page(page_count = 50, &block)
      @page_count = page_count
      if pages?
        response = get_page(@link_hash['first'])
        @headers = response.headers
        @status = response.status
        @method = response.env[:method]
        init_pages(@headers[:link])
        @members = process_body(response)
        block.call(@members)
        while @link_hash['next']
          response = get_page(@link_hash['next'])
          @headers = response.headers
          @status = response.status
          @method = response.env[:method]
          @members = process_body(response)
          init_pages(@headers[:link])
          block.call(@members)
        end
        @link_hash = {}
      else
        block.call(@members)
      end
    end

    def all_pages_each(page_count = 50, &block)
      each_page(page_count) do |page|
        page.each &block
      end
    end

    def all_pages!(page_count = 50)
      if pages?
        @page_count = page_count
        response = get_page(@link_hash['first'])
        @headers = response.headers
        @status = response.status
        @method = response.env[:method]
        init_pages(@headers[:link])
        @members = process_body(response)
        while @link_hash['next']
          response = get_page(@link_hash['next'])
          @headers = response.headers
          @status = response.status
          @method = response.env[:method]
          @members.concat(process_body(response))
          init_pages(@headers[:link])
        end
        @link_hash = {}
      end
      self
    end

    private

    def init_pages(link_header)
      @link_hash = {}
      if @headers.has_key? 'Link'
        links = link_header.split(/,\s?/)

        links.each do |link|
          link_parts = link.split(/;\s?/)
          url = link_parts.shift.strip.gsub(/^<|>$/, '')
          rel = link_parts.find { |part| part.gsub('"', '').split('=').first == 'rel' }
          @link_hash[rel.gsub('"', '').split('=').last] = url
        end
      end
    end

    def get_page(url, params = {})
      params['per_page'] = @page_count unless params.key?('per_page') || !@page_count
      query = URI.parse(url).query
      p = CGI.parse(query)
      u = url.gsub("?#{query}", '')

      # strip value out of array if value is an array and key doesn't have [] (parameter is not an array parameter)
      p.each { |k, v| p[k] = v.first if v.is_a?(Array) && k !~ /\[\]$/ }
      # remove [] from key names, this is copied from rails' {}.transform_keys!
      p.keys.each { |k| p[k.delete('[]')] = p.delete(k) }
      # merge params
      p.merge!(params)

      @api_client.connection.send(:get) do |r|
        r.url(u, p)
      end
    end

    def load_page(rel)
      if @link_hash.has_key? rel
        response = get_page(@link_hash[rel])
        ApiArray.process_response(response, @api_client)
      end
    end

    #TODO: This is a quick fix for JSONAPI responses if we need to do this for anything else we need to do this a better way
    def self.array_key(response)
      key = nil
      if response.env[:method] == :get
        path = response.env[:url].path
        if path.match(/.*\/(courses||groups)\/\d+\/conferences/)
          key = 'conferences'
        elsif path.match(/.*\/accounts\/(?:(?:\d+~)?\d+|self)\/terms/)
          key = 'enrollment_terms'
        end
      end
      key
    end

    def process_body(response)
      if response.body.is_a?(Array)
        response.body
      elsif response.body.is_a?(Hash) && @array_key
        response.body[@array_key]
      end
    end

  end
end