# frozen_string_literal: true

require "active_support/test_case"
require "fileutils"
require "json"
require "nokogiri"
require "open-uri"
require_dependency "file_helper"

EMOJI_GROUPS_PATH ||= "lib/emoji/groups.json"

EMOJI_DB_PATH ||= "lib/emoji/db.json"

EMOJI_IMAGES_PATH ||= "public/images/emoji"

EMOJI_ORDERING_URL ||= "http://www.unicode.org/emoji/charts/emoji-ordering.html"

# Format is search pattern => associated emojis
# eg: "cry" => [ "sob" ]
# for a "cry" query should return: cry and sob
SEARCH_ALIASES ||= {
  "sad" => [ "frowning_face", "slightly_frowning_face", "sob", "crying_cat_face", "cry" ],
  "cry" => [ "sob" ]
}

# emoji aliases are actually created as images
# eg: "right_anger_bubble" => [ "anger_right" ]
# your app will physically have right_anger_bubble.png and anger_right.png
EMOJI_ALIASES ||= {
  "right_anger_bubble" => [ "anger_right" ],
  "ballot_box" => [ "ballot_box_with_ballot" ],
  "basketball_man" => [ "basketball_player", "person_with_ball" ],
  "beach_umbrella" => [ "umbrella_on_ground", "beach", "beach_with_umbrella" ],
  "parasol_on_ground" => [ "umbrella_on_ground" ],
  "bellhop_bell" => [ "bellhop" ],
  "biohazard" => [ "biohazard_sign" ],
  "bow_and_arrow" => [ "archery" ],
  "spiral_calendar" => [ "calendar_spiral", "spiral_calendar_pad" ],
  "card_file_box" => [ "card_box" ],
  "champagne" => [ "bottle_with_popping_cork" ],
  "cheese" => [ "cheese_wedge" ],
  "city_sunset" => [ "city_dusk" ],
  "couch_and_lamp" => [ "couch" ],
  "crayon" => [ "lower_left_crayon" ],
  "cricket_bat_and_ball" => [ "cricket_bat_ball" ],
  "latin_cross" => [ "cross" ],
  "dagger" => [ "dagger_knife" ],
  "desktop_computer" => [ "desktop" ],
  "card_index_dividers" => [ "dividers" ],
  "dove" => [ "dove_of_peace" ],
  "footprints" => [ "feet" ],
  "fire" => [ "flame" ],
  "black_flag" => [ "flag_black", "waving_black_flag" ],
  "cn" => [ "flag_cn" ],
  "de" => [ "flag_de" ],
  "es" => [ "flag_es" ],
  "fr" => [ "flag_fr" ],
  "uk" => [ "gb", "flag_gb" ],
  "it" => [ "flag_it" ],
  "jp" => [ "flag_jp" ],
  "kr" => [ "flag_kr" ],
  "ru" => [ "flag_ru" ],
  "us" => [ "flag_us" ],
  "white_flag" => [ "flag_white", "waving_white_flag" ],
  "plate_with_cutlery" => [ "fork_knife_plate", "fork_and_knife_with_plate" ],
  "framed_picture" => [ "frame_photo", "frame_with_picture" ],
  "hammer_and_pick" => [ "hammer_pick" ],
  "heavy_heart_exclamation" => [ "heart_exclamation", "heavy_heart_exclamation_mark_ornament" ],
  "houses" => [ "homes", "house_buildings" ],
  "hotdog" => [ "hot_dog" ],
  "derelict_house" => [ "house_abandoned", "derelict_house_building" ],
  "desert_island" => [ "island" ],
  "old_key" => [ "key2" ],
  "laughing" => [ "satisfied" ],
  "business_suit_levitating" => [ "levitate", "man_in_business_suit_levitating" ],
  "weight_lifting_man" => [ "lifter", "weight_lifter" ],
  "medal_sports" => [ "medal", "sports_medal" ],
  "metal" => [ "sign_of_the_horns" ],
  "fu" => [ "middle_finger", "reversed_hand_with_middle_finger_extended" ],
  "motorcycle" => [ "racing_motorcycle" ],
  "mountain_snow" => [ "snow_capped_mountain" ],
  "newspaper_roll" => [ "newspaper2", "rolled_up_newspaper" ],
  "spiral_notepad" => [ "notepad_spiral", "spiral_note_pad" ],
  "oil_drum" => [ "oil" ],
  "older_woman" => [ "grandma" ],
  "paintbrush" => [ "lower_left_paintbrush" ],
  "paperclips" => [ "linked_paperclips" ],
  "pause_button" => [ "double_vertical_bar" ],
  "peace_symbol" => [ "peace" ],
  "fountain_pen" => [ "pen_fountain", "lower_left_fountain_pen" ],
  "ping_pong" => [ "table_tennis" ],
  "place_of_worship" => [ "worship_symbol" ],
  "poop" => [ "shit", "hankey", "poo" ],
  "radioactive" => [ "radioactive_sign" ],
  "railway_track" => [ "railroad_track" ],
  "robot" => [ "robot_face" ],
  "skull" => [ "skeleton" ],
  "skull_and_crossbones" => [ "skull_crossbones" ],
  "speaking_head" => [ "speaking_head_in_silhouette" ],
  "male_detective" => [ "spy", "sleuth_or_spy" ],
  "thinking" => [ "thinking_face" ],
  "-1" => [ "thumbsdown" ],
  "+1" => [ "thumbsup" ],
  "cloud_with_lightning_and_rain" => [ "thunder_cloud_rain", "thunder_cloud_and_rain" ],
  "tickets" => [ "admission_tickets" ],
  "next_track_button" => [ "track_next", "next_track" ],
  "previous_track_button" => [ "track_previous", "previous_track" ],
  "unicorn" => [ "unicorn_face" ],
  "funeral_urn" => [ "urn" ],
  "sun_behind_large_cloud" => [ "white_sun_cloud", "white_sun_behind_cloud" ],
  "sun_behind_rain_cloud" => [ "white_sun_rain_cloud", "white_sun_behind_cloud_with_rain" ],
  "partly_sunny" => [ "white_sun_small_cloud", "white_sun_with_small_cloud" ],
  "open_umbrella" => [ "umbrella2" ],
  "hammer_and_wrench" => [ "tools" ],
  "face_with_thermometer" => [ "thermometer_face" ],
  "timer_clock" => [ "timer" ],
  "keycap_ten" => [ "ten" ],
  "memo" => [ "pencil" ],
  "rescue_worker_helmet" => [ "helmet_with_cross", "helmet_with_white_cross" ],
  "slightly_smiling_face" => [ "slightly_smiling", "slight_smile"],
  "construction_worker_man" => [ "construction_worker" ],
  "upside_down_face" => [ "upside_down" ],
  "money_mouth_face" => [ "money_mouth" ],
  "nerd_face" => [ "nerd" ],
  "hugs" => [ "hugging", "hugging_face" ],
  "roll_eyes" => [ "rolling_eyes", "face_with_rolling_eyes" ],
  "slightly_frowning_face" => [ "slight_frown" ],
  "frowning_face" => [ "frowning2", "white_frowning_face" ],
  "zipper_mouth_face" => [ "zipper_mouth" ],
  "face_with_head_bandage" => [ "head_bandage" ],
  "raised_hand_with_fingers_splayed" => [ "hand_splayed" ],
  "raised_hand" => [ "hand" ],
  "vulcan_salute" => [ "vulcan", "raised_hand_with_part_between_middle_and_ring_fingers" ],
  "policeman" => [ "cop" ],
  "running_man" => [ "runner" ],
  "walking_man" => [ "walking" ],
  "bowing_man" => [ "bow" ],
  "no_good_woman" => [ "no_good" ],
  "raising_hand_woman" => [ "raising_hand" ],
  "pouting_woman" => [ "person_with_pouting_face" ],
  "frowning_woman" => [ "person_frowning" ],
  "haircut_woman" => [ "haircut" ],
  "massage_woman" => [ "massage" ],
  "tshirt" => [ "shirt" ],
  "biking_man" => [ "bicyclist" ],
  "mountain_biking_man" => [ "mountain_bicyclist" ],
  "passenger_ship" => [ "cruise_ship" ],
  "motor_boat" => [ "motorboat", "boat" ],
  "flight_arrival" => [ "airplane_arriving" ],
  "flight_departure" => [ "airplane_departure" ],
  "small_airplane" => [ "airplane_small" ],
  "racing_car" => [ "race_car" ],
  "family_man_woman_boy_boy" => [ "family_man_woman_boys" ],
  "family_man_woman_girl_girl" => [ "family_man_woman_girls" ],
  "family_woman_woman_boy" => [ "family_women_boy" ],
  "family_woman_woman_girl" => [ "family_women_girl" ],
  "family_woman_woman_girl_boy" => [ "family_women_girl_boy" ],
  "family_woman_woman_boy_boy" => [ "family_women_boys" ],
  "family_woman_woman_girl_girl" => [ "family_women_girls" ],
  "family_man_man_boy" => [ "family_men_boy" ],
  "family_man_man_girl" => [ "family_men_girl" ],
  "family_man_man_girl_boy" => [ "family_men_girl_boy" ],
  "family_man_man_boy_boy" => [ "family_men_boys" ],
  "family_man_man_girl_girl" => [ "family_men_girls" ],
  "cloud_with_lightning" => [ "cloud_lightning" ],
  "tornado" => [ "cloud_tornado", "cloud_with_tornado" ],
  "cloud_with_rain" => [ "cloud_rain" ],
  "cloud_with_snow" => [ "cloud_snow" ],
  "asterisk" => [ "keycap_star" ],
  "studio_microphone" => [ "microphone2" ],
  "medal_military" => [ "military_medal" ],
  "couple_with_heart_woman_woman" => [ "female_couple_with_heart" ],
  "couple_with_heart_man_man" => [ "male_couple_with_heart" ],
  "couplekiss_woman_woman" => [ "female_couplekiss" ],
  "couplekiss_man_man" => [ "male_couplekiss" ],
  "honeybee" => [ "bee" ],
  "lion" => [ "lion_face" ],
  "artificial_satellite" => [ "satellite_orbital" ],
  "computer_mouse" => [ "mouse_three_button", "three_button_mouse" ],
  "hocho" => [ "knife" ],
  "swimming_man" => [ "swimmer" ],
  "wind_face" => [ "wind_blowing_face" ],
  "golfing_man" => [ "golfer" ],
  "facepunch" => [ "punch" ],
  "building_construction" => [ "construction_site" ],
  "family_man_woman_girl_boy" => [ "family" ],
  "ice_hockey" => [ "hockey" ],
  "snowman_with_snow" => [ "snowman2" ],
  "play_or_pause_button" => [ "play_pause" ],
  "film_projector" => [ "projector" ],
  "shopping" => [ "shopping_bags" ],
  "open_book" => [ "book" ],
  "national_park" => [ "park" ],
  "world_map" => [ "map" ],
  "pen" => [ "pen_ballpoint", "lower_left_ballpoint_pen" ],
  "email" => [ "envelope", "e-mail" ],
  "phone" => [ "telephone" ],
  "atom_symbol" => [ "atom" ],
  "mantelpiece_clock" => [ "clock" ],
  "camera_flash" => [ "camera_with_flash" ],
  "film_strip" => [ "film_frames" ],
  "balance_scale" => [ "scales" ],
  "surfing_man" => [ "surfer" ],
  "couplekiss_man_woman" => [ "couplekiss" ],
  "couple_with_heart_woman_man" => [ "couple_with_heart" ],
  "clamp" => [ "compression" ],
  "dancing_women" => [ "dancers" ],
  "blonde_man" => [ "person_with_blond_hair" ],
  "sleeping_bed" => [ "sleeping_accommodation" ],
  "om" => [ "om_symbol" ],
  "tipping_hand_woman" => [ "information_desk_person" ],
  "rowing_man" => [ "rowboat" ],
  "new_moon" => [ "moon" ],
  "oncoming_automobile" => [ "car", "automobile" ],
  "fleur_de_lis" => [ "fleur-de-lis" ],
  "face_vomiting" => [ "puke" ]
}

