require 'csv'

class Districts

  # Generates a mapping of zip codes to districts.
  # This is done, in theory, just once after redistricting until the next one
  #
  # This mapping is generated by using our boundary services loaded ZCTAs,
  # and congressional districts. It looks up all ZCTAs in the system, then
  # checks each one to see which districts it intersects, and saves to CSV.

  # options:
  #   zip: do a particular zip code

  def self.run(options = {})
    # failsafe, should be many less records than this
    maximum = 100000

    page = 1
    per_page = options[:per_page] ? options[:per_page].to_i : 100

    zip_count = 0

    dest = Environment.config['location']['zips']
    FileUtils.mkdir_p File.dirname(dest)
    if File.exists?(dest)
    	FileUtils.rm dest
    end

    # Zip.delete_all

    errors = []

    CSV.open(dest, "w") do |csv|
      while page < (maximum / per_page)
        puts "Fetching page #{page}..."

        if options[:zip]
          zips = [options[:zip]]
        else
          zips = zips_for page, per_page, options

          if zips.nil?
            Report.failure self, "Failure paging through zips on page #{page}, aborting"
            return
          end
        end

        zips.each do |zip|
          puts "[#{zip}] Finding districts..."

          districts = districts_for zip, options
          if districts.nil?
            errors << {zip: zip, message: "Couldn't load districts intersecting this zip"}
            next
          end

          districts.each do |district|
            csv << [zip, district[:state], district[:district]]
          end

          # cache in DB for quick lookup in exactly the format we want
	  if not Zip.where(zip: zip, districts: districts).first
          	Zip.create! zip: zip, districts: districts
		puts "Creating zip for #{zip} because it doesn't exist in the database"
	  end

          puts "[#{zip}] Wrote #{districts.size} districts."
          zip_count += 1
        end

        if zips.size < per_page
          break
        else
          page += 1
        end
      end
    end

    if errors.any?
      Report.warning self, "Found #{errors.size} district lookup errors for zips", errors: errors
    end

    Report.success self, "Wrote #{zip_count} zips to #{dest}."
  end

  def self.zips_for(page, per_page, options = {})
    offset = (page - 1) * per_page
    limit = per_page

    host = Environment.config['location']['host']
    url = "http://#{host}/boundaries/?sets=zcta&limit=#{limit}&offset=#{offset}"

    response = Utils.download url, {json: true}.merge(options)
    return nil unless response

    response['objects'].map {|object| object['name']}
  end

  def self.districts_for(zip, options = {})
    host = Environment.config['location']['host']
    url = "http://#{host}/boundaries/?intersects=zcta/#{zip}&sets=cd&limit=1000"

    begin
      response = Utils.download url, {json: true, destination: destination_for(zip)}.merge(options)
    rescue Yajl::ParseError
      return nil
    end
    return nil unless response

    response['objects'].map do |object|
      pieces = object['name'].split " "
      if object['name'] =~ /at Large/i
        {state: pieces[0], district: 0}
      else
        {state: pieces[0], district: pieces[1].to_i}
      end
    end
  end

  def self.destination_for(zip)
    "data/districts/zips/#{zip}/districts.json"
  end

  def self.page_dest_for(page)
    "data/districts/pages/#{page}.json"
  end

end
