--- Describes the extension.
function descriptor()
  return {
    title = "MergeSub",
    version = "0.1",
    author = "Christopher Gundler",
    url = '',
    shortdesc = "Mix subtitles";
    description = "An extension for the VLC Media Player, which allows to mix multiple subtitles and merge them into one file",
    capabilities = { "menu" }
  }
end

--- Activates the extension.
function activate()
  MergeDialog:instance()
end

--- Closes the extension.
function close()
  -- Remove the merged cache file for the subtitles if it exists
  local path = get_tmp_file()
  local f = io.open(path, "r")
  if f ~= nil then
    io.close(f)
    os.remove(path)
  end

  vlc.deactivate()
end

--- Deactivates the extension.
function deactivate() end

function meta_changed() end

--- Returns the elements which are to be shown in the menu.
-- @return A map of subtitle descriptions ordered by their id.
function menu()
  return {}
end

--- Handles a click on the menu.
-- @param sub_id The id of the menu entry
function trigger_menu(id) end

--- Opens user interface, let the user select their subtitles and merge them
function merge_subtitles()
  local dialog = MergeDialog:instance()
  local subs = dialog:selected_subtitles()
  local file = get_current_file()

  if get_table_len(subs) < 2 then
    vlc.msg.err("Please select at least two subtitles!")
  elseif dialog:is_waiting() then
    vlc.msg.err("Please wait!")
  elseif not file then
    vlc.msg.err("Please select a valid mkv file!")
  else
    -- Disable further interactions
    dialog:wait()

    -- Extract the subtitles
    local files = {}
    local command = string.format("mkvextract tracks %q", file)
    for k, v in pairs(subs) do
      local tmp_file = os.tmpname()
      files[#files+1] = tmp_file
      command = string.format("%s %u:%s", command, k, tmp_file)
    end
    assert(os.execute(command) ~= 0, "Extraction failed")

    -- Load and merge the subtitles in RAM
    local sub = nil
    for _, file in ipairs(files) do
      if not sub then
        sub = SubStationAlpha:load(file) -- Load ...
      else
        sub:merge_with(file) -- ... or merge.
      end
      os.remove(file)
    end

    sub:set_title("Merged")

    -- Save file and set subtitle
    local tmp_file = get_tmp_file()
    assert(sub:write(tmp_file))
    vlc.input.add_subtitle(tmp_file)

    dialog:close()
    vlc.deactivate()
  end
end

--- Returns the path to a temporal file.
-- @return The path to a temporal file.
function get_tmp_file()
  if not tmp_file then
    tmp_file = os.tmpname()
  end

  return tmp_file
end

--- Returns the path of the current MKV file.
-- @return The path to the current video.
function get_current_file()
  local item = vlc.input.item()
  if item then
    local path = string.match(vlc.strings.decode_uri(item:uri()), "^file://(.+%.mkv)$")
    if path then
      return path
    end
  end
end

--- Returns the length of an arbitrary table.
-- @return A number.
function get_table_len(table)
  local count = 0
  for k,v in pairs(table) do
     count = count + 1
  end
  return count
end

--- Returns a list of available SubStation Alpha subtitles.
-- @return A table of subtitles.
function get_subtitles()
  sub = {}
  for k, v in pairs(vlc.input.item():info()) do
    if(v.Type == "Subtitle" and v.Codec == "SubStation Alpha subtitles (ssa )") then
      sub[tonumber(string.match(k, "(%d+)"))] = v.Description
    end
  end
  return sub
end

-- [ Class: MergeDialog ] --

--- A user dialog which allows the selection of multiple subtitles.
MergeDialog = {}
MergeDialog.__index = MergeDialog

--- Returns an instance of the dialog by creating it or returning a handle.
-- @return A MergeDialog instance.
function MergeDialog:instance()
  if not dialog_singletone then
    local d = vlc.dialog( "Mix subtitles" )
    d:add_label("Please select subtitles:")

    local subtitle_list = d:add_list()
    for k, v in pairs(get_subtitles()) do
      subtitle_list:add_value(v, k)
    end

    local subtitle_button = d:add_button("Merge", merge_subtitles)

    local this = {
      dialog = d,
      subtitles = subtitle_list,
      button = subtitle_button
    }
    setmetatable(this, MergeDialog)

    this.dialog:show()

    -- Set the global var
    dialog_singletone = this
  end

  return dialog_singletone
end

--- Closes the dialog instance.
function MergeDialog:close()
  self.dialog:delete()
  dialog_singletone = nil
end

--- Sets the dialog in a state where it does not response to further input.
function MergeDialog:wait()
  self.button:set_text("Please wait...")
end

--- Checks if the dialog does not response to input currently.
-- @return True, if the dialog is inactive.
function MergeDialog:is_waiting()
  return self.button:get_text() == "Please wait..."
end

--- Returns the selected subtitles.
-- @return A table of subtitles.
function MergeDialog:selected_subtitles()
  return self.subtitles:get_selection()
end

-- [ Class: SubStationAlpha ] --

--- A parser for the SubStation Alpha format.
SubStationAlpha = {}
SubStationAlpha.__index = SubStationAlpha

--- Parses the format out of a file.
-- @return The parsed subtitle format in memory.
function SubStationAlpha:load(path)
  local result = {}
  local current_key = nil

  for line in io.lines(path) do
    -- Extract key and value
    local key, value = string.match(line, "%s*(%g+):%s*(.+)")

    -- If a value is parsed which has to be added under a existing key add it
    if value and current_key then
      -- If the key does not exists create it ...
      if not result[current_key][key] then
        result[current_key][key] = value
      -- ... or add value to a already existing collection
      else
        if type(result[current_key][key]) ~= "table" then
          local current_val = result[current_key][key]
          result[current_key][key] = {}
          result[current_key][key][1] = current_val
        end
        result[current_key][key][#result[current_key][key] + 1] = value
      end
    -- Add a value globally if there is no existing category
    elseif value and not current_key then
      result[key] = value
    -- Create a new category
    else
      local category = string.match(line, "%s*%[(.+)%]")
      if category then
        result[category] = {}
        current_key = category
      end
    end
  end

  local this = {
    data = result
  }
  setmetatable(this, SubStationAlpha)
  return this
end

--- Merges two SubStation alpha subtitles.
function SubStationAlpha:merge_with(path)
  local tmp = SubStationAlpha:load(path)
  for _, dialogue in ipairs(tmp.data["Events"]["Dialogue"]) do
    self.data["Events"]["Dialogue"][#self.data["Events"]["Dialogue"] + 1] = dialogue
  end
end

--- Sets the title of the subtitle format.
function SubStationAlpha:set_title(title)
  self.data["Script Info"]["Title"] = title
end

--- Writes the subtitle into a file.
function SubStationAlpha:write(path)
  local file = io.open(path, "w+")
  if file == nil then return false end

  for key, value in pairs(self.data) do
    -- Find categories
    if type(value) == "table" then
      file:write("[", key, "]\n")
      -- Print items of category
      for sub_key, sub_value in pairs(value) do
        -- Check for dialogue
        if type(sub_value) == "table" then
          for _, element in ipairs(sub_value) do
            file:write(sub_key, ": ", element, "\n")
          end
        else
          file:write(sub_key, ": ", sub_value, "\n")
        end
      end
    else
      file:write(key, ": ", value, "\n")
    end
  end

  file:close()
  return true
end