EMOJI_GROUPS ||= [
  {
    "name" => "smileys_&_emotion",
    "tabicon" => "grinning"
  },
  {
    "name" => "people_&_body",
    "tabicon" => "wave"
  },
  {
    "name" => "animals_&_nature",
    "tabicon" => "evergreen_tree"
  },
  {
    "name" => "food_&_drink",
    "tabicon" => "hamburger"
  },
  {
    "name" => "travel_&_places",
    "tabicon" => "airplane"
  },
  {
    "name" => "activities",
    "tabicon" => "soccer"
  },
  {
    "name" => "objects",
    "tabicon" => "eyeglasses"
  },
  {
    "name" => "symbols",
    "tabicon" => "white_check_mark"
  },
  {
    "name" => "flags",
    "tabicon" => "checkered_flag"
  }
]

FITZPATRICK_SCALE ||= [ "1f3fb", "1f3fc", "1f3fd", "1f3fe", "1f3ff" ]

DEFAULT_SET ||= "twitter"

# Replace the platform by another when downloading the image (accepts names or categories)
EMOJI_IMAGES_PATCH ||= {
  "apple" => { "snowboarder" => "twitter" },
  "windows" => { "country-flag" => "twitter" }
}

EMOJI_SETS ||= {
  "apple" => "apple",
  "google" => "google",
  "google_blob" => "google_classic",
  "facebook" => "facebook_messenger",
  "twitter" => "twitter",
  "windows" => "win10",
}

