--[[
| This file was obtained through the combined efforts
| of Madbluntz & Plymouth Antiquarian Society.
|
| Credits: lifestorm, Gregory Wayne Rossel JR.,
| 	Maloy, DrPepper10 @ RIP, Atle!
|
| Visit for more: https://plymouth.thetwilightzone.ru/
--]]

---- Clientside language stuff

-- Need to build custom tables of strings. Can't use language.Add as there is no
-- way to access the translated string in Lua. Identifiers only get translated
-- when Source/gmod print them. By using our own table and our own lookup, we
-- have far more control. Maybe it's slower, but maybe not, we aren't scanning
-- strings for "#identifiers" after all.

LANG.Strings = {}

local ttt_language = CreateConVar("ttt_language", "auto", FCVAR_ARCHIVE)

LANG.DefaultLanguage = "english"
LANG.ActiveLanguage  = LANG.DefaultLanguage

LANG.ServerLanguage  = "english"

local cached_default, cached_active

function LANG.CreateLanguage(raw_lang_name)
   if not raw_lang_name then return end
   lang_name = string.lower(raw_lang_name)

   if not LANG.IsLanguage(lang_name) then
      LANG.Strings[lang_name] = {
         -- Empty string is very convenient to have, so init with that.
         [""] = "",
         -- Presentational, not-lowercased language name
         language_name = raw_lang_name
      }
   end

   if lang_name == LANG.DefaultLanguage then
      cached_default = LANG.Strings[lang_name]

      -- when a string is not found in the active or the default language, an
      -- error message is shown
      setmetatable(LANG.Strings[lang_name],
                   {
                      __index = function(tbl, name)
                                   return Format("[ERROR: Translation of %s not found]", name), false
                                end
                   })
   end

   return LANG.Strings[lang_name]
end

-- Add a string to a language. Should not be used in a language file, only for
-- adding strings elsewhere, such as a SWEP script.
function LANG.AddToLanguage(lang_name, string_name, string_text)
   lang_name = lang_name and string.lower(lang_name)

   if not LANG.IsLanguage(lang_name) then
      ErrorNoHalt(Format("Failed to add '%s' to language '%s': language does not exist.\n", tostring(string_name), tostring(lang_name)))
      return false
   end

   LANG.Strings[lang_name][string_name] = string_text

   return string_name
end

-- Simple and fastest name->string lookup
function LANG.GetTranslation(name)
   return cached_active[name]
end

-- Lookup with no error upon failback, just nil. Slightly slower, but best way
-- to handle lookup of strings that may legitimately fail to exist
-- (eg. SWEP-defined).
function LANG.GetRawTranslation(name)
   return rawget(cached_active, name) or rawget(cached_default, name)
end

-- A common idiom
local GetRaw = LANG.GetRawTranslation
function LANG.TryTranslation(name)
   return GetRaw(name) or name
end

local interp = string.Interp

-- Parameterised version, performs string interpolation. Slower than
-- GetTranslation.
function LANG.GetParamTranslation(name, params)
   return interp(cached_active[name], params)
end
LANG.GetPTranslation = LANG.GetParamTranslation

function LANG.GetTranslationFromLanguage(name, lang_name)
   return rawget(LANG.Strings[lang_name], name)
end

-- Ability to perform lookups in the current language table directly is of
-- interest to consumers in draw/think hooks. Grabbing a translation directly
-- from the table is very fast, and much simpler than a local caching solution.
-- Modifying it would typically be a bad idea.
function LANG.GetUnsafeLanguageTable() return cached_active end

function LANG.GetUnsafeNamed(name) return LANG.Strings[name] end

-- Safe and slow access, not sure if it's ever useful.
function LANG.GetLanguageTable(lang_name)
   lang_name = lang_name or LANG.ActiveLanguage

   local cpy = table.Copy(LANG.Strings[lang_name])
   SetFallback(cpy)

   return cpy
end


local function SetFallback(tbl)
   -- languages may deal with this themselves, or may already have the fallback
   local m = getmetatable(tbl)
   if m and m.__index then return end

   -- Set the __index of the metatable to use the default lang, which makes any
   -- keys not found in the table to be looked up in the default. This is faster
   -- than using branching ("return lang[x] or default[x] or errormsg") and
   -- allows fallback to occur even when consumer code directly accesses the
   -- lang table for speed (eg. in a rendering hook).
   setmetatable(tbl,
                {
                   __index = cached_default
                })

end

function LANG.SetActiveLanguage(lang_name)
   lang_name = lang_name and string.lower(lang_name)

   if LANG.IsLanguage(lang_name) then
      local old_name = LANG.ActiveLanguage
      LANG.ActiveLanguage = lang_name

      -- cache ref to table to avoid hopping through LANG and Strings every time
      cached_active = LANG.Strings[lang_name]

      -- set the default lang as fallback, if it hasn't yet
      SetFallback(cached_active)

      -- some interface elements will want to know so they can update themselves
      if old_name != lang_name then
         hook.Call("TTTLanguageChanged", GAMEMODE, old_name, lang_name)
      end
   else
      MsgN(Format("The language '%s' does not exist on this server. Falling back to English...", lang_name))

      -- fall back to default if possible
      if lang_name != LANG.DefaultLanguage then
         LANG.SetActiveLanguage(LANG.DefaultLanguage)
      end
   end
