--[[
VLSub Extension for VLC media player 1.1 and 2.0
Copyright 2013 Guillaume Le Maout

Authors:  Guillaume Le Maout
Contact: 
http://addons.videolan.org/messages/?action=newmessage&username=exebetche
Bug report: http://addons.videolan.org/content/show.php/?content=148752

This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
--]]


            --[[ Global var ]]-- 

-- You can set here your default language by replacing nil with
-- your language code (see below).Example: 
-- language = "fre", 
-- language = "ger", 
-- language = "eng",
-- ...

local options = {
  language = nil,
  downloadBehaviour = 'save',
  langExt = false,
  removeTag = false,
  showMediaInformation = true,
  progressBarSize = 80,
  intLang = 'eng',
  translations_avail = {
    eng = 'English',
    cze = 'Czech', 
    dan = 'Danish', 
    dut = 'Nederlands',
    fre = 'Français',
    ell = 'Greek',
    baq = 'Basque',
    pob = 'Brazilian Portuguese',
    por = 'Portuguese (Portugal)',
    rum = 'Romanian',
    slo = 'Slovak',
    spa = 'Spanish',
    swe = 'Swedish',
    ukr = 'Ukrainian',
    hun = 'Hungarian'
  },
  translation = {
    int_all = 'All',
    int_descr = 'Download subtitles from OpenSubtitles.org',
    int_research = 'Research',
    int_config = 'Config',
    int_configuration = 'Configuration',
    int_help = 'Help',
    int_search_hash = 'Search by hash',
    int_search_name = 'Search by name',
    int_title = 'Title',
    int_season = 'Season (series)',
    int_episode = 'Episode (series)',
    int_show_help = 'Show help',
    int_show_conf = 'Show config',
    int_dowload_sel = 'Download selection',
    int_close = 'Close',
    int_ok = 'Ok',
    int_save = 'Save',
    int_cancel = 'Cancel',
    int_bool_true = 'Yes',
    int_bool_false = 'No',
    int_search_transl = 'Search translations',
    int_searching_transl = 'Searching translations ...',
    int_int_lang = 'Interface language',
    int_default_lang = 'Subtitles language',
    int_dowload_behav = 'What to do with subtitles',
    int_dowload_save = 'Load and save',
    int_dowload_load = 'Load only',
    int_dowload_manual =  'Manual download',
    int_display_code = 'Display language code in file name',
    int_remove_tag = 'Remove tags',
    int_vlsub_work_dir = 'VLSub working directory',
    int_os_username = 'Username',
    int_os_password = 'Password',
    int_help_mess =[[
      Download subtitles from 
      <a href='http://www.opensubtitles.org/'>
      opensubtitles.org
      </a> and display them while watching a video.<br>
      <br>
      <b><u>Usage:</u></b><br>
      <br>
      Start your video. If you use Vlsub witout playing a video 
      you will get a link to download the subtitles in your browser 
      but the subtitles won't be saved and loaded automatically.<br>
      <br>
      Choose the language for your subtitles and click on the 
      button corresponding to one of the two research methods 
      provided by VLSub:<br>
      <br>
      <b>Method 1: Search by hash</b><br>
      It is recommended to try this method first, because it 
      performs a research based on the video file print, so you 
      can find subtitles synchronized with your video.<br>
      <br>
      <b>Method 2: Search by name</b><br>
      If you have no luck with the first method, just check the 
      title is correct before clicking. If you search subtitles 
      for a series, you can also provide a season and episode 
      number.<br>
      <br>
      <b>Downloading Subtitles</b><br>
      Select one subtitle in the list and click on 'Download'.<br>
      It will be put in the same directory that your video, with 
      the same name (different extension)
      so VLC will load them automatically the next time you'll 
      start the video.<br>
      <br>
      <b>/!\\ Beware :</b> Existing subtitles are overwritten 
      without asking confirmation, so put them elsewhere if 
      they're important.<br>
      <br>
      Find more VLC extensions at 
      <a href='http://addons.videolan.org'>addons.videolan.org</a>.
      ]],
    int_no_support_mess = [[
      <strong>VLSub is not working with Vlc 2.1.x on 
      any platform</strong>
      because the lua "net" module needed to interact 
      with opensubtitles has been 
      removed in this release for the extensions.
      <br>
      <strong>Works with Vlc 2.2 on mac and linux.</strong>
      <br>
      <strong>On windows you have to install an older version 
      of Vlc (2.0.8 for example)</strong>
      to use Vlsub:
      <br>
      <a target="_blank" rel="nofollow" 
      href="http://download.videolan.org/pub/videolan/vlc/2.0.8/">
      http://download.videolan.org/pub/videolan/vlc/2.0.8/</a><br>
    ]],
  
    action_login = 'Logging in',
    action_logout = 'Logging out',
    action_noop = 'Checking session',
    action_search = 'Searching subtitles',
    action_hash = 'Calculating movie hash',
    
    mess_success = 'Success',
    mess_error = 'Error',
    mess_no_response = 'Server not responding',
    mess_unauthorized = 'Request unauthorized',
    mess_expired = 'Session expired, retrying',
    mess_overloaded = 'Server overloaded, please retry later',
    mess_no_input = 'Please use this method during playing',
    mess_not_local = 'This method works with local file only (for now)',
    mess_not_found = 'File not found',
    mess_not_found2 = 'File not found (illegal character?)',
    mess_no_selection = 'No subtitles selected',
    mess_save_fail = 'Unable to save subtitles',
    mess_click_link = 'Click here to open the file',
    mess_complete = 'Research complete',
    mess_no_res = 'No result',
    mess_res = 'result(s)',
    mess_loaded = 'Subtitles loaded',
    mess_not_load = 'Unable to load subtitles',
    mess_downloading = 'Downloading subtitle',
    mess_dowload_link = 'Download link',
    mess_err_conf_access ='Can\'t find a suitable path to save'..
      'config, please set it manually',
    mess_err_wrong_path ='the path contains illegal character, '..
      'please correct it'
  }
}

local languages = {
  {'alb', 'Albanian'},
  {'ara', 'Arabic'},
  {'arm', 'Armenian'},
  {'baq', 'Basque'},
  {'ben', 'Bengali'},
  {'bos', 'Bosnian'},
  {'bre', 'Breton'},
  {'bul', 'Bulgarian'},
  {'bur', 'Burmese'},
  {'cat', 'Catalan'},
  {'chi', 'Chinese'},
  {'hrv', 'Croatian'},
  {'cze', 'Czech'},
  {'dan', 'Danish'},
  {'dut', 'Dutch'},
  {'eng', 'English'},
  {'epo', 'Esperanto'},
  {'est', 'Estonian'},
  {'fin', 'Finnish'},
  {'fre', 'French'},
  {'glg', 'Galician'},
  {'geo', 'Georgian'},
  {'ger', 'German'},
  {'ell', 'Greek'},
  {'heb', 'Hebrew'},
  {'hin', 'Hindi'},
  {'hun', 'Hungarian'},
  {'ice', 'Icelandic'},
  {'ind', 'Indonesian'},
  {'ita', 'Italian'},
  {'jpn', 'Japanese'},
  {'kaz', 'Kazakh'},
  {'khm', 'Khmer'},
  {'kor', 'Korean'},
  {'lav', 'Latvian'},
  {'lit', 'Lithuanian'},
  {'ltz', 'Luxembourgish'},
  {'mac', 'Macedonian'},
  {'may', 'Malay'},
  {'mal', 'Malayalam'},
  {'mon', 'Mongolian'},
  {'nor', 'Norwegian'},
  {'oci', 'Occitan'},
  {'per', 'Persian'},
  {'pol', 'Polish'},
  {'por', 'Portuguese'},
  {'pob', 'Brazilian Portuguese'},
  {'rum', 'Romanian'},
  {'rus', 'Russian'},
  {'scc', 'Serbian'},
  {'sin', 'Sinhalese'},
  {'slo', 'Slovak'},
  {'slv', 'Slovenian'},
  {'spa', 'Spanish'},
  {'swa', 'Swahili'},
  {'swe', 'Swedish'},
  {'syr', 'Syriac'},
  {'tgl', 'Tagalog'},
  {'tel', 'Telugu'},
  {'tha', 'Thai'},
  {'tur', 'Turkish'},
  {'ukr', 'Ukrainian'},
  {'urd', 'Urdu'},
  {'vie', 'Vietnamese'}
}