EMOJI_DB_REPO ||= "git@github.com:jjaffeux/emoji-db.git"

EMOJI_DB_REPO_PATH ||= File.join("tmp", "emoji-db")

GENERATED_PATH ||= File.join(EMOJI_DB_REPO_PATH, "generated")

desc "update emoji images"
task "emoji:update" do
  copy_emoji_db

  json_db = open(File.join(GENERATED_PATH, "db.json")).read
  db = JSON.parse(json_db)

  write_db_json(db["emojis"], db["translations"])
  fix_incomplete_sets(db["emojis"])
  write_aliases
  groups = generate_emoji_groups(db["emojis"], db["sections"])
  write_js_groups(db["emojis"], groups)
  optimize_images(Dir.glob(File.join(Rails.root, EMOJI_IMAGES_PATH, "/**/*.png")))

  TestEmojiUpdate.run_and_summarize

  FileUtils.rm_rf(EMOJI_DB_REPO_PATH)
end

desc "test the emoji generation script"
task "emoji:test" do
  ENV['EMOJI_TEST'] = "1"
  Rake::Task["emoji:update"].invoke
end

def optimize_images(images)
  images.each do |filename|
    FileHelper.image_optim(
      allow_pngquant: true,
      strip_image_metadata: true
    ).optimize_image!(filename)
  end
end

def copy_emoji_db
  `rm -rf tmp/emoji-db && git clone #{EMOJI_DB_REPO} tmp/emoji-db`

  path = "#{EMOJI_IMAGES_PATH}/**/*"
  confirm_overwrite(path)
  puts "Cleaning emoji folder..."
  FileUtils.rm_rf(Dir.glob(path))

  EMOJI_SETS.each do |set_name, set_destination|
    origin = File.join(GENERATED_PATH, set_name)
    destination = File.join(EMOJI_IMAGES_PATH, set_destination)
    FileUtils.mv(origin, destination)
  end
end

def fix_incomplete_sets(emojis)
  emojis.each do |code, config|
    EMOJI_SETS.each do |set_name, set_destination|
      patch_set = EMOJI_SETS[EMOJI_IMAGES_PATCH.dig(set_name, config["name"])] ||
        EMOJI_SETS[EMOJI_IMAGES_PATCH.dig(set_name, config["category"])]

      if patch_set || !File.exist?(File.join(EMOJI_IMAGES_PATH, set_destination, "#{config['name']}.png"))
        origin = File.join(EMOJI_IMAGES_PATH, patch_set || EMOJI_SETS[DEFAULT_SET], config['name'])

        FileUtils.cp("#{origin}.png", File.join(EMOJI_IMAGES_PATH, set_destination, "#{config['name']}.png"))
        if File.directory?(origin)
          FileUtils.cp_r(origin, File.join(EMOJI_IMAGES_PATH, set_destination, config['name']))
        end
      end
    end
  end
end

def generate_emoji_groups(keywords, sections)
  puts "Generating groups..."

  list = open(EMOJI_ORDERING_URL).read
  doc = Nokogiri::HTML5(list)
  table = doc.css("table")[0]

  EMOJI_GROUPS.map do |group|
    group["icons"] ||= []

    sub_sections = sections[group["name"]]["sub_sections"]
    sub_sections.each do |section|
      title_section = table.css("tr th a[@name='#{section}']")
      emoji_list_section = title_section.first.parent.parent.next_element
      emoji_list_section.css("a.plain img").each do |link|
        emoji_code = link.attr("title")
          .scan(/U\+(.{4,5})\b/)
          .flatten
          .map { |code| code.downcase.strip }
          .join("_")

        emoji_char = code_to_emoji(emoji_code)

        if emoji = keywords[emoji_char]
          group["icons"] << { name: emoji["name"], diversity: emoji["fitzpatrick_scale"] }
        end
      end
    end
    group.delete("sections")
    group
  end
end

def write_aliases
  EMOJI_ALIASES.each do |original, aliases|
    aliases.each do |emoji_alias|
      EMOJI_SETS.each do |set_name, set_destination|
        origin_file = File.join(EMOJI_IMAGES_PATH, set_destination, "#{original}.png")
        origin_dir = File.join(EMOJI_IMAGES_PATH, set_destination, original)
        FileUtils.cp(origin_file, File.join(EMOJI_IMAGES_PATH, set_destination, "#{emoji_alias}.png"))

        if File.directory?(origin_dir)
          FileUtils.cp_r(origin_dir, File.join(EMOJI_IMAGES_PATH, set_destination, emoji_alias))
        end
      end
    end
  end
