local memes = {}

local internet  =  require 'internet'
local imlib     =  require 'imlib2'
local CONFIG    =  require 'config'

--------------------------------------------------------------------------------
-- Util

local function reverse (l0)
    local l, j  =  {}, #l0
    for i = 1, #l0 do
        l[i], j  =  l0[j], j - 1
    end
    return l
end

local function choose(l)
    assert(type(l) == 'table')
    return l[math.random(#l)]
end

local function shallow_copy (t0)
    local t  =  {}
    for k,v in pairs(t0) do  t[k] = v  end
    return t
end

local function clean_text (text)
    return text:gsub('%s+', ' '):match('^%s*(.-)%s*$')
end

local function check_file_exists (filename)
    assert(type(filename) == 'string')
    local status  =  os.execute('test -e "'..filename..'"')
    return status == 0
end

--------------------------------------------------------------------------------
-- Internet shit


local function copy_remotely (origin_server, origin_path, target_server, target_path)
    local origin  =  origin_server and origin_server ~= 'localhost' and origin_server..':'..origin_path or origin_path
    local target  =  target_server and target_server ~= 'localhost' and target_server..':'..target_path or target_path
    local cmd  =  (origin_server == 'localhost' and target_server == 'localhost') and 'cp' or 'scp'
    --
    if origin_server == 'localhost' and not check_file_exists(origin_path) then
        error('File "'..origin_path..'" does not exist!')
    end
    --
    local status  =  os.execute(cmd..' '..origin..' '..target)
    if status ~= 0 then
        error('Could not copy file! Got error code: '..tostring(status))
    end
end

local function save_file_to_dcav (filename, ext)
    assert(type(filename) == 'string')
    -- Upload to dcav
    local ext  =  ext or filename:match '%.(%a+)$'
    local remote_name = 'otmemes_'..os.time()..'.'..ext
    copy_remotely('localhost', filename, CONFIG.STORAGE_SERVER, CONFIG.STORAGE_SERVER_PATH..remote_name)
    return CONFIG.STORAGE_DIR_URL..remote_name
end

local function save_img (img)
    assert(img)
    local filename  =  os.tmpname() .. '.' .. img:get_format()
    img:save(filename)
    img:free()
    return filename
end

local function save_img_to_cloud (img)
    assert(img)
    --
    local filename  =  save_img(img)
    return save_file_to_dcav(filename)
end

--------------------------------------------------------------------------------
-- Draw shit

local function flatten_onto(target_img, other_img, x0, y0)
    assert(x0 + other_img:get_width()  <= target_img:get_width())
    assert(y0 + other_img:get_height() <= target_img:get_height())

    for x = 0, other_img:get_width() do
        for y = 0, other_img:get_height() do
            target_img:draw_pixel(x + x0, y + y0, other_img:get_pixel(x, y))
        end
    end
    --
    return
end

local MAX_FONT_SIZE  =  1000

local function fit_font_to_line_height (font_name, height)
    assert(type(font_name) == 'string')
    assert(type(height) == 'number')
    --
    local size_min, size_max  =  1, MAX_FONT_SIZE
    while true do
        local size_guess  =  math.floor((size_max + size_min)/2)
        assert(size_min <= size_guess and size_guess <= size_max)
        local font  =  assert(imlib.font.load(font_name..'/'..size_guess))
        if size_guess == size_min then  return font, size_guess  end
        local text_w, text_h  =  font:get_size 'k'
        --
        if text_h <= height then  size_min = size_guess
        else                      size_max = size_guess
        end
    end
    return nil
end

local function load_font_of_size (font_name, size)
    return assert(imlib.font.load(font_name..'/'..size))
end

local function determine_required_font_size (font_name, text, width)
    assert(type(font_name) == 'string')
    assert(type(text) == 'string')
    assert(type(width) == 'number')
    --
    local size_min, size_max  =  1, MAX_FONT_SIZE
    while true do
        local size_guess  =  math.floor((size_max + size_min)/2)
        assert(size_min <= size_guess and size_guess <= size_max)
        local font  =  load_font_of_size(font_name, size_guess)
        if size_guess == size_min then  return font, size_guess  end
        local text_w, text_h  =  font:get_size(text)
        --
        if text_w <= width then  size_min = size_guess
        else                     size_max = size_guess
        end
    end
    return nil
end

local function wrap_lines (font, text, width)
    local lines, current, x  =  {}, {}, 0
    local SPACE_ADVANCE   =  font:get_advance ' '
    for word in text:gmatch '[^%s]+' do
        assert(type(word) == 'string')
        local word_w  =  font:get_advance(word)
        local next_x  =  x + word_w + SPACE_ADVANCE
        if word == '' then
            -- Do nothing
        elseif next_x <= width then
            current[#current+1], x  =  word, next_x
        else
            lines[#lines+1], current = table.concat(current, ' '), { word }
            x = word_w
        end
    end
    -- Færdiggør sidste linje
    lines[#lines+1] = table.concat(current, ' ')
    -- Ret
    return lines
end

local COLOR_WHITE = imlib.color.new(255, 255, 255)
local COLOR_BLACK = imlib.color.new(  0,   0,   0)


local function draw_centered_lines (draw_onto, lines, x0, y0, width, font, font_colors)
    assert(type(lines) == 'table')
    assert(type(font_colors) == 'table' and #font_colors >= 1)
    --
    local y = y0
    for i, line in ipairs(lines) do
        local text_w, text_h  =  font:get_size(line)
        local SHADOW_OFFSET   =  select(2, font:get_size(line)) / -20
        local x  =  x0 + (width - text_w) / 2
        for i, color in ipairs(type(font_colors) == 'table' and font_colors or {font_colors}) do
            local offset  =  (i-1) * SHADOW_OFFSET
            draw_onto:draw_text(font, line, x + offset, y + offset, color)
        end
        --
        y = y + text_h
    end
end

local function determine_font_and_lines_for_box (font_name, text, width, height)
    local num_lines, last_actual, actual_lines  =  1, nil, nil
    for iteration = 1, 100 do
        local estimate_font  =  determine_required_font_size(font_name, text, width * num_lines)
        local line_height = select(2, estimate_font:get_size(text))

        local estimate_num_lines  =  height / select(2, estimate_font:get_size(text))
        local lines  =  wrap_lines(estimate_font, text, width)
        local actual_num_lines = #lines
        if #lines * line_height >= height then
            estimate_num_lines = estimate_num_lines - 1
        end
        -- Test for convergence
        if last_actual == actual_num_lines then
            actual_lines  =  lines
            break
        end
        num_lines = (estimate_num_lines + actual_num_lines) / 2
        last_actual  =  actual_num_lines
    end

    assert(actual_lines)
    -- Find final font size
    local _, min_font_size  =  fit_font_to_line_height(font_name, height / #actual_lines)
    for _, line in ipairs(actual_lines) do
        min_font_size  =  math.min(min_font_size, select(2, determine_required_font_size(font_name, line, width)))
    end
    local actual_font  =  load_font_of_size(font_name, min_font_size)

    -- Assertions and error correction
    --[[
    if (#actual_lines * select(2, actual_font:get_size(text)) < height) then
        io.write(' !! Performed bad fitting of text: '..text..'\n')
        actual_font, actual_lines  =  determine_required_font_size(font_name, text, width), {text}
    end
    --]]

    return actual_font, actual_lines
end

local function draw_centered_text_in_box (font_name, draw_onto, text, x0, y0, width, height, bg_color, font_colors)
    assert(type(font_name)   == 'string')
    assert(type(font_colors) == 'table' and #font_colors >= 1)
    --
    local font, lines  =  determine_font_and_lines_for_box(font_name, text, width, height)
    --
    local y  =  y0 + (height - #lines * select(2, font:get_size(text))) / 2
    --
    if bg_color then  draw_onto:fill_rectangle(x0, y0, width, height, bg_color)  end
    draw_centered_lines(draw_onto, lines, x0, y, width, font, font_colors)
end

local function choose_random_font ()
    local fonts = imlib.font.list_fonts()
    assert(#fonts > 0, 'Could not find any fonts')
    return fonts[math.random(#fonts)]
end

local function load_random_font ()
    local font_name = choose_random_font()
    local font, errmsg  =  imlib.font.load(font_name..'/30')
    assert(font, errmsg)
    return font_name
end

local function paste_topic_onto_image (target, topic, x, y, w, h, bg_color, font_name, font_color)
    assert(target)
    assert(type(topic) == 'table')
    assert(type(font_name) == 'string')

    if not font_color then  font_color = { COLOR_BLACK }  end
    assert(type(font_color) == 'table' and #font_color >= 1)

    -- Download and paste found image
    if topic.type == 'image' then
	    assert(type(topic.filename) == 'string')
	    assert(not topic.url)

        local found_img  =  assert(imlib.image.load(topic.filename))
        found_img:crop_and_scale(0, 0, found_img:get_width(), found_img:get_height(), w, h)
        flatten_onto (target, found_img, x, y)
        found_img:free()
        --os.remove(filename)
    elseif topic.type == 'text' then
	    assert(type(topic.text) == 'string')
        local text = topic.text
        draw_centered_text_in_box(font_name, target, text, x, y, w, h, bg_color, font_color)
    elseif topic.type == 'droste' then
        target:fill_rectangle(x, y, w, h, COLOR_WHITE)
    else
        assert(false, topic.type)
    end
end

local DROSTE_ITERATIONS_DEFAULT = 10

local function draw_droste_effect (target_image, droste_positions, iterations)
    assert(type(droste_positions) == 'table')
    iterations = interations or DROSTE_ITERATIONS_DEFAULT
    assert(type(iterations)       == 'number')
    --
    if #droste_positions <= 0 or iterations <= 0 then  return target_image  end
    for _ = 1, iterations do
        for _, position in ipairs(droste_positions) do
            local paste_image = target_image:clone()
                  paste_image:crop_and_scale(0, 0, paste_image:get_width(), paste_image:get_height(), position.w, position.h)
            flatten_onto (target_image, paste_image, position.x, position.y)
            paste_image:free()
        end
    end
    return target_image
end

--------------------------------------------------------------------------------
-- Meme modifiers

local function add_compression_artifacts_to_image (image_filename, quality)
    -- Recommend quality < 10. Repeat runs at very low quality will
    -- not further destroy the image.
    assert(type(image_filename) == 'string')
    assert(type(quality) == 'number' and quality == math.floor(quality) and quality > 0 and quality < 100)
    --
    local output_filename  =  os.tmpname()..'.jpg'
    local img  =  imlib.image.load(image_filename)
          img:save(output_filename)
          img:free()
    -- Mogrify to low quality
    os.execute('mogrify -quality '..quality..' "'..output_filename..'"')
    --
    return output_filename
end

local function add_banner_to_image (image_filename, banner_filename, x_offset, background_color)
    assert(type(image_filename)  == 'string')
    assert(type(banner_filename) == 'string')
    assert(type(x_offset)        == 'number')
    --
    local image_img   =  assert(imlib.image.load(image_filename))
    local banner_img  =  assert(imlib.image.load(banner_filename))

    local combi_img   =  imlib.image.new( image_img:get_width(), image_img:get_height() + banner_img:get_height() )
    local xpos        =  (0 <= x_offset) and x_offset or (combi_img:get_width() - banner_img:get_width() + x_offset)

    -- Draw
    if background_color then
        combi_img:draw_rectangle(0, 0, combi_img:get_width(), combi_img:get_height(), background_color)
    end
    flatten_onto(combi_img, image_img,  0, 0)
    flatten_onto(combi_img, banner_img, xpos, image_img:get_height())

    -- Save and free
    image_img:free()
    banner_img:free()
    local output_filename  =  os.tmpname()..'.jpg'
    combi_img:save(output_filename)
    combi_img:free()

    return output_filename
end

local CHANCE_OF_MODIFIER  =  0.3

local modifiers = {
    -- Compression artifacts
    function (image_filename)  return add_compression_artifacts_to_image(image_filename, math.random(1, 20))  end,
    -- iFunny banner
    function (image_filename)  return add_banner_to_image(image_filename, 'images/banner_ifunny.png', -7, imlib.color.new(27, 27, 27))  end,
}

--------------------------------------------------------------------------------
-- Comparison memes

local DROSTE_EFFECT_TRIGGERS = {
    ['induktion'] = true,
    ['rekursion'] = true,
    ['uendelig rekursion'] = true,
    ['rekursive'] = true,
    ['rekursive funktioner'] = true,
    ['recursion'] = true,
    ['infinite recursion'] = true,
    ['tail recursion'] = true,
    ['recursive'] = true,
    ['recursive function'] = true,
    ['induktion'] = true,
    ['droste']    = true,
    ['drostle']   = true, -- Misspelling

    ['uendelig'] = true,
    ['uendeligt'] = true,

    ['strange loop'] = true,
    ['loop'] = true,
}

local function download_and_standardize_image (image_url)
    local file_extension =  image_url:match '%.(%a+)$'
    local url, filename  =  image_url, os.tmpname()
    assert(type(url) == 'string' and #url > 0)
    assert(type(filename) == 'string' and #filename > 0)
    local success, errmsg =  internet.download_file(url, filename)

    -- Convert svg to png
    if success and url:match '%.svg$' then
        local filename_2  =  filename..'.svg'
        os.execute('convert -density "1200" -resize 400x400 "'..filename..'" "'..filename_2..'" &> /dev/null')
        filename = filename_2
        assert(check_file_exists(filename_2))
    end
    --

    return success and filename, errmsg
end

local function fill_in_topics_information (topics)
    assert(type(topics) == 'table')
    --
    local topic_to_image_url = internet.search_images(topics)
    --
    local new_topics = {}
    for i, topic in ipairs(topics) do
        assert(type(topic) == 'string')

        local url = topic_to_image_url[topic]

        if DROSTE_EFFECT_TRIGGERS[topic] then
		new_topics[i]  =  { topic = topic, type = 'droste'             }
        elseif url                       then
		local filename   =  download_and_standardize_image(url)
		new_topics[i]  =  { topic = topic, type = 'image', filename = filename }
        else
		new_topics[i]  =  { topic = topic, type = 'text', text = topic }
        end
    end
    return new_topics
end



local function droste_positions_from_topics (topics, places)
    local droste_poss = {}
    for i, topic in ipairs(topics) do
        if topic.type == 'droste' then
            droste_poss[#droste_poss+1] = {
                x = places[i].x,  y = places[i].y,
                w = places[i].w,  h = places[i].h,
            }
        end
    end
    return droste_poss
end

local function generate_comparison_meme_generator (positions)
    assert(type(positions) == 'table')
    assert(type(positions.base_img_path) == 'string')

    return function (topics)
        assert(type(topics) == 'table' and #topics == #positions)

        local font_name = choose_random_font()

        local base_img  =  assert(imlib.image.load(positions.base_img_path))

        -- Randomize pos
        local rand_positions = {}
        for i, pos in ipairs(positions) do
            rand_positions[i] = shallow_copy(pos)
            rand_positions[i].x = pos.x + math.random(-(pos.xr or 0), pos.xr or 0)
            rand_positions[i].y = pos.y + math.random(-(pos.yr or 0), pos.yr or 0)
        end

        -- Paste topic onto head
        for index, pos in ipairs(rand_positions) do
            -- Font colors
            if pos.font_color == nil then  pos.font_color  =  COLOR_BLACK  end

            local font_colors  =  {}
            if     pos.font_color == COLOR_WHITE then  font_colors[1]  =  COLOR_BLACK
            elseif pos.font_color == COLOR_BLACK then  font_colors[1]  =  COLOR_WHITE
            end
            table.insert(font_colors, pos.font_color)

            -- Paste
            assert(base_img, topics[index])
            assert(type(font_colors) == 'table' and #font_colors >= 1)
            paste_topic_onto_image(base_img, topics[index], pos.x, pos.y, pos.w, pos.h, nil, font_name, font_colors)
        end

        -- Droste
        base_img = draw_droste_effect(base_img, droste_positions_from_topics(topics, rand_positions))

        -- Save img
        local image_filename  =  save_img(base_img)

        -- Use modifiers
        while math.random() < CHANCE_OF_MODIFIER do
            image_filename  =  choose(modifiers)(image_filename)
        end

        return save_file_to_dcav(image_filename)
    end
end

local generate_distracted_boyfriend  =  generate_comparison_meme_generator {
    base_img_path = './images/distracted_boyfriend.jpg',

    { x = 640, xr = 20, y = 50, yr = 20, w = 150, h = 150 },
    { x = 180, xr = 20, y = 50, yr = 20, w = 150, h = 150 },
}

local generate_distracted_boyfriend_oldy_times  =  generate_comparison_meme_generator {
    base_img_path = './images/distracted_boyfriend_oldy_times.jpg',

    { x = 650, xr = 10, y = 100, yr = 20, w = 150, h = 150, font_color = COLOR_WHITE },
    { x = 124, xr = 10, y =  38, yr = 10, w = 250, h = 250, font_color = COLOR_WHITE },
}

local generate_drake_egon_olsen =  generate_comparison_meme_generator {
    base_img_path = './images/drake_egon.png',

    { x = 380, xr = 0, y =   0, yr = 0, w = 380, h = 354 },
    { x = 377, xr = 0, y = 354, yr = 0, w = 383, h = 360 },
}

local generate_skorsten_image  =  generate_comparison_meme_generator {
    base_img_path = './images/bamse_skorsten_1.png',

    { x = 646, xr = 0, y = 366, yr = 0, w = 631, h = 215, font_color = COLOR_WHITE },
}

local generate_into_the_trash =  generate_comparison_meme_generator {
    base_img_path = './images/into_the_trash.png',

    { x = 377, xr = 20, y = 261, yr = 20, w = 400, h = 400, font_color = COLOR_BLACK },
}

local generate_is_this_a_pidgeon =  generate_comparison_meme_generator {
    base_img_path = './images/is_this_a_pidgeon.png',

    { x = 1100, xr = 20, y =  100, yr = 20, w = 450,       h = 450,          font_color = COLOR_WHITE },
    { x =   15, xr =  0, y = 1200, yr =  0, w = 1587 - 15, h = 1443-1200-15, font_color = COLOR_WHITE },
}

local generate_omg_his_first_word do
    local internal  =  generate_comparison_meme_generator {
        base_img_path = './images/his_first_words.png',

        { x =  20, xr = 0, y =  10, yr = 0, w = 325, h =  83, font_color = COLOR_BLACK, type = 'text' },
        { x = 371, xr = 0, y =  10, yr = 0, w = 316, h = 100, font_color = COLOR_BLACK, type = 'text' },
        { x =  29, xr = 0, y = 374, yr = 0, w = 455, h = 155, font_color = COLOR_BLACK, type = 'text' },
    }

    generate_omg_his_first_word = function (text)
        assert(type(text) == 'string')
        local short  =  text:sub(1,1)..'...'..text:sub(1,2)..'...'
        return internal {
            { type = 'text', text = short },
            { type = 'text', text = 'Åh gud! Hans første ord!' },
            { type = 'text', text = text }
        }
    end
end

local GENERATE_COMPARISON_MEME_OF_2 = {
    generate_distracted_boyfriend,
    generate_distracted_boyfriend_oldy_times,
    generate_drake_egon_olsen
}

local BRAIN_ROW_HEIGHT = 150
local BRAIN_ROW_WIDTH  = 200

local BRAIN_MAX_EXPLOSION_TOPICS  =  6
local BRAIN_DROSTE_POS = {}
for i = 1, BRAIN_MAX_EXPLOSION_TOPICS do
    BRAIN_DROSTE_POS[i] = {
        x = 0,
        y = (i-1) * BRAIN_ROW_HEIGHT,
        w = BRAIN_ROW_WIDTH,
        h = BRAIN_ROW_HEIGHT
    }
end


local function generate_brain_explosion_image (topics)
    assert(type(topics) == 'table' and 1 <= #topics and #topics <= BRAIN_MAX_EXPLOSION_TOPICS)
    --
    local base_img = imlib.image.new(BRAIN_ROW_WIDTH * 2, BRAIN_ROW_HEIGHT * #topics)
          base_img:fill_rectangle(0, 0, BRAIN_ROW_WIDTH * 2, BRAIN_ROW_HEIGHT * #topics, COLOR_WHITE)
    local font_name = choose_random_font()
    --
    for i, topic_info in ipairs(topics) do
        paste_topic_onto_image(base_img, topic_info, 0, (i-1) * BRAIN_ROW_HEIGHT, BRAIN_ROW_WIDTH, BRAIN_ROW_HEIGHT, COLOR_WHITE, font_name)

        local brain_img  =  imlib.image.load(CONFIG.IMGGEN_PATH_BRAINS..'brain_'..i..'.png')
              brain_img:crop_and_scale(0, 0, brain_img:get_width(), brain_img:get_height(), BRAIN_ROW_WIDTH, BRAIN_ROW_HEIGHT)
        flatten_onto (base_img, brain_img, BRAIN_ROW_WIDTH, (i-1) * BRAIN_ROW_HEIGHT)
        brain_img:free()
    end

    -- Droste
    base_img = draw_droste_effect(base_img, droste_positions_from_topics(topics, BRAIN_DROSTE_POS))

    -- Save
    return save_img_to_cloud(base_img)
end

local function is_comparison_message (message)
    local leq, gep  =  message:match '<', message:match '>'
    return leq and not gep and '<' or not leq and gep and '>'
end

local COMP_SENTENCES do
    local ORD_MED_ATTITYDE = {
        bedre     = 'positiv',
        dårligere = 'negativ',
        hurtigere = 'positiv',
        langsommere = 'negativt',
        robust      = 'positivt',
    }
    COMP_SENTENCES = {}
    for ord, attityde in pairs(ORD_MED_ATTITYDE) do
        local first, second = '{A}', '{B}'
        if attityde == 'negativ' then  first, second = second, first  end
        for _, fmt in ipairs { '%s er %s end %s', '%s er mere %s end %s', '%s er meget %s end %s', '%s %s end %s' } do
            table.insert(COMP_SENTENCES, fmt:format(first, ord, second))
        end
    end
end

for sentence_i, sentence in ipairs(COMP_SENTENCES) do
    local A_pos, B_pos  =  sentence:find '{A}', sentence:find '{B}'
    local best_first = (A_pos < B_pos)
    local pattern_sentence  =  sentence:gsub('{[AB]}', '(.+)'):gsub('%s+','%%s+')
    COMP_SENTENCES[pattern_sentence]  =  { best_first = best_first }
    COMP_SENTENCES[sentence_i] = nil
end

local function get_topics_from_comparison_message(message)
    local comp  =  is_comparison_message(message)
    if comp then
        local topics = {}
        for topic in message:gmatch('[^'..comp..']+') do
            local topic_text   =  clean_text(topic)
            if topic_text:match '[^%s]' then
                topics[#topics+1]  =  topic_text
            end
        end
        -- Rev
        if comp == '>' then  topics = reverse(topics)  end
        --
        return (#topics >= 2) and topics or nil
    end

    -- Natural language comparisons
    for pattern, sentence_info in pairs(COMP_SENTENCES) do
        local good, bad   =  message:lower():match(pattern)
        if not sentence_info.best_first then  good, bad  =  bad, good  end
        if good then  return { bad, good }  end
    end

    --
    return nil
end

--------------------------------------------------------------------------------
-- Other memes

local function generate_bait_link()
    return 'https://dcav.pw/jbait'
end

local curb  =  require 'curb_your_enthusiasm'

--------------------------------------------------------------------------------

local function is_image_link (str)
    if str:match '%.png$' or str:match '%.gif$' or str:match '%.jpg$' then
        return true
    end
    --
    if type(str) ~= 'string'      then  return false  end
    if not str:match '^%a+://.+$' then  return false  end
    local headers  =  internet.download_headers(str)
    return type(headers) == 'table' and headers['content-type']:match '^image/%a+$'
end

function memes.generate_for_message (user, message)

    -- Bait msg
    if message:match '%f[%a]bait%f[%A]' then
        return generate_bait_link(), 'BAIT'
    end

    -- Meme machine
    if message:match '%f[%a]meme%s+machine%f[%A]' then
        return 'https://www.youtube.com/watch?v=wl-LeTFM8zo', 'MACHINE'
    end

    do  -- Curb your enthusiasm
        local url  =  message:match '^[Cc]urb%s+[Yy]our%s+(.+)$'
        if url then
            local url2, timestamp  =  message:match '^(..-)%s+at%s+(-?[%d.]+)$'
            if url2 then  url  =  url2  end
            timestamp  =  timestamp and tonumber(timestamp) or nil
            assert(type(timestamp) == 'number' or timestamp == nil)
            local video_filename  =  curb.your_video(url, timestamp)
            assert(check_file_exists(video_filename))
            local video_url       =  save_file_to_dcav(video_filename)
            os.remove(video_filename)
            return video_url, 'CURB'
        end
    end

    -- Vælter min skorsten
    do
        local problem_text = message:lower():match 'vælter%s+min%s+skorsten%s*(.+)$'
        if problem_text then
            if problem_text:match '^er' then  problem_text = problem_text:match '^er%s*(.+)$'  end
            if problem_text:match '^:'  then  problem_text = problem_text:match '^:%s*(.+)$'   end
            local img_link = generate_skorsten_image {{ type = 'text', text = problem_text }}
            return img_link, 'SKRSTEN'
        end
    end

    -- Into the trash
    do
        -- Attempt to match
        local problem_text
        for _, pattern in ipairs {  '^(.-)%s+[Ee][Rr]%s+[Ss][Kk][Rr][Aa][Ll][Dd](.-)$',  '^(.-)%s+[Ii][Ss]%s+[Tt][Rr][Aa][Ss][Hh](.-)$' } do
            problem_text  =  message:match(pattern)
            if problem_text then  break  end
        end

        -- Create image based on match
        if problem_text then
            local topic  =  fill_in_topics_information { problem_text }
            local img_link  =  generate_into_the_trash(topic)
            return img_link, 'TRASH'
        end
    end

    -- Is this a rich picture?
    if is_image_link(message) then
        local filename, status  =  download_and_standardize_image(message)
        if filename then
            local img_link  =  generate_is_this_a_pidgeon {
                { type = 'image', filename  =  filename },
                { type = 'text',  text = 'Is this a rich picture?' }
            }
            return img_link, 'KYNG'
        else
            img_link  =  'Kunne ikke skaffe det billede. Fik fejlkode '..tostring(status)
            return img_link, '!KYNG'
        end
    end

    -- OMG his first word
    if math.random() < 0.01 then
        local img_link  =  generate_omg_his_first_word(message)
        return img_link, 'OMG'
    end

    -- Comparison memes
    local topics    =  get_topics_from_comparison_message(message)
    if not topics then  return  end

    local topics    =  fill_in_topics_information(topics)
    --
    assert(#topics >= 2)
    local img_link  =  (#topics == 2) and choose(GENERATE_COMPARISON_MEME_OF_2)(topics)
                                       or generate_brain_explosion_image(topics)
    --
    return img_link, 'COMPAR'
end

--------------------------------------------------------------------------------
-- Find memes from dankmark

function memes.generate_meme_report (subreddits, time_interval)
    assert(type(subreddits)    == 'table' and #subreddits    == 1)
    assert(type(time_interval) == 'table' and #time_interval == 2)

    local current_check_time     =  os.time()
    local new_memes  =  internet.find_reddit_memes(subreddits[1], function(t)  return 35 <= t.score and time_interval[1] < t.created and t.created <= time_interval[2]  end)
    if #new_memes == 0 then  return  end

    -- Find best meme
    local best_meme  =  new_memes[1]
    for i = 2, #new_memes do
        if best_meme.score < new_memes[i].score then  best_meme = new_memes[i]  end
    end

    -- Return message to IRC
    return string.format('Fugtig migmig fra %s: %s: %s', subreddits[1], best_meme.title, best_meme.url)
end

--------------------------------------------------------------------------------

return memes