-- Languages code conversion table: iso-639-1 to iso-639-3
-- See https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes
local lang_os_to_iso = {
  sq = "alb",
  ar = "ara",
  hy = "arm",
  eu = "baq",
  bn = "ben",
  bs = "bos",
  br = "bre",
  bg = "bul",
  my = "bur",
  ca = "cat",
  zh = "chi",
  hr = "hrv",
  cs = "cze",
  da = "dan",
  nl = "dut",
  en = "eng",
  eo = "epo",
  et = "est",
  fi = "fin",
  fr = "fre",
  gl = "glg",
  ka = "geo",
  de = "ger",
  el = "ell",
  he = "heb",
  hi = "hin",
  hu = "hun",
  is = "ice",
  id = "ind",
  it = "ita",
  ja = "jpn",
  kk = "kaz",
  km = "khm",
  ko = "kor",
  lv = "lav",
  lt = "lit",
  lb = "ltz",
  mk = "mac",
  ms = "may",
  ml = "mal",
  mn = "mon",
  no = "nor",
  oc = "oci",
  fa = "per",
  pl = "pol",
  pt = "por",
  po = "pob",
  ro = "rum",
  ru = "rus",
  sr = "scc",
  si = "sin",
  sk = "slo",
  sl = "slv",
  es = "spa",
  sw = "swa",
  sv = "swe",
  tl = "tgl",
  te = "tel",
  th = "tha",
  tr = "tur",
  uk = "ukr",
  ur = "urd",
  vi = "vie"
}

local dlg = nil
local input_table = {} -- General widget id reference
local select_conf = {} -- Drop down widget / option table association 

            --[[ VLC extension stuff ]]--

function descriptor()
  return { 
    title = "VLsub 0.9.13",
    version = "0.9.13",
    author = "exebetche",
    url = 'http://www.opensubtitles.org/',
    shortdesc = "Download Subtitles";
    description = options.translation.int_descr,
    capabilities = {"menu", "input-listener" }
  }
end

function activate()
  vlc.msg.dbg("[VLsub] Welcome")
  
  if not check_config() then 
  	vlc.msg.err("[VLsub] Unsupported VLC version")
  	return false 
  end
  	
  if vlc.input.item() then
    openSub.getFileInfo()
    openSub.getMovieInfo()
  end
  
  show_main()
end

function close()
  vlc.deactivate()
end

function deactivate()
  vlc.msg.dbg("[VLsub] Bye bye!")
  if dlg then
    dlg:hide() 
  end
  
  if openSub.session.token and openSub.session.token ~= "" then
    openSub.request("LogOut")
  end
end

function menu()
  return { 	  
    lang.int_research, 
    lang.int_config, 
    lang.int_help
  }
end

function meta_changed()
  return false
end

function input_changed()
  collectgarbage()
  set_interface_main()
  collectgarbage()
end

            --[[ Interface data ]]--

function interface_main()
  dlg:add_label(lang["int_default_lang"]..':', 1, 1, 1, 1)
  input_table['language'] =  dlg:add_dropdown(2, 1, 2, 1)
  dlg:add_button(lang["int_search_hash"], 
    searchHash, 4, 1, 1, 1)
  
  dlg:add_label(lang["int_title"]..':', 1, 2, 1, 1)
  input_table['title'] = dlg:add_text_input(
    openSub.movie.title or "", 2, 2, 2, 1)
  dlg:add_button(lang["int_search_name"], 
    searchIMBD, 4, 2, 1, 1)
  dlg:add_label(lang["int_season"]..':', 1, 3, 1, 1)
  input_table['seasonNumber'] = dlg:add_text_input(
    openSub.movie.seasonNumber or "", 2, 3, 2, 1)
  dlg:add_label(lang["int_episode"]..':', 1, 4, 1, 1)
  input_table['episodeNumber'] = dlg:add_text_input(
    openSub.movie.episodeNumber or "", 2, 4, 2, 1)
  input_table['mainlist'] = dlg:add_list(1, 5, 4, 1)
  input_table['message'] = nil
  input_table['message'] = dlg:add_label(' ', 1, 6, 4, 1)
  dlg:add_button(
    lang["int_show_help"], show_help, 1, 7, 1, 1)
  dlg:add_button(
    '   '..lang["int_show_conf"]..'   ', show_conf, 2, 7, 1, 1)
  dlg:add_button(
    lang["int_dowload_sel"], download_subtitles, 3, 7, 1, 1)
  dlg:add_button(
    lang["int_close"], deactivate, 4, 7, 1, 1) 
  
  assoc_select_conf(
    'language',
    'language',
    openSub.conf.languages, 
    2, 
    lang["int_all"])
    
  display_subtitles()
end

function set_interface_main()
  -- Update movie title and co. if video input change
  if not type(input_table['title']) == 'userdata' then return false end
  
  openSub.getFileInfo()
  openSub.getMovieInfo()
  
  input_table['title']:set_text(
    openSub.movie.title or "")
  input_table['episodeNumber']:set_text(
    openSub.movie.episodeNumber or "")
  input_table['seasonNumber']:set_text(
    openSub.movie.seasonNumber or "")
end

