require "json"
require "math"

class Champion
  include JSON::Serializable

  property cost : Int32
  property name : String
  property traits : Array(String)
end

class Trait
  include JSON::Serializable

  class Effect
    include JSON::Serializable

    @[JSON::Field(key: "minUnits")]
    property min_units : Int32

    @[JSON::Field(key: "maxUnits")]
    property max_units : Int32

    property style : Int32
  end

  property effects : Array(Effect)
  property name : String

  def active_at?(unit_count)
    return effects.any? do |effect|
      effect.max_units >= unit_count && effect.min_units <= unit_count
    end
  end

  def tier_at(unit_count)
    return effects.index do |effect|
      effect.max_units >= unit_count && effect.min_units <= unit_count
    end
  end

  def unique?
    return effects.any? do |effect|
      effect.style == 4
    end
  end
end

def comb(n, r)
  return ((n.to_u64-r+1)..n).product // (1.to_u64..r).product
end

def check_active(champions, trait_data, uniques = false)
  active = [] of Tuple(Trait, Int32, Int32)
  count = {} of String => Int32
  emblems = [] of String

  champions.each do |champion|
    champion.traits.tally(count)
  end
  emblems.tally(count)
  count.each do |trait_name, count|
    trait = trait_data[trait_name]
    if trait.active_at?(count) && (uniques || !trait.unique?)
      active << {trait, count, trait.tier_at(count).not_nil!}
    end
  end

  return active
end

def score_comp(champions, trait_data)
  traits = check_active(champions, trait_data)
  score = 0
  # score += 100 if champions.any?{|e| e.name == "Vander"}
  # if traits.any?{|e| e[0].name == "Family"}
  #   score += 100
  # end
  traits.each do |trait, count, tier|
    score += 3**tier
  end
  return score
end

def run_comp?(champions)
  return true #champions.any?{|e| e.name == "Vander"}
end


RES_FILE = "res/en_us.json"

file = File.open(RES_FILE)
data = JSON.parse(file)
trait_data = data["sets"]["13"]["traits"].as_a.to_json
champion_data = data["sets"]["13"]["champions"].as_a.to_json

traits = Array(Trait).from_json(trait_data).index_by(&.name)
champions = Array(Champion).from_json(champion_data)
champions.reject!{ |champ| champ.traits.empty? }
cbn = champions.index_by(&.name)

unit_count  = ARGV[0]?.try(&.to_i) || 7
cost_limit  = ARGV[1]?.try(&.to_i) || 10
min_score   = ARGV[2]?.try(&.to_i) || unit_count

champions.select!{ |champ| champ.cost <= cost_limit }
champions.sort_by!{ |champ| champ.name }
puts "#{unit_count.to_s.rjust(2)}x #{champions.size.to_s.rjust(3)} champions"

results = [] of Tuple(Array(Champion), Int32)

counter : UInt64 = 0
total = comb(champions.size, unit_count)

test_array1 = [] of Champion
champions.each_combination(unit_count, reuse: test_array1) do |units|
  next unless run_comp?(units)
  score = score_comp(units, traits)
  if score >= min_score
    results << {units.dup, score}
  end
  if counter % 100000 == 0
    percent = (counter / total * 100).format(decimal_places: 2).rjust(6) + "%"
    puts "#{percent} | #{counter.to_s.rjust(10)} | #{score.to_s.rjust(3)} | #{units.map(&.name).join(", ")}"
  end
  counter += 1
  units.clear
end

output = results.sort_by{|e| e[1]}.reverse.first(50).map do |list, count|
  <<-HERE
  #{list.map(&.name).join(", ")}
    Score:         #{count}
    Traits:        #{check_active(list, traits).size}
    Traits count:  #{check_active(list, traits).map{|e| "#{e[0].name} #{e[1]}"}.join(", ")}
  HERE
end

File.write("output.txt", output.join("\n"))

puts "\nTop Ten:"
puts "-" * 80
puts output.first(10).reverse.join("\n")