end

function LANG.Init()
   local lang_name = ttt_language:GetString()

   -- if we want to use the server language, we'll be switching to it as soon as
   -- we hear from the server which one it is, for now use default
   if LANG.IsServerDefault(lang_name) then
      lang_name = LANG.ServerLanguage
   end

   LANG.SetActiveLanguage(lang_name)
end

function LANG.IsServerDefault(lang_name)
   lang_name = string.lower(lang_name)
   return lang_name == "server default" or lang_name == "auto"
end

function LANG.IsLanguage(lang_name)
   lang_name = lang_name and string.lower(lang_name)
   return LANG.Strings[lang_name]
end

local function LanguageChanged(cv, old, new)
   if new and new != LANG.ActiveLanguage then

      if LANG.IsServerDefault(new) then
         new = LANG.ServerLanguage
      end

      LANG.SetActiveLanguage(new)
   end
end
cvars.AddChangeCallback("ttt_language", LanguageChanged)

local function ForceReload()
   LANG.SetActiveLanguage("english")
end
concommand.Add("ttt_reloadlang", ForceReload)

-- Get a copy of all available languages (keys in the Strings tbl)
function LANG.GetLanguages()
   local langs = {}
   for lang, strings in pairs(LANG.Strings) do
      table.insert(langs, lang)
   end
   return langs
end

function LANG.GetLanguageNames()
   -- Typically preferable to GetLanguages but separate to avoid API breakage.
   local lang_names = {}
   for lang, strings in pairs(LANG.Strings) do
      lang_names[lang] = strings["language_name"] or string.Capitalize(lang)
   end
   return lang_names
end

---- Styling

local bgcolor = {
   [ROLE_TRAITOR]   = Color(150, 0, 0, 200),
   [ROLE_DETECTIVE] = Color(0, 0, 150, 200),
   [ROLE_INNOCENT]  = Color(0, 50,  0, 200)
};

-- Table of styles that can take a string and display it in some position,
-- colour, etc.
LANG.Styles = {
   default = function(text)
                MSTACK:AddMessage(text)
                print("TTT:   " .. text)
             end,

   rolecolour = function(text)
                   MSTACK:AddColoredBgMessage(text,
                                              bgcolor[ LocalPlayer():GetRole() ])
                   print("TTT:   " .. text)
                end,

   chat_warn  = function(text)
                   chat.AddText(COLOR_RED, text)
                end,


   chat_plain = chat.AddText
};

-- Table mapping message name => message style name. If no message style is
-- defined, the default style is used. This is the case for the vast majority of
-- messages at the time of writing.
LANG.MsgStyle = {}

-- Access of message styles
function LANG.GetStyle(name)
   return LANG.MsgStyle[name] or LANG.Styles.default
end

-- Set a style by name or directly as style-function
function LANG.SetStyle(name, style)
   if isstring(style) then
      style = LANG.Styles[style]
   end

   LANG.MsgStyle[name] = style
end

function LANG.ShowStyledMsg(text, style)
   style(text)
end

function LANG.ProcessMsg(name, params)
   local raw  = LANG.GetTranslation(name)

   local text = raw

   if params then
      -- some of our params may be string names themselves
      for k, v in pairs(params) do
         if isstring(v) then
            local name = LANG.GetNameParam(v)
            if not name then continue end

            params[k] = LANG.GetTranslation(name)
         end
      end

      text = interp(raw, params)
   end

   LANG.ShowStyledMsg(text, LANG.GetStyle(name))
end



--- Message style declarations

-- Rather than having a big list of LANG.SetStyle calls, we specify it the other
-- way around here and churn through it in code. This is convenient because
-- we're doing it en-masse for some random interface things spread out over the
-- place.
--
-- Styles of custom SWEP messages and such should use LANG.SetStyle in their
-- script. The SWEP stuff here might be moved out to the SWEPS too.

local styledmessages = {
   rolecolour = {
      "round_traitors_one",
      "round_traitors_more",

      "buy_no_stock",
      "buy_pending",
      "buy_received",

      "xfer_no_recip",
      "xfer_no_credits",
      "xfer_success",
      "xfer_received",

      "c4_no_disarm",

      "tele_failed",
      "tele_no_mark",
      "tele_marked",

      "dna_identify",
      "dna_notfound",
      "dna_limit",
      "dna_decayed",
      "dna_killer",
      "dna_no_killer",
      "dna_armed",
      "dna_object",
      "dna_gone",

      "credit_det_all",
      "credit_tr_all",
      "credit_kill"
   },

   chat_plain = {
      "body_call",
      "disg_turned_on",
      "disg_turned_off",
      "mute_all",
      "mute_off",
      "mute_specs",
      "mute_living"
   },

   chat_warn = {
      "spec_mode_warning",
      "radar_not_owned",
      "radar_charging",
      "drop_no_room",
      "body_burning",

      "tele_no_ground",
      "tele_no_crouch",
      "tele_no_mark_ground",
      "tele_no_mark_crouch",

      "drop_no_ammo"
   }
};

local set_style = LANG.SetStyle
for style, msgs in pairs(styledmessages) do
   for _, name in ipairs(msgs) do
      set_style(name, style)
   end
end