function interface_config()
  input_table['intLangLab'] = dlg:add_label(
    lang["int_int_lang"]..':', 1, 1, 1, 1)
  input_table['intLangBut'] = dlg:add_button(
    lang["int_search_transl"],
    get_available_translations, 2, 1, 1, 1)
  input_table['intLang'] = dlg:add_dropdown(3, 1, 1, 1)	
  dlg:add_label(
    lang["int_default_lang"]..':', 1, 2, 2, 1)
  input_table['default_language'] = dlg:add_dropdown(3, 2, 1, 1)	
  dlg:add_label(
    lang["int_dowload_behav"]..':', 1, 3, 2, 1)
  input_table['downloadBehaviour'] = dlg:add_dropdown(3, 3, 1, 1)
  dlg:add_label(
    lang["int_display_code"]..':', 1, 4, 0, 1)
  input_table['langExt'] = dlg:add_dropdown(3, 4, 1, 1)
  dlg:add_label(
    lang["int_remove_tag"]..':', 1, 5, 0, 1)
  input_table['removeTag'] = dlg:add_dropdown(3, 5, 1, 1)
    
  if openSub.conf.dirPath then
    if openSub.conf.os == "win" then
      dlg:add_label(
        "<a href='file:///"..openSub.conf.dirPath.."'>"..
        lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
    else
      dlg:add_label(
        "<a href='"..openSub.conf.dirPath.."'>"..
        lang["int_vlsub_work_dir"].."</a>", 1, 6, 2, 1)
    end
  else
    dlg	:add_label(
      lang["int_vlsub_work_dir"], 1, 6, 2, 1)
  end
  
  input_table['dir_path'] = dlg:add_text_input(
    openSub.conf.dirPath, 2, 6, 2, 1)
  
  dlg:add_label(
    lang["int_os_username"]..':', 1, 7, 0, 1)
  input_table['os_username'] = dlg:add_text_input(
    type(openSub.option.os_username) == "string" 
    and openSub.option.os_username or "", 2, 7, 2, 1)
  dlg:add_label(
    lang["int_os_password"]..':', 1, 8, 0, 1)
  input_table['os_password'] = dlg:add_text_input(
    type(openSub.option.os_password) == "string" 
    and openSub.option.os_password or "", 2, 8, 2, 1)
        
  input_table['message'] = nil
  input_table['message'] = dlg:add_label(' ', 1, 9, 3, 1)
  
  dlg:add_button(
    lang["int_cancel"],
    show_main, 2, 10, 1, 1)
  dlg:add_button(
    lang["int_save"],
    apply_config, 3, 10, 1, 1)
  
  input_table['langExt']:add_value(
    lang["int_bool_"..tostring(openSub.option.langExt)], 1)
  input_table['langExt']:add_value(
    lang["int_bool_"..tostring(not openSub.option.langExt)], 2)
  input_table['removeTag']:add_value(
    lang["int_bool_"..tostring(openSub.option.removeTag)], 1)
  input_table['removeTag']:add_value(
    lang["int_bool_"..tostring(not openSub.option.removeTag)], 2)
  
  assoc_select_conf(
    'intLang',
    'intLang',
    openSub.conf.translations_avail,
    2)
  assoc_select_conf(
    'default_language',
    'language',
    openSub.conf.languages,
    2,
    lang["int_all"])
  assoc_select_conf(
    'downloadBehaviour',
    'downloadBehaviour',
    openSub.conf.downloadBehaviours,
    1)
end

function interface_help()
  local help_html = lang["int_help_mess"]
    
  input_table['help'] = dlg:add_html(
    help_html, 1, 1, 4, 1)
  dlg:add_label(
    string.rep ("&nbsp;", 100), 1, 2, 3, 1)
  dlg:add_button(
    lang["int_ok"], show_main, 4, 2, 1, 1)
end

function interface_no_support()
  local no_support_html = lang["int_no_support_mess"]
    
  input_table['no_support'] = dlg:add_html(
    no_support_html, 1, 1, 4, 1)
  dlg:add_label(
    string.rep ("&nbsp;", 100), 1, 2, 3, 1)
end

function trigger_menu(dlg_id)
  if dlg_id == 1 then
    close_dlg()
    dlg = vlc.dialog(
      openSub.conf.useragent)
    interface_main()
  elseif dlg_id == 2 then
    close_dlg()
    dlg = vlc.dialog(
      openSub.conf.useragent..': '..lang["int_configuration"])
    interface_config()
  elseif dlg_id == 3 then
    close_dlg()
    dlg = vlc.dialog(
      openSub.conf.useragent..': '..lang["int_help"])
    interface_help()
  end
  collectgarbage() --~ !important	
end 

function show_main()
  trigger_menu(1)
end

function show_conf()
  trigger_menu(2)
end

function show_help()
  trigger_menu(3)
end

function close_dlg()
  vlc.msg.dbg("[VLSub] Closing dialog")

  if dlg ~= nil then 
    --~ dlg:delete() -- Throw an error
    dlg:hide() 
  end
  
  dlg = nil
  input_table = nil
  input_table = {}
  collectgarbage() --~ !important	
end

            --[[ Drop down / config association]]--

function assoc_select_conf(select_id, option, conf, ind, default)
-- Helper for i/o interaction between drop down and option list
  select_conf[select_id] = {
    cf = conf,
    opt  = option,
    dflt = default,
    ind = ind
  }
  set_default_option(select_id)
  display_select(select_id)
end

function set_default_option(select_id)
-- Put the selected option of a list in first place of the associated table 
  local opt = select_conf[select_id].opt
  local cfg = select_conf[select_id].cf
  local ind = select_conf[select_id].ind
  if openSub.option[opt] then
    table.sort(cfg, function(a, b) 
      if a[1] == openSub.option[opt] then
        return true
      elseif b[1] == openSub.option[opt] then
        return false
      else
        return a[ind] < b[ind] 
      end
    end)
  end
end

function display_select(select_id)
-- Display the drop down values with an optional default value at the top
  local conf = select_conf[select_id].cf
  local opt = select_conf[select_id].opt
  local option = openSub.option[opt]
  local default = select_conf[select_id].dflt
  local default_isset = false
    
  if not default then 
    default_isset = true
  end
  
  for k, l in ipairs(conf) do
    if default_isset then
      input_table[select_id]:add_value(l[2], k)
    else
      if option then
        input_table[select_id]:add_value(l[2], k)
        input_table[select_id]:add_value(default, 0)
      else
        input_table[select_id]:add_value(default, 0)
        input_table[select_id]:add_value(l[2], k)
      end
      default_isset = true
    end
  end
end

            --[[ Config & interface localization]]--

function check_config()
  -- Make a copy of english translation to use it as default 
  -- in case some element aren't translated in other translations
  eng_translation = {}
  for k, v in pairs(openSub.option.translation) do
    eng_translation[k] = v
  end
  
  -- Get available translation full name from code
  trsl_names = {}
  for i, lg in ipairs(languages) do
    trsl_names[lg[1]] = lg[2]
  end
  
  if is_window_path(vlc.config.datadir()) then
    openSub.conf.os = "win"
    slash = "\\"
  else
    openSub.conf.os = "lin"
    slash = "/"
  end
  
  local path_generic = {"lua", "extensions", "userdata", "vlsub"}
  local dirPath = slash..table.concat(path_generic, slash)
  local filePath	= slash.."vlsub_conf.xml"
  local config_saved = false
  sub_dir = slash.."vlsub_subtitles"
  
  -- Check if config file path is stored in vlc config
  local other_dirs = {}
  
  for path in 
  vlc.config.get("sub-autodetect-path"):gmatch("[^,]+") do
    if path:match(".*"..sub_dir.."$") then
      openSub.conf.dirPath = path:gsub(
        "%s*(.*)"..sub_dir.."%s*$", "%1")
      config_saved = true
    end
    table.insert(other_dirs, path)
  end
  
  -- if not stored in vlc config
  -- try to find a suitable config file path 
  
  if openSub.conf.dirPath then
    if not is_dir(openSub.conf.dirPath) and
    (openSub.conf.os == "lin"  or
    is_win_safe(openSub.conf.dirPath)) then
      mkdir_p(openSub.conf.dirPath)
    end
  else
    local userdatadir = vlc.config.userdatadir()
    local datadir = vlc.config.datadir()
    
    -- check if the config already exist
    if file_exist(userdatadir..dirPath..filePath) then
      -- in vlc.config.userdatadir()
      openSub.conf.dirPath = userdatadir..dirPath
      config_saved = true
    elseif file_exist(datadir..dirPath..filePath) then
      -- in vlc.config.datadir()
      openSub.conf.dirPath = datadir..dirPath
      config_saved = true
    else
      -- if not found determine an accessible path
      local extension_path = slash..path_generic[1]
        ..slash..path_generic[2]
      
      -- use the same folder as the extension if accessible
      if is_dir(userdatadir..extension_path) 
      and file_touch(userdatadir..dirPath..filePath) then
          openSub.conf.dirPath = userdatadir..dirPath
      elseif file_touch(datadir..dirPath..filePath) then
        openSub.conf.dirPath = datadir..dirPath
      end
      
      -- try to create working dir in user folder
      if not openSub.conf.dirPath
      and is_dir(userdatadir) then
        if not is_dir(userdatadir..dirPath) then
          mkdir_p(userdatadir..dirPath)
        end
        if is_dir(userdatadir..dirPath) and
        file_touch(userdatadir..dirPath..filePath) then
          openSub.conf.dirPath = userdatadir..dirPath
        end
      end
      
      -- try to create working dir in vlc folder	
      if not openSub.conf.dirPath and
      is_dir(datadir) then
        if not is_dir(datadir..dirPath) then
          mkdir_p(datadir..dirPath)
        end
        if file_touch(datadir..dirPath..filePath) then
          openSub.conf.dirPath = datadir..dirPath
        end
      end
    end
  end
  
  if openSub.conf.dirPath then
    vlc.msg.dbg("[VLSub] Working directory: " ..
      (openSub.conf.dirPath or "not found"))
    
    openSub.conf.filePath = openSub.conf.dirPath..filePath 
    openSub.conf.localePath = openSub.conf.dirPath..slash.."locale"
    
    if config_saved 
    and file_exist(openSub.conf.filePath) then
      vlc.msg.dbg(
        "[VLSub] Loading config file: "..openSub.conf.filePath)
      load_config()
    else
      vlc.msg.dbg("[VLSub] No config file")
      getenv_lang()
      config_saved = save_config()
      if not config_saved then
        vlc.msg.dbg("[VLSub] Unable to save config")
      end
    end
    
    -- Check presence of a translation file 
    -- in "%vlsub_directory%/locale"
    -- Add translation files to available translation list
    local file_list = list_dir(openSub.conf.localePath)
    local translations_avail = openSub.conf.translations_avail
    
    if file_list then
      for i, file_name in ipairs(file_list) do
        local lg =  string.gsub(
          file_name,
          "^(%w%w%w).xml$",
          "%1")
        if lg 
        and not openSub.option.translations_avail[lg] then
          table.insert(translations_avail, {
            lg,
            trsl_names[lg]
          })
        end
      end
    end
    
    -- Load selected translation from file
    if openSub.option.intLang ~= "eng" 
    and not openSub.conf.translated
    then
      local transl_file_path = openSub.conf.localePath..
        slash..openSub.option.intLang..".xml"
      if file_exist(transl_file_path) then
        vlc.msg.dbg(
          "[VLSub] Loading translation from file: "..
          transl_file_path)
        load_transl(transl_file_path)
      end
    end
  else
    vlc.msg.dbg("[VLSub] Unable to find a suitable path"..
      "to save config, please set it manually")
  end
  
  lang = nil
  lang = options.translation -- just a short cut
  
  if not vlc.net or not vlc.net.poll then
    dlg = vlc.dialog(
      openSub.conf.useragent..': '..lang["mess_error"])
    interface_no_support()
    dlg:show()
    return false
  end
  
  SetDownloadBehaviours()
  if not openSub.conf.dirPath then
    setError(lang["mess_err_conf_access"])
  end
    
  -- Set table list of available translations from assoc. array 
  -- so it is sortable
  
  for k, l in pairs(openSub.option.translations_avail) do		
    if k == openSub.option.int_research then
      table.insert(openSub.conf.translations_avail, 1, {k, l})
    else
      table.insert(openSub.conf.translations_avail, {k, l})
    end
  end
  collectgarbage()
  return true
end

function load_config()
-- Overwrite default conf with loaded conf
  local tmpFile = io.open(openSub.conf.filePath, "rb")
  if not tmpFile then return false end
  local resp = tmpFile:read("*all")
  tmpFile:flush()
  tmpFile:close()
  local option = parse_xml(resp)
  
  for key, value in pairs(option) do
    if type(value) == "table" then
      if key == "translation" then
        openSub.conf.translated = true
        for k, v in pairs(value) do
          openSub.option.translation[k] = v
        end
      else
        openSub.option[key] = value
      end
    else
      if value == "true" then
        openSub.option[key] = true
      elseif value == "false" then
        openSub.option[key] = false
      else
        openSub.option[key] = value
      end
    end
  end
  collectgarbage()
end

function load_transl(path)
-- Overwrite default conf with loaded conf
  local tmpFile = assert(io.open(path, "rb"))
  local resp = tmpFile:read("*all")
  tmpFile:flush()
  tmpFile:close()
  openSub.option.translation = nil
  
  openSub.option.translation = parse_xml(resp)	
  collectgarbage()
end

function apply_translation()
-- Overwrite default conf with loaded conf
  for k, v in pairs(eng_translation) do
    if not openSub.option.translation[k] then
      openSub.option.translation[k] = eng_translation[k]
    end
  end
end

function getenv_lang()
-- Retrieve the user OS language 
  local os_lang = os.getenv("LANG")
  
  if os_lang then -- unix, mac
    os_lang = string.sub(os_lang, 0, 2)
    if type(lang_os_to_iso[os_lang]) then
      openSub.option.language = lang_os_to_iso[os_lang]
    end
  else -- Windows
    local lang_w = string.match(
      os.setlocale("", "collate"),
      "^[^_]+")
    for i, v in ipairs(openSub.conf.languages) do
     if v[2] == lang_w then
      openSub.option.language = v[1]
     end
    end 
  end
end

function apply_config()
-- Apply user config selection to local config
  local lg_sel = input_table['intLang']:get_value()
  local sel_val
  local opt
  local sel_cf
  
  if lg_sel and lg_sel ~= 1 
  and openSub.conf.translations_avail[lg_sel] then
    local lg = openSub.conf.translations_avail[lg_sel][1]
    set_translation(lg)
    SetDownloadBehaviours()
  end
  
  for select_id, v in pairs(select_conf) do
    if input_table[select_id] 
    and select_conf[select_id] then
      sel_val = input_table[select_id]:get_value()
      sel_cf = select_conf[select_id]
      opt = sel_cf.opt
      
      if sel_val == 0 then
        openSub.option[opt] = nil
      else
        openSub.option[opt] = sel_cf.cf[sel_val][1]
      end
      
      set_default_option(select_id)
    end
  end
  
  
  openSub.option.os_username = input_table['os_username']:get_text()
  openSub.option.os_password = input_table['os_password']:get_text()
  
  if input_table["langExt"]:get_value() == 2 then
    openSub.option.langExt = not openSub.option.langExt
  end
  
  if input_table["removeTag"]:get_value() == 2 then
    openSub.option.removeTag = not openSub.option.removeTag
  end
  
  -- Set a custom working directory
  local dir_path = input_table['dir_path']:get_text()
  local dir_path_err = false
  if trim(dir_path) == "" then dir_path = nil end
  
  if dir_path ~= openSub.conf.dirPath then
    if openSub.conf.os == "lin" 
    or is_win_safe(dir_path) 
    or not dir_path then
      local other_dirs = {}
    
      for path in 
      vlc.config.get(
        "sub-autodetect-path"):gmatch("[^,]+"
      ) do
        path = trim(path)
        if path ~= (openSub.conf.dirPath or "")..sub_dir then
          table.insert(other_dirs, path)
        end
      end
      openSub.conf.dirPath = dir_path
      if dir_path then
        table.insert(other_dirs, 
        string.gsub(dir_path, "^(.-)[\\/]?$", "%1")..sub_dir)
        
        if not is_dir(dir_path) then
          mkdir_p(dir_path)
        end
        
        openSub.conf.filePath = openSub.conf.dirPath..
          slash.."vlsub_conf.xml"
        openSub.conf.localePath = openSub.conf.dirPath..
          slash.."locale"
      else
        openSub.conf.filePath = nil
        openSub.conf.localePath = nil
      end
      vlc.config.set(
        "sub-autodetect-path",
        table.concat(other_dirs, ", "))
    else
      dir_path_err = true
      setError(lang["mess_err_wrong_path"]..
        "<br><b>"..
        string.gsub(
          dir_path,
          "[^%:%w%p%s§¤]+",
          "<span style='color:#B23'>%1</span>"
        )..
        "</b>")
    end
  end
  
  if openSub.conf.dirPath and
  not dir_path_err then
    local config_saved = save_config()
    trigger_menu(1)
    if not config_saved then
      setError(lang["mess_err_conf_access"])
    end
  else
    setError(lang["mess_err_conf_access"])
  end
end

function save_config()
-- Dump local config into config file 
  if openSub.conf.dirPath
  and openSub.conf.filePath then
    vlc.msg.dbg(
      "[VLSub] Saving config file:  "..
      openSub.conf.filePath)
    
    if file_touch(openSub.conf.filePath) then
      local tmpFile = assert(
        io.open(openSub.conf.filePath, "wb"))
      local resp = dump_xml(openSub.option)
      tmpFile:write(resp)
      tmpFile:flush()
      tmpFile:close()
      tmpFile = nil
    else
      return false
    end
    collectgarbage()
    return true
  else
    vlc.msg.dbg("[VLSub] Unable fount a suitable path "..
      "to save config, please set it manually")
    setError(lang["mess_err_conf_access"])
    return false
  end
end

function SetDownloadBehaviours()
  openSub.conf.downloadBehaviours = nil 
  openSub.conf.downloadBehaviours = { 
    {'save', lang["int_dowload_save"]},
    {'manual', lang["int_dowload_manual"]}
  }
end

function get_available_translations()
-- Get all available translation files from the internet
-- (drop previous direct download from github repo 
-- causing error  with github https CA certficate on OS X an XP)
-- https://github.com/exebetche/vlsub/tree/master/locale
  
  local translations_url = "http://addons.videolan.org/CONTENT/"..
    "content-files/148752-vlsub_translations.xml"
  
  if input_table['intLangBut']:get_text() == lang["int_search_transl"] 
  then
    openSub.actionLabel = lang["int_searching_transl"]
    
    local translations_content, lol = get(translations_url)
    local translations_avail = openSub.option.translations_avail
    all_trsl = parse_xml(translations_content)
    local lg, trsl
    
    for lg, trsl in pairs(all_trsl) do
      if lg ~= options.intLang[1] 
      and not translations_avail[lg] then
        translations_avail[lg] = trsl_names[lg] or ""
        table.insert(openSub.conf.translations_avail, {
          lg,
          trsl_names[lg]
        })
        input_table['intLang']:add_value(
          trsl_names[lg],
          #openSub.conf.translations_avail)
      end
    end
    
    setMessage(success_tag(lang["mess_complete"]))
    collectgarbage()
  end
end

function set_translation(lg)
  openSub.option.translation = nil
  openSub.option.translation = {}
  
  if lg == 'eng' then
    for k, v in pairs(eng_translation) do
      openSub.option.translation[k] = v
    end
  else
    -- If translation file exists in /locale directory load it
    if openSub.conf.localePath 
    and file_exist(openSub.conf.localePath..
      slash..lg..".xml") then
      local transl_file_path = openSub.conf.localePath..
      slash..lg..".xml"
      vlc.msg.dbg("[VLSub] Loading translation from file: "..
        transl_file_path)
      load_transl(transl_file_path)
      apply_translation()
    else
    -- Load translation file from internet
      if not all_trsl then
        get_available_translations()
      end

      if not all_trsl or not all_trsl[lg] then
        vlc.msg.dbg("[VLSub] Error, translation not found")
        return false
      end
      openSub.option.translation = all_trsl[lg]
      apply_translation()
      all_trsl = nil
    end
  end
  
  lang = nil
  lang = openSub.option.translation
  collectgarbage()
end 

            --[[ Core ]]--

openSub = {
  itemStore = nil,
  actionLabel = "",
  conf = {
    url = "http://api.opensubtitles.org/xml-rpc",
    path = nil,
    userAgentHTTP = "VLSub",
    useragent = "VLSub 0.9",
    translations_avail = {},
    downloadBehaviours = nil,
    languages = languages
  },
  option = options,
  session = {
    loginTime = 0,
    token = ""
  },
  file = {
    hasInput = false,
    uri = nil,
    ext = nil,
    name = nil,
    path = nil,
    protocol = nil,
    cleanName = nil,
    dir = nil,
    hash = nil,
    bytesize = nil,
    fps = nil,
    timems = nil,
    frames = nil
  },
  movie = {
    title = "",
    seasonNumber = "",
    episodeNumber = "",
    sublanguageid = ""
  },
  request = function(methodName)
    local params = openSub.methods[methodName].params()
    local reqTable = openSub.getMethodBase(methodName, params)
    local request = "<?xml version='1.0'?>"..dump_xml(reqTable)
    local host, path = parse_url(openSub.conf.url)		
    local header = {
      "POST "..path.." HTTP/1.1", 
      "Host: "..host, 
      "User-Agent: "..openSub.conf.userAgentHTTP, 
      "Content-Type: text/xml", 
      "Content-Length: "..string.len(request),
      "",
      ""
    }
    request = table.concat(header, "\r\n")..request
    
    local response
    local status, responseStr = http_req(host, 80, request)
    
    if status == 200 then 
      response = parse_xmlrpc(responseStr)
      if response then
        if response.status == "200 OK" then
          return openSub.methods[methodName]
            .callback(response)
        elseif response.status == "406 No session" then
          openSub.request("LogIn")
        elseif response then
          setError("code '"..
            response.status..
            "' ("..status..")")
          return false
        end
      else
        setError("Server not responding")
        return false
      end
    elseif status == 401 then
      setError("Request unauthorized")
      
      response = parse_xmlrpc(responseStr)
      if openSub.session.token ~= response.token then
        setMessage("Session expired, retrying")
        openSub.session.token = response.token
        openSub.request(methodName)
      end
      return false
    elseif status == 503 then 
      setError("Server overloaded, please retry later")
      return false
    end
    
  end,
  getMethodBase = function(methodName, param)
    if openSub.methods[methodName].methodName then
      methodName = openSub.methods[methodName].methodName
    end
    
    local request = {
     methodCall={
      methodName=methodName,
      params={ param=param }}}
    
    return request
  end,
  methods = {
    LogIn = {
      params = function()
        openSub.actionLabel = lang["action_login"]
        return {
          { value={ string=openSub.option.os_username } },
          { value={ string=openSub.option.os_password } },
          { value={ string=openSub.movie.sublanguageid } },
          { value={ string=openSub.conf.useragent } } 
        }
      end,
      callback = function(resp)
        openSub.session.token = resp.token
        openSub.session.loginTime = os.time()
        return true
      end
    },
    LogOut = {
      params = function()
        openSub.actionLabel = lang["action_logout"]
        return {
          { value={ string=openSub.session.token } } 
        }
      end,
      callback = function()
        return true
      end
    },
    NoOperation = {
      params = function()
        openSub.actionLabel = lang["action_noop"]
        return {
          { value={ string=openSub.session.token } } 
        }
      end,
      callback = function(resp)
        return true
      end
    },
    SearchSubtitlesByHash = {
      methodName = "SearchSubtitles",
      params = function()
        openSub.actionLabel = lang["action_search"]
        setMessage(openSub.actionLabel..": "..
          progressBarContent(0))
        
        return {
          { value={ string=openSub.session.token } },
          { value={
            array={
             data={
              value={
               struct={
                member={
                 { name="sublanguageid", value={ 
                  string=openSub.movie.sublanguageid } 
                  },
                 { name="moviehash", value={ 
                  string=openSub.file.hash } },
                 { name="moviebytesize", value={ 
                  double=openSub.file.bytesize } } 
                  }}}}}}}
        }
      end,
      callback = function(resp)
        openSub.itemStore = resp.data
      end
    },
    SearchSubtitles = {
      methodName = "SearchSubtitles",
      params = function()
        openSub.actionLabel = lang["action_search"]
        setMessage(openSub.actionLabel..": "..
          progressBarContent(0))
                
        local member = {
             { name="sublanguageid", value={ 
              string=openSub.movie.sublanguageid } },
             { name="query", value={ 
              string=openSub.movie.title } } }
             
        
        if openSub.movie.seasonNumber ~= nil then
          table.insert(member, { name="season", value={ 
            string=openSub.movie.seasonNumber } })
        end 
        
        if openSub.movie.episodeNumber ~= nil then
          table.insert(member, { name="episode", value={ 
            string=openSub.movie.episodeNumber } })
        end 
        
        return {
          { value={ string=openSub.session.token } },
          { value={
            array={
             data={
              value={
               struct={
                member=member
                  }}}}}}
        }
      end,
      callback = function(resp)
        openSub.itemStore = resp.data
      end
    }
  },
  getInputItem = function()
    return vlc.item or vlc.input.item()
  end,
  getFileInfo = function()
  -- Get video file path, name, extension from input uri
    local item = openSub.getInputItem()
    local file = openSub.file
    if not item then
      file.hasInput = false;
      file.cleanName = nil;
      file.protocol = nil;
      file.path = nil;
      file.ext = nil;
      file.uri = nil;
    else
      vlc.msg.dbg("[VLSub] Video URI: "..item:uri())
      local parsed_uri = vlc.net.url_parse(item:uri())
      file.uri = item:uri()
      file.protocol = parsed_uri["protocol"]
      file.path = parsed_uri["path"]
      
    -- Corrections
      
      -- For windows
      file.path = string.match(file.path, "^/(%a:/.+)$") or file.path
      
      -- For file in archive
      local archive_path, name_in_archive = string.match(
        file.path, '^([^!]+)!/([^!/]*)$')
      if archive_path and archive_path ~= "" then
        file.path = string.gsub(
          archive_path,
          '\063',
          '%%')
        file.path = vlc.strings.decode_uri(file.path)
        file.completeName = string.gsub(
          name_in_archive,
          '\063',
          '%%')
        file.completeName = vlc.strings.decode_uri(
          file.completeName)
        file.is_archive = true
      else -- "classic" input
        file.path = vlc.strings.decode_uri(file.path)
        file.dir, file.completeName = string.match(
          file.path,
          '^(.*/)([^/]*)$')
        
        local file_stat = vlc.net.stat(file.path)
        if file_stat 
        then
          file.stat = file_stat
        end
        
        file.is_archive = false
      end
      
      file.name, file.ext = string.match(
        file.completeName,
        '^([^/]-)%.?([^%.]*)$')
      
      if file.ext == "part" then
        file.name, file.ext = string.match(
          file.name,
          '^([^/]+)%.([^%.]+)$')
      end
      
      file.hasInput = true;
      file.cleanName = string.gsub(
        file.name,
        "[%._]", " ")
      vlc.msg.dbg("[VLSub] file info "..(dump_xml(file)))
    end
    collectgarbage()
  end,
  getMovieInfo = function()
  -- Clean video file name and check for season/episode pattern in title
    if not openSub.file.name then
      openSub.movie.title = ""
      openSub.movie.seasonNumber = ""
      openSub.movie.episodeNumber = ""
      return false 
    end
    
    local showName, seasonNumber, episodeNumber = string.match(
      openSub.file.cleanName,
      "(.+)[sS](%d%d)[eE](%d%d).*")

    if not showName then
      showName, seasonNumber, episodeNumber = string.match(
      openSub.file.cleanName,
      "(.+)(%d)[xX](%d%d).*")
    end
    
    if showName then
      openSub.movie.title = showName
      openSub.movie.seasonNumber = seasonNumber
      openSub.movie.episodeNumber = episodeNumber
    else
      openSub.movie.title = openSub.file.cleanName
      openSub.movie.seasonNumber = ""
      openSub.movie.episodeNumber = ""
    end
    collectgarbage()
  end,
  getMovieHash = function()
  -- Calculate movie hash
    openSub.actionLabel = lang["action_hash"]
    setMessage(openSub.actionLabel..": "..
      progressBarContent(0))
    
    local item = openSub.getInputItem()
    
    if not item then
      setError(lang["mess_no_input"])
      return false
    end
    
    openSub.getFileInfo()
      
    if not openSub.file.path then
      setError(lang["mess_not_found"])
      return false
    end
    
    local data_start = ""
    local data_end = ""
    local size
    local chunk_size = 65536
        
    -- Get data for hash calculation
    if openSub.file.is_archive then
      vlc.msg.dbg("[VLSub] Read hash data from stream")
    
      local file = vlc.stream(openSub.file.uri)
      local dataTmp1 = ""
      local dataTmp2 = ""
      size = chunk_size
      
      data_start = file:read(chunk_size)
      
      while data_end do
        size = size + string.len(data_end)
        dataTmp1 = dataTmp2
        dataTmp2 = data_end
        data_end = file:read(chunk_size)
        collectgarbage()
      end
      data_end = string.sub((dataTmp1..dataTmp2), -chunk_size)
    elseif not file_exist(openSub.file.path) 
    and openSub.file.stat then
      vlc.msg.dbg("[VLSub] Read hash data from stream")
      
      local file = vlc.stream(openSub.file.uri)
      
      if not file then
        vlc.msg.dbg("[VLSub] No stream")
        return false
      end
      
      size = openSub.file.stat.size
      local decal = size%chunk_size
      
      data_start = file:read(chunk_size)
      
      -- "Seek" to the end 
      file:read(decal)
      
      for i = 1, math.floor(((size-decal)/chunk_size))-2 do
        file:read(chunk_size)
      end
      
      data_end = file:read(chunk_size)
        
      file = nil
    else
      vlc.msg.dbg("[VLSub] Read hash data from file")
      local file = io.open( openSub.file.path, "rb")
      if not file then
        vlc.msg.dbg("[VLSub] No stream")
        return false
      end
      
      data_start = file:read(chunk_size)
      size = file:seek("end", -chunk_size) + chunk_size
      data_end = file:read(chunk_size)
      file = nil
    end
    
  -- Hash calculation
    local lo = size
    local hi = 0
    local o,a,b,c,d,e,f,g,h
    local hash_data = data_start..data_end
    local max_size = 4294967296
    local overflow
    
    for i = 1,  #hash_data, 8 do
      a,b,c,d,e,f,g,h = hash_data:byte(i,i+7)
      lo = lo + a + b*256 + c*65536 + d*16777216
      hi = hi + e + f*256 + g*65536 + h*16777216
      
      if lo > max_size then
        overflow = math.floor(lo/max_size)
        lo = lo-(overflow*max_size)
        hi = hi+overflow
      end
      
      if hi > max_size then
        overflow = math.floor(hi/max_size)
        hi = hi-(overflow*max_size)
      end
    end
    
    openSub.file.bytesize = size
    openSub.file.hash = string.format("%08x%08x", hi,lo)
    vlc.msg.dbg("[VLSub] Video hash: "..openSub.file.hash)
    vlc.msg.dbg("[VLSub] Video bytesize: "..size)
    collectgarbage()
    return true
  end,
  checkSession = function()
    
    if openSub.session.token == "" then
      openSub.request("LogIn")
    else
      openSub.request("NoOperation")
    end
  end
}

function searchHash()
  local sel = input_table["language"]:get_value()
  if sel == 0 then
    openSub.movie.sublanguageid = 'all'
  else
    openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
  end
  
  openSub.getMovieHash()
  
  if openSub.file.hash then
    openSub.checkSession()
    openSub.request("SearchSubtitlesByHash")
    display_subtitles()
  end
end

function searchIMBD()
  openSub.movie.title = trim(input_table["title"]:get_text())
  openSub.movie.seasonNumber = tonumber(
    input_table["seasonNumber"]:get_text())
  openSub.movie.episodeNumber = tonumber(
    input_table["episodeNumber"]:get_text())

  local sel = input_table["language"]:get_value()
  if sel == 0 then
    openSub.movie.sublanguageid = 'all'
  else
    openSub.movie.sublanguageid = openSub.conf.languages[sel][1]
  end
  
  if openSub.movie.title ~= "" then
    openSub.checkSession()
    openSub.request("SearchSubtitles")
    display_subtitles()
  end
end

function display_subtitles()
  local mainlist = input_table["mainlist"]
  mainlist:clear()
  
  if openSub.itemStore == "0" then 
    mainlist:add_value(lang["mess_no_res"], 1)
    setMessage("<b>"..lang["mess_complete"]..":</b> "..
      lang["mess_no_res"])
  elseif openSub.itemStore then 
    for i, item in ipairs(openSub.itemStore) do
      mainlist:add_value(
      item.SubFileName..
      " ["..item.SubLanguageID.."]"..
      " ("..item.SubSumCD.." CD)", i)
    end
    setMessage("<b>"..lang["mess_complete"]..":</b> "..
      #(openSub.itemStore).."  "..lang["mess_res"])
  end
end

function get_first_sel(list)
  local selection = list:get_selection()
  for index, name in pairs(selection) do 
    return index
  end
  return 0
end

function download_subtitles()
  local index = get_first_sel(input_table["mainlist"])
  
  if index == 0 then
    setMessage(lang["mess_no_selection"])
    return false
  end
  
  openSub.actionLabel = lang["mess_downloading"] 
  
  display_subtitles() -- reset selection
  
  local item = openSub.itemStore[index]
  
  if openSub.option.downloadBehaviour == 'manual' 
  or not openSub.file.hasInput then
    local link = "<span style='color:#181'>"
    link = link.."<b>"..lang["mess_dowload_link"]..":</b>"
    link = link.."</span> &nbsp;"
    link = link.."</span> &nbsp;<a href='"..
      item.ZipDownloadLink.."'>"
    link = link..item.MovieReleaseName.."</a>"
    
    setMessage(link)
    return false
  end
  
  local message = ""
  local subfileName = openSub.file.name or ""
  
  if openSub.option.langExt then
    subfileName = subfileName.."."..item.SubLanguageID
  end
  
  subfileName = subfileName.."."..item.SubFormat
  local tmp_dir
  local file_target_access = true
  
  if is_dir(openSub.file.dir) then
    tmp_dir = openSub.file.dir
  elseif openSub.conf.dirPath then
    tmp_dir = openSub.conf.dirPath
    
    message = "<br>"..error_tag(lang["mess_save_fail"].." &nbsp;"..
    "<a href='"..vlc.strings.make_uri(openSub.conf.dirPath).."'>"..
    lang["mess_click_link"].."</a>")
  else
    setError(lang["mess_save_fail"].." &nbsp;"..
    "<a href='"..item.ZipDownloadLink.."'>"..
    lang["mess_click_link"].."</a>")
    return false
  end
  
  local tmpFileURI, tmpFileName = dump_zip(
    item.ZipDownloadLink, 
    tmp_dir, 
    item.SubFileName)
  
  vlc.msg.dbg("[VLsub] tmpFileName: "..tmpFileName)
  
  -- Determine if the path to the video file is accessible for writing
  
  local target = openSub.file.dir..subfileName
  
  if not file_touch(target) then
    if openSub.conf.dirPath then
      target =  openSub.conf.dirPath..slash..subfileName
      message = "<br>"..
        error_tag(lang["mess_save_fail"].." &nbsp;"..
        "<a href='"..vlc.strings.make_uri(
          openSub.conf.dirPath).."'>"..
          lang["mess_click_link"].."</a>")
    else
      setError(lang["mess_save_fail"].." &nbsp;"..
      "<a href='"..item.ZipDownloadLink.."'>"..
      lang["mess_click_link"].."</a>")
      return false
    end
  end
  
  vlc.msg.dbg("[VLsub] Subtitles files: "..target)
  
  -- Unzipped data into file target 
    
  local stream = vlc.stream(tmpFileURI)
  local data = ""
  local subfile = io.open(target, "wb")
  
  while data do
    subfile:write(data)
    data = stream:read(65536)
  end
  
  subfile:flush()
  subfile:close()
  
  stream = nil
  collectgarbage()
  
  if not os.remove(tmpFileName) then
    vlc.msg.err("[VLsub] Unable to remove temp: "..tmpFileName)
  end
    
  -- load subtitles
  if add_sub(target) then 
    message = success_tag(lang["mess_loaded"]) .. message
  else
    message = error_tag(lang["mess_not_load"]) .. message
  end
  
  setMessage(message)
end

function dump_zip(url, dir, subfileName)
  -- Dump zipped data in a temporary file
  setMessage(openSub.actionLabel..": "..progressBarContent(0))
  local resp = get(url)
  
  if not resp then 
    setError(lang["mess_no_response"])
    return false 
  end
  
  local tmpFileName = dir..subfileName..".gz"
  if not file_touch(tmpFileName) then
    return false
  end
  local tmpFile = assert(io.open(tmpFileName, "wb"))
  
  tmpFile:write(resp)
  tmpFile:flush()
  tmpFile:close()
  tmpFile = nil
  collectgarbage()
  return "zip://"..make_uri(tmpFileName)
    .."!/"..subfileName, tmpFileName
end

function add_sub(subPath)
  if vlc.item or vlc.input.item() then
    subPath = decode_uri(subPath)
    vlc.msg.dbg("[VLsub] Adding subtitle :" .. subPath)
    return vlc.input.add_subtitle(subPath)
  end
  return false
end

            --[[ Interface helpers]]--

function progressBarContent(pct)
  local accomplished = math.ceil(
    openSub.option.progressBarSize*pct/100)
  local left = openSub.option.progressBarSize - accomplished
  local content = "<span style='background-color:#181;color:#181;'>"..
    string.rep ("-", accomplished).."</span>"..
    "<span style='background-color:#fff;color:#fff;'>"..
    string.rep ("-", left)..
    "</span>"
  return content
end

function setMessage(str)
  if input_table["message"] then
    input_table["message"]:set_text(str)
    dlg:update()
  end
end

function setError(mess)
  setMessage(error_tag(mess))
end

function success_tag(str)
  return "<span style='color:#181'><b>"..
  lang["mess_success"]..":</b></span> "..str..""
end

function error_tag(str)
  return "<span style='color:#B23'><b>"..
  lang["mess_error"]..":</b></span> "..str..""
end

            --[[ Network utils]]--

function get(url)
  local host, path = parse_url(url)
  local header = {
    "GET "..path.." HTTP/1.1", 
    "Host: "..host, 
    "User-Agent: "..openSub.conf.userAgentHTTP,
    "",
    ""
  }
  local request = table.concat(header, "\r\n")
    
  local response
  local status, response = http_req(host, 80, request)
  
  if status == 200 then 
    return response
  else
    return false, status, response
  end
end

function http_req(host, port, request)
  local fd = vlc.net.connect_tcp(host, port)
  if not fd then return false end
  local pollfds = {}
  
  pollfds[fd] = vlc.net.POLLIN
  vlc.net.send(fd, request)
  vlc.net.poll(pollfds)
  
  local chunk = vlc.net.recv(fd, 2048)
  local response = ""
  local headerStr, header, body
  local contentLength, status
  local pct = 0
  
  while chunk do
    response = response..chunk
    if not header then
        headerStr, body = response:match("(.-\r?\n)\r?\n(.*)")
        if headerStr then
            response = body
            header = parse_header(headerStr)
            contentLength = tonumber(header["Content-Length"])
            status = tonumber(header["statuscode"])
        end
    end

    if contentLength then
        bodyLenght = #response
        pct = bodyLenght / contentLength * 100
        setMessage(openSub.actionLabel..": "..progressBarContent(pct))
      if bodyLenght >= contentLength then
        break
      end
    end

    vlc.net.poll(pollfds)
    chunk = vlc.net.recv(fd, 1024)
  end

  vlc.net.close(fd)
  
  if status == 301 
  and header["Location"] then
    local host, path = parse_url(trim(header["Location"]))
    request = request
    :gsub("^([^%s]+ )([^%s]+)", "%1"..path)
    :gsub("(Host: )([^\n]*)", "%1"..host)
    
    return http_req(host, port, request)
  end

  return status, response
end

function parse_header(data)
  local header = {}
  
  for name, s, val in string.gmatch(
    data,
    "([^%s:]+)(:?)%s([^\n]+)\r?\n")
  do
    if s == "" then 
    header['statuscode'] = tonumber(string.sub(val, 1 , 3))
    else 
      header[name] = val
    end
  end
  return header
end 

function parse_url(url)
  local url_parsed = vlc.net.url_parse(url)
  return  url_parsed["host"], 
    url_parsed["path"],
    url_parsed["option"]
end

            --[[ XML utils]]--

function parse_xml(data)
  local tree = {}
  local stack = {}
  local tmp = {}
  local level = 0
  local op, tag, p, empty, val
  table.insert(stack, tree)
  local resolve_xml =  vlc.strings.resolve_xml_special_chars

  for op, tag, p, empty, val in string.gmatch(
    data, 
    "[%s\r\n\t]*<(%/?)([%w:_]+)(.-)(%/?)>"..
    "[%s\r\n\t]*([^<]*)[%s\r\n\t]*"
  ) do
    if op=="/" then
      if level>0 then
        level = level - 1
        table.remove(stack)
      end
    else
      level = level + 1
      if val == "" then
        if type(stack[level][tag]) == "nil" then
          stack[level][tag] = {}
          table.insert(stack, stack[level][tag])
        else
          if type(stack[level][tag][1]) == "nil" then
            tmp = nil
            tmp = stack[level][tag]
            stack[level][tag] = nil
            stack[level][tag] = {}
            table.insert(stack[level][tag], tmp)
          end
          tmp = nil
          tmp = {}
          table.insert(stack[level][tag], tmp)
          table.insert(stack, tmp)
        end
      else
        if type(stack[level][tag]) == "nil" then
          stack[level][tag] = {}
        end
        stack[level][tag] = resolve_xml(val)
        table.insert(stack,  {})
      end
      if empty ~= "" then
        stack[level][tag] = ""
        level = level - 1
        table.remove(stack)
      end
    end
  end
  
  collectgarbage()
  return tree
end

function parse_xmlrpc(data)
  local tree = {}
  local stack = {}
  local tmp = {}
  local tmpTag = ""
  local level = 0
  local op, tag, p, empty, val
  local resolve_xml =  vlc.strings.resolve_xml_special_chars
  table.insert(stack, tree)

  for op, tag, p, empty, val in string.gmatch(
    data, 
    "<(%/?)([%w:]+)(.-)(%/?)>[%s\r\n\t]*([^<]*)"
  ) do
    if op=="/" then
      if tag == "member" or tag == "array" then
        if level>0  then
          level = level - 1
          table.remove(stack)
        end
      end
    elseif tag == "name" then 
      level = level + 1
      if val~= "" then tmpTag  = resolve_xml(val) end
      
      if type(stack[level][tmpTag]) == "nil" then
        stack[level][tmpTag] = {}
        table.insert(stack, stack[level][tmpTag])
      else
        tmp = nil
        tmp = {}
        table.insert(stack[level-1], tmp)
        
        stack[level] = nil
        stack[level] = tmp
        table.insert(stack, tmp)
      end
      if empty ~= "" then
        level = level - 1
        stack[level][tmpTag] = ""
        table.remove(stack)
      end
    elseif tag == "array" then
      level = level + 1
      tmp = nil
      tmp = {}
      table.insert(stack[level], tmp)
      table.insert(stack, tmp)
    elseif val ~= "" then 
      stack[level][tmpTag] = resolve_xml(val)
    end
  end
  collectgarbage()
  return tree
end

function dump_xml(data)
  local level = 0
  local stack = {}
  local dump = ""
  local convert_xml = vlc.strings.convert_xml_special_chars
  
  local function parse(data, stack)
    local data_index = {}
    local k
    local v
    local i
    local tb
    
    for k,v in pairs(data) do
      table.insert(data_index, {k, v})
      table.sort(data_index, function(a, b)
        return a[1] < b[1] 
      end)
    end
    
    for i,tb in pairs(data_index) do
      k = tb[1]
      v = tb[2]
      if type(k)=="string" then
        dump = dump.."\r\n"..string.rep(
          " ",
          level)..
          "<"..k..">"	
        table.insert(stack, k)
        level = level + 1
      elseif type(k)=="number" and k ~= 1 then
        dump = dump.."\r\n"..string.rep(
          " ",
          level-1)..
          "<"..stack[level]..">"
      end
      
      if type(v)=="table" then
        parse(v, stack)
      elseif type(v)=="string" then
        dump = dump..(convert_xml(v) or v)
      elseif type(v)=="number" then
        dump = dump..v
      else
        dump = dump..tostring(v)
      end
      
      if type(k)=="string" then
        if type(v)=="table" then
          dump = dump.."\r\n"..string.rep(
            " ",
            level-1)..
            "</"..k..">"
        else
          dump = dump.."</"..k..">"
        end
        table.remove(stack)
        level = level - 1
        
      elseif type(k)=="number" and k ~= #data then
        if type(v)=="table" then
          dump = dump.."\r\n"..string.rep(
            " ",
            level-1)..
            "</"..stack[level]..">"
        else
          dump = dump.."</"..stack[level]..">"
        end
      end
    end
  end
  parse(data, stack)
  collectgarbage()
  return dump
end

            --[[ Misc utils]]--

function make_uri(str)
  str = str:gsub("\\", "/")
  local windowdrive = string.match(str, "^(%a:).+$")
  local encode_uri = vlc.strings.encode_uri_component
  local encodedPath = ""
  for w in string.gmatch(str, "/([^/]+)") do
    encodedPath = encodedPath.."/"..encode_uri(w) 
  end
    
  if windowdrive then
    return "file:///"..windowdrive..encodedPath
  else
    return "file://"..encodedPath
  end
end

function file_touch(name) -- test write ability
  if not name or trim(name) == "" 
  then return false end
  
  local f=io.open(name ,"w")
  if f~=nil then 
    io.close(f) 
    return true 
  else 
    return false 
  end
end

function file_exist(name) -- test readability
  if not name or trim(name) == "" 
  then return false end
  local f=io.open(name ,"r")
  if f~=nil then 
    io.close(f) 
    return true 
  else 
    return false 
  end
end

function is_dir(path)
  if not path or trim(path) == "" 
  then return false end
  -- Remove slash at the end or it won't work on Windows
  path = string.gsub(path, "^(.-)[\\/]?$", "%1")
  local f, _, code = io.open(path, "rb")
  
  if f then 
    _, _, code = f:read("*a")
    f:close()
    if code == 21 then
      return true
    end
  elseif code == 13 then
    return true
  end
  
  return false
end

function list_dir(path)
  if not path or trim(path) == "" 
  then return false end
  local dir_list_cmd 
  local list = {}
  if not is_dir(path) then return false end
  
  if openSub.conf.os == "win" then
    dir_list_cmd = io.popen('dir /b "'..path..'"')
  elseif openSub.conf.os == "lin" then
    dir_list_cmd = io.popen('ls -1 "'..path..'"')
  end
  
  if dir_list_cmd then
    for filename in dir_list_cmd:lines() do
      if string.match(filename, "^[^%s]+.+$") then
        table.insert(list, filename)
      end
    end
    return list
  else
    return false
  end
end

function mkdir_p(path)
  if not path or trim(path) == "" 
  then return false end
  if openSub.conf.os == "win" then
    os.execute('mkdir "' .. path..'"')
  elseif openSub.conf.os == "lin" then
    os.execute("mkdir -p '" .. path.."'")
  end
end

function decode_uri(str)
  vlc.msg.err(slash)
  return str:gsub("/", slash)
end

function is_window_path(path)
  return string.match(path, "^(%a:.+)$")
end

function is_win_safe(path)
  if not path or trim(path) == "" 
  or not is_window_path(path)
  then return false end
  return string.match(path, "^%a?%:?[\\%w%p%s§¤]+$")
end
    
function trim(str)
  if not str then return "" end
  return string.gsub(str, "^[\r\n%s]*(.-)[\r\n%s]*$", "%1")
end

function remove_tag(str)
  return string.gsub(str, "{[^}]+}", "")
end
