-- Usage:
--    i - subtitle mode on/off
--    ↑, ↓ (Space) - mode specific shortcuts
-- Note:
--    The script is inspired by voracious.app (https://github.com/rsimmons/voracious).
--
--    - Listening Practice:
--        "Subtitles are initially hidden. At the end of each subtitle, the video will pause automatically.
--         Could you hear what was said? Press ↑ to replay, if necessary.
--         Then press ↓ to reveal the subs, and check if you heard correctly.
--         Then press ↓ to unpause the video."
--
--    - Reading Practice:
--        "At the start of each new subtitle, the video will pause automatically.
--         Try reading the sub. Then press ↓ to unpause the video, and hear it spoken.
--         Did you read it correctly?"
--
----------------------------------

--------- Script Options ---------
srt_file_extensions = { '.srt', '.en.srt', '.eng.srt' }
----------------------------------
sub_pad_start = 0.25
sub_pad_end = 0.2
----------------------------------

function srt_time_to_seconds(time)
    major, minor = time:match('(%d%d:%d%d:%d%d),(%d%d%d)')
    hours, mins, secs = major:match('(%d%d):(%d%d):(%d%d)')
    return hours * 3600 + mins * 60 + secs + minor / 1000
end

function seconds_to_srt_time(time)
    hours = math.floor(time / 3600)
    mins = math.floor(time / 60) % 60
    secs = math.floor(time % 60)
    milliseconds = (time * 1000) % 1000

    return string.format('%02d:%02d:%02d,%03d', hours, mins, secs, milliseconds)
end

function open_subtitles_file()
    local video_path = mp.get_property('path')
    local srt_filename = video_path:gsub('\\', '/'):match('^(.+)/.+$') .. '/' .. mp.get_property('filename/no-ext')

    for i, ext in ipairs(srt_file_extensions) do
        local f, err = io.open(srt_filename .. ext, 'r')

        if f then
            return f
        end
    end

    return false
end

function read_subtitles()
    local f = open_subtitles_file()

    if not f then
        return false
    end

    local data = f:read('*all')
    data = string.gsub(data, '\r\n', '\n')
    f:close()

    subs = {}
    subs_start = {}
    subs_end = {}
    for start_time, end_time, text in
        string.gmatch(data, '(%d%d:%d%d:%d%d,%d%d%d) %-%-> (%d%d:%d%d:%d%d,%d%d%d)\n(.-)\n\n')
    do
        if not filter_subtitles(text) then
            table.insert(subs, text)
            table.insert(subs_start, srt_time_to_seconds(start_time))
            table.insert(subs_end, srt_time_to_seconds(end_time))
        end
    end

    return true
end

function filter_subtitles(text)
    if string.match(text, '^%[(.*)%]$') then
        return true
    end

    return false
end

function adjust_subtitles()
    sub_id = 1

    while sub_id < #subs do
        if subs_start[sub_id + 1] - subs_end[sub_id] > sub_pad_end then
            subs_end[sub_id] = subs_end[sub_id] + sub_pad_end
        elseif subs_start[sub_id + 1] - subs_end[sub_id] > 0 then
            subs_end[sub_id] = subs_end[sub_id] + (subs_start[sub_id + 1] - subs_end[sub_id]) / 2
        end

        if subs_start[sub_id + 1] - subs_end[sub_id] > sub_pad_start then
            subs_start[sub_id + 1] = subs_start[sub_id + 1] - sub_pad_start
        elseif subs_start[sub_id + 1] - subs_end[sub_id] > 0 then
            subs_start[sub_id + 1] = subs_start[sub_id + 1] - (subs_start[sub_id + 1] - subs_end[sub_id]) / 2
        end

        sub_id = sub_id + 1
    end

    subs_start[1] = subs_start[1] - sub_pad_start
    subs_end[#subs] = subs_end[#subs] + sub_pad_end
end

function update_sub_id()
    local pos = mp.get_property_number('time-pos')

    if pos == nil then
        sub_id = nil
        return
    end

    sub_id = 1
    while sub_id < #subs and subs_end[sub_id] < pos do
        sub_id = sub_id + 1
    end

    if subtitle_mode == 'Reading Practice' and sub_id < #subs then
        sub_id = sub_id + 1
    end
end

function replay_sub_without_subtitles()
    if sub_id ~= nil then
        player_state = 'replay'
        mp.set_property('sub-visibility', 'no')
        mp.commandv('seek', subs_start[sub_id], 'absolute+exact')
    end
end

function on_seek()
    if player_state ~= 'replay' then
        update_sub_id()
    end
end

function on_playback_restart()
    if player_state == 'replay' then
        mp.set_property('pause', 'no')
        player_state = nil
    end
end

function on_up_arrow_key()
    if subtitle_mode == 'Listening Practice' then
        replay_sub_without_subtitles()
    end
end

function on_down_arrow_key()
    if player_state == 'pause' then
        if mp.get_property('sub-visibility') == 'no' then
            mp.set_property('sub-visibility', 'yes')
        else
            mp.set_property('sub-visibility', 'no')
            mp.set_property('pause', 'no')
            player_state = nil

            if sub_id < #subs then
                sub_id = sub_id + 1
            else
                sub_id = nil
            end
        end
    end
end

function on_space_key()
    if player_state == 'pause' then
        on_down_arrow_key()
    else
        mp.set_property_bool('pause', not mp.get_property_bool('pause'))
    end
end

function subtitle_mode_timer()
    local pos = mp.get_property_number('time-pos')

    if pos ~= nil then
        if sub_id ~= nil and mp.get_property('pause') ~= 'yes' then
            if subtitle_mode == 'Listening Practice' and pos >= subs_end[sub_id] then
                mp.set_property('pause', 'yes')
                player_state = 'pause'
            elseif subtitle_mode == 'Reading Practice' then
                if (pos + 0.25) >= subs_start[sub_id] then
                    mp.set_property('sub-visibility', 'yes')
                end

                if pos >= subs_start[sub_id] then
                    mp.set_property('pause', 'yes')
                    player_state = 'pause'
                end
            end
        end
    end
end

function init_subtitle_mode()
    default_sub_visibility = mp.get_property('sub-visibility')

    mp.set_property('sub-visibility', 'no')

    timer = mp.add_periodic_timer(0.05, subtitle_mode_timer)

    update_sub_id()

    mp.add_key_binding('up', 'up-arrow-key', on_up_arrow_key)
    mp.add_key_binding('down', 'down-arrow-key', on_down_arrow_key)
    mp.add_key_binding('space', 'space-arrow-key', on_space_key)

    mp.register_event('seek', on_seek)
    mp.register_event('playback-restart', on_playback_restart)
end

function release_subtitle_mode()
    mp.set_property('sub-visibility', default_sub_visibility)

    timer:kill()

    mp.remove_key_binding('up-arrow-key')
    mp.remove_key_binding('down-arrow-key')
    mp.remove_key_binding('space-arrow-key')

    mp.unregister_event(on_seek)
    mp.unregister_event(on_playback_restart)
end

function toggle_subtitle_mode()
    if subtitle_mode == nil then
        subtitle_mode = 'Listening Practice'
        mp.set_property('sub-delay', sub_pad_end + 0.25)
    elseif subtitle_mode == 'Listening Practice' then
        subtitle_mode = 'Reading Practice'
        mp.set_property('sub-delay', -sub_pad_start - 0.25)
    else
        mp.set_property('sub-delay', 0)
        subtitle_mode = nil
    end

    if subtitle_mode ~= nil then
        mp.osd_message('Subtitle Mode: ' .. subtitle_mode)
        init_subtitle_mode()
    else
        mp.osd_message('Subtitle Mode: ' .. 'Off')
        release_subtitle_mode()
    end
end

function init()
    local ret = read_subtitles()

    if ret == false or #subs == 0 then
        return
    end

    adjust_subtitles()

    mp.add_key_binding('i', 'toggle-interactive-mode', toggle_subtitle_mode)
end

mp.register_event('file-loaded', init)