end

def write_db_json(emojis, translations)
  puts "Writing #{EMOJI_DB_PATH}..."

  confirm_overwrite(EMOJI_DB_PATH)

  FileUtils.mkdir_p(File.expand_path("..", EMOJI_DB_PATH))

  # skin tones variations of emojis shouldn’t appear in autocomplete
  emojis_without_tones = emojis
    .select { |char, config|
                           !FITZPATRICK_SCALE.any? { |scale|
                             codepoints_to_code(char.codepoints, config["fitzpatrick_scale"])[scale]
                           }
                         }
    .map { |char, config|
    {
      "code" => codepoints_to_code(char.codepoints, config["fitzpatrick_scale"]).tr("_", "-"),
      "name" => config["name"]
    }
  }

  emoji_with_tones = emojis
    .select { |code, config| config["fitzpatrick_scale"] }
    .map { |code, config| config["name"] }

  db = {
    "emojis" => emojis_without_tones,
    "tonableEmojis" => emoji_with_tones,
    "aliases" => EMOJI_ALIASES,
    "searchAliases" => SEARCH_ALIASES,
    "translations" => translations
  }

  File.write(EMOJI_DB_PATH, JSON.pretty_generate(db))
end

def write_js_groups(emojis, groups)
  puts "Writing #{EMOJI_GROUPS_PATH}..."

  confirm_overwrite(EMOJI_GROUPS_PATH)

  template = JSON.pretty_generate(groups)
  FileUtils.mkdir_p(File.expand_path("..", EMOJI_GROUPS_PATH))
  File.write(EMOJI_GROUPS_PATH, template)
end

def code_to_emoji(code)
  code
    .split("_")
    .map { |e| e.to_i(16) }
    .pack "U*"
end

def codepoints_to_code(codepoints, fitzpatrick_scale)
  codepoints = codepoints
    .map { |c| c.to_s(16).rjust(4, "0") }
    .join("_")
    .downcase

  if !fitzpatrick_scale
    codepoints.gsub!(/_fe0f$/, "")
  end

  codepoints
end

def confirm_overwrite(path)
  return if ENV['EMOJI_TEST']

  STDOUT.puts("[!] You are about to overwrite #{path}, are you sure? [CTRL+c] to cancel, [ENTER] to continue")
  STDIN.gets.chomp
end

class TestEmojiUpdate < MiniTest::Test
  def self.run_and_summarize
    puts "Runnings tests..."
    reporter = Minitest::SummaryReporter.new
    TestEmojiUpdate.run(reporter)
    puts reporter.to_s
  end

  def image_path(style, name)
    File.join("public", "images", "emoji", style, "#{name}.png")
  end

  def test_code_to_emoji
    assert_equal "😎", code_to_emoji("1f60e")
  end

  def test_codepoints_to_code
    assert_equal "1f6b5_200d_2640", codepoints_to_code([128693, 8205, 9792, 65039], false)
  end

  def test_codepoints_to_code_with_scale
    assert_equal "1f6b5_200d_2640_fe0f", codepoints_to_code([128693, 8205, 9792, 65039], true)
  end

  def test_groups_js_es6_creation
    assert File.exists?(EMOJI_GROUPS_PATH)
    assert File.size?(EMOJI_GROUPS_PATH)
  end

  def test_db_json_creation
    assert File.exists?(EMOJI_DB_PATH)
    assert File.size?(EMOJI_DB_PATH)
  end

  def test_alias_creation
    original_image = image_path("apple", "right_anger_bubble")
    alias_image = image_path("apple", "anger_right")

    assert_equal File.size(original_image), File.size(alias_image)
  end

  def test_cell_index_patch
    original_image = image_path("apple", "snowboarder")
    alias_image = image_path("twitter", "snowboarder")

    assert_equal File.size(original_image), File.size(alias_image)
  end

  def test_scales
    original_image = image_path("apple", "blonde_woman")
    assert File.exists?(original_image)
    assert File.size?(original_image)

    (2..6).each do |scale|
      image = image_path("apple", "blonde_woman/#{scale}")
      assert File.exists?(image)
      assert File.size?(image)
    end
  end

  def test_default_set
    original_image = image_path("twitter", "snowboarder")
    alias_image = image_path("apple", "snowboarder")
    assert_equal File.size(original_image), File.size(alias_image)

    original_image = image_path("twitter", "macau")
    alias_image = image_path("win10", "macau")
    assert_equal File.size(original_image), File.size(alias_image)
  end
end
