#!/usr/bin/env ruby

# Run it like this:
#
#   ./codegen/flatten_group_generator.rb > src/main/scala/com/twitter/scalding/typed/GeneratedFlattenGroup.scala

$indent = "  "

TYPES = ('A'..'Z').to_a

# generating too many implicit enrichments really slows down compile time, so we only generate
# a limited number of enrichments
MAX_IMPLICIT_ENRICHMENT_ARITY = 6

def make_nested_type(arity)
  if arity < 2 
    raise "arity < 2 doesn't make sense here"
  end
    
  if arity == 2 
    return "(#{TYPES[0]}, #{TYPES[1]})"
  else
    prev = make_nested_type(arity - 1)
    return "(#{prev}, #{TYPES[arity - 1]})"
  end
end

def make_flatten_left_join(arity)
  nested_type = make_nested_type(arity)
  flat_type = TYPES[0..(arity - 1)].join(", ")
  
  puts "#{$indent}def flattenNestedTuple[#{flat_type}](nested: #{nested_type}): (#{flat_type}) = {"
  puts "#{$indent*2}val #{nested_type.downcase} = nested"
  puts "#{$indent*2}(#{flat_type.downcase})"
  puts "#{$indent}}"
  puts
  
  if arity <= MAX_IMPLICIT_ENRICHMENT_ARITY
    puts "#{$indent}class FlattenLeftJoin#{arity}[KEY, KLL[KLL_K, +KLL_V] <: KeyedListLike[KLL_K, KLL_V, KLL], #{flat_type}](nested: KLL[KEY, #{nested_type}]) {"
    puts "#{$indent*2}def flattenValueTuple: KLL[KEY, (#{flat_type})] = nested.mapValues { tup => FlattenGroup.flattenNestedTuple(tup) }"
    puts "#{$indent}}"
    puts
    puts "#{$indent}implicit def toFlattenLeftJoin#{arity}[KEY, KLL[KLL_K, +KLL_V] <: KeyedListLike[KLL_K, KLL_V, KLL], #{flat_type}](nested: KLL[KEY, #{nested_type}]) = new FlattenLeftJoin#{arity}(nested)"    
  end
    
end

def make_alternating_nested_type(arity)
  if arity < 2 
    raise "arity < 2 doesn't make sense here"
  end
    
  if arity == 2 
    return "(Option[#{TYPES[0]}], Option[#{TYPES[1]}])"
  else
    prev = make_alternating_nested_type(arity - 1)
    return "(Option[#{prev}], Option[#{TYPES[arity - 1]}])"
  end
end

def make_flatten_outer_join(arity)
  nested_type = make_alternating_nested_type(arity)
  types = TYPES[0..(arity - 1)]
  flat_type = types.join(", ")
  flat_type_options = types.map {|x| "Option[#{x}]"}.join(", ")

  puts "#{$indent}def flattenNestedOptionTuple[#{flat_type}](nested: #{nested_type}): (#{flat_type_options}) = {"
  puts "#{$indent*2}val (rest1, #{TYPES[arity-1].downcase}) = nested"

  (1..(arity-3)).each do |n|
    puts "#{$indent*2}val (rest#{n+1}, #{TYPES[arity-1-n].downcase}) = rest#{n}.getOrElse(pairOfNones)"
  end
  
  puts "#{$indent*2}val (#{TYPES[0].downcase}, #{TYPES[1].downcase}) = rest#{arity-2}.getOrElse(pairOfNones)"
  
  puts "#{$indent*2}(#{flat_type.downcase})"
  
  puts "#{$indent}}"
  puts
  
  if arity <= MAX_IMPLICIT_ENRICHMENT_ARITY
    puts "#{$indent}class FlattenOuterJoin#{arity}[KEY, KLL[KLL_K, +KLL_V] <: KeyedListLike[KLL_K, KLL_V, KLL], #{flat_type}](nested: KLL[KEY, #{nested_type}]) {"
    puts "#{$indent*2}def flattenValueTuple: KLL[KEY, (#{flat_type_options})] = nested.mapValues { tup => FlattenGroup.flattenNestedOptionTuple(tup) }"
    puts "#{$indent}}"
    puts
    puts "#{$indent}implicit def toFlattenOuterJoin#{arity}[KEY, KLL[KLL_K, +KLL_V] <: KeyedListLike[KLL_K, KLL_V, KLL], #{flat_type}](nested: KLL[KEY, #{nested_type}]) = new FlattenOuterJoin#{arity}(nested)"
  end
  
end

puts "// following were autogenerated by #{__FILE__} at #{Time.now} do not edit"
puts %q|package com.twitter.scalding.typed

/**
 * Autogenerated methods for flattening the nested value tuples that result after
 * joining many pipes together. These methods can be used directly, or via the
 * the joins available in MultiJoin.
 */
object FlattenGroup {
  val pairOfNones = (None, None)

|

puts "#{$indent}// methods for flattening results of join / leftJoin"
puts
(3..22).each { |a|
  make_flatten_left_join(a)
  puts
}

puts "#{$indent}// methods for flattening results of outerJoin"
puts
(3..22).each { |a|
  make_flatten_outer_join(a)
  puts
}


puts "}"

puts "// end of autogenerated"
