MessageSettings = {
  none            = {},
  consoleRed      = { color = TextColors.red,    consoleTab='Default' },
  consoleOrange   = { color = TextColors.orange, consoleTab='Default' },
  consoleBlue     = { color = TextColors.lightblue,   consoleTab='Default' },
  centerRed       = { color = TextColors.red,    consoleTab='Server Log', screenTarget='lowCenterLabel' },
  centerGreen     = { color = TextColors.green,  consoleTab='Server Log', screenTarget='highCenterLabel',   consoleOption='showInfoMessagesInConsole' },
  centerWhite     = { color = TextColors.white,  consoleTab='Server Log', screenTarget='middleCenterLabel', consoleOption='showEventMessagesInConsole' },
  bottomWhite     = { color = TextColors.white,  consoleTab='Server Log', screenTarget='statusLabel',       consoleOption='showEventMessagesInConsole' },
  status          = { color = TextColors.white,  consoleTab='Server Log', screenTarget='statusLabel',       consoleOption='showStatusMessagesInConsole' },
  statusSmall     = { color = TextColors.white,                           screenTarget='statusLabel' },
  private         = { color = TextColors.lightblue,                       screenTarget='privateLabel' }
}

MessageTypes = {
  [MessageModes.MonsterSay] = MessageSettings.consoleOrange,
  [MessageModes.MonsterYell] = MessageSettings.consoleOrange,
  [MessageModes.BarkLow] = MessageSettings.consoleOrange,
  [MessageModes.BarkLoud] = MessageSettings.consoleOrange,
  [MessageModes.Failure] = MessageSettings.statusSmall,
  [MessageModes.Login] = MessageSettings.bottomWhite,
  [MessageModes.Game] = MessageSettings.centerWhite,
  [MessageModes.Status] = MessageSettings.status,
  [MessageModes.Warning] = MessageSettings.centerRed,
  [MessageModes.Look] = MessageSettings.centerGreen,
  [MessageModes.Loot] = MessageSettings.centerGreen,
  [MessageModes.Red] = MessageSettings.consoleRed,
  [MessageModes.Blue] = MessageSettings.consoleBlue,
  [MessageModes.PrivateFrom] = MessageSettings.consoleBlue,

  [MessageModes.GamemasterBroadcast] = MessageSettings.consoleRed,

  [MessageModes.DamageDealed] = MessageSettings.status,
  [MessageModes.DamageReceived] = MessageSettings.status,
  [MessageModes.Heal] = MessageSettings.status,
  [MessageModes.Exp] = MessageSettings.status,

  [MessageModes.DamageOthers] = MessageSettings.none,
  [MessageModes.HealOthers] = MessageSettings.none,
  [MessageModes.ExpOthers] = MessageSettings.none,

  [MessageModes.TradeNpc] = MessageSettings.centerWhite,
  [MessageModes.Guild] = MessageSettings.centerWhite,
  [MessageModes.Party] = MessageSettings.centerGreen,
  [MessageModes.PartyManagement] = MessageSettings.centerWhite,
  [MessageModes.TutorialHint] = MessageSettings.centerWhite,
  [MessageModes.BeyondLast] = MessageSettings.centerWhite,
  [MessageModes.Report] = MessageSettings.consoleRed,
  [MessageModes.HotkeyUse] = MessageSettings.centerGreen,

  [254] = MessageSettings.private
}

messagesPanel = nil

function init()
  for messageMode, _ in pairs(MessageTypes) do
    registerMessageMode(messageMode, displayMessage)
  end

  connect(g_game, 'onGameEnd', clearMessages)
  messagesPanel = g_ui.loadUI('textmessage', modules.game_interface.getRootPanel())
end

function terminate()
  for messageMode, _ in pairs(MessageTypes) do
    unregisterMessageMode(messageMode, displayMessage)
  end

  disconnect(g_game, 'onGameEnd', clearMessages)
  clearMessages()
  messagesPanel:destroy()
end

function calculateVisibleTime(text)
  return math.max(#text * 100, 4000)
end

-- ------------------------------------------------------- rzadkosc lupu na ekranie
--
-- Wymaganie: JEDNA linia komunikatu, rzadkie nazwy pokolorowane W MIEJSCU
-- (tak jak na starym silniku), tymi samymi 3 kolorami co w konsoli.
--
-- Dlaczego nie nakladka jak w konsoli: etykieta na srodku ekranu jest
-- wysrodkowana i zawijana (text-align: center, text-wrap: true) i uzywa fontu
-- "verdana-11px-rounded", ktory ma niezerowy odstep miedzy znakami
-- (spacing: -1 -3 w pliku .otfont). Nakladka z konsoli (addRarityOverlays)
-- dziala tylko dlatego, ze kazda ukryta litera da sie zastapic wypelniaczem
-- string.char(127) o TEJ SAMEJ szerokosci co oryginalna litera - a tabela
-- szerokosci liter w game_console.lua jest policzona dla fontu
-- "verdana-11px-antialised" (spacing 0 0 - gdy w pliku fontu brak klucza
-- "spacing", bitmapfont.cpp zaklada Size(0,0)). Przy niezerowym odstepie
-- kazda dodatkowa "sztuczna" litera skraca slowo o wartosc odstepu, wiec dla
-- dluzszych slow kolor wyladowalby kilka pikseli obok prawdziwego tekstu.
-- To nie da sie bezpiecznie zagwarantowac - i nie o to tu chodzi.
--
-- Zamiast liczyc piksele, korzystamy z ukladu silnika: zdanie dzielimy na
-- SLOWA (splitWords) i kazde slowo renderujemy jako WLASNA etykiete
-- (TextMessageWordLabel) w wierszu z layoutem horizontalBox
-- (TextMessageRow, patrz textmessage.otui). Pozycje wynikaja z tego, jak
-- horizontalBox ustawia dzieci jedno za drugim - kazda etykieta mierzy SAMA
-- SIEBIE (text-auto-resize), wiec nie ma znaczenia jaki to font ani jaki ma
-- odstep miedzy znakami. Dlugie komunikaty nie miesciloby sie w jednym
-- wierszu (panel ma 360px) - dlatego lamiemy na kolejne wiersze
-- (TextMessageRows, verticalBox), zawsze na granicy slowa, tak jak robi to
-- font:wrapText() dla zwyklych etykiet.
--
-- Komunikaty BEZ gwiazdek dalej ida stara sciezka - jedna etykieta,
-- text-wrap silnika - zeby ich wyglad w ogole sie nie zmienil.

--- Dzieli tekst na slowa (rozdzielone spacjami) z pozycjami znakow, zeby
--- dalo sie je zestawic z pozycjami segmentow z parseRarityMarkup.
local function splitWords(text)
  local words = {}
  for s, word, e in text:gmatch("()(%S+)()") do
    table.insert(words, { text = word, s = s, e = e - 1 })
  end
  return words
end

--- Zwraca numer rzadkosci (1-3) jesli slowo choc czesciowo naklada sie na
--- ktorys segment (np. "sword," - przecinek zaraz po nazwie trafia do tego
--- samego slowa co "sword", wiec liczy sie jako nachodzenie), albo nil.
local function tierForWord(word, segments)
  for _, seg in ipairs(segments) do
    if word.s <= seg.e and word.e >= seg.s then
      return seg.tier
    end
  end
  return nil
end

-- Jedna, ukryta etykieta uzywana wylacznie do mierzenia szerokosci slow
-- (text-auto-resize daje dokladny, prawdziwy rozmiar zaraz po setText -
-- synchronicznie, patrz UIWidget::updateText w silniku). Tworzona leniwie,
-- bo w momencie ladowania modulu jeszcze nie ma czego mierzyc.
local rarityMeasureLabel
local raritySpaceWidth

local function measureWord(text)
  if not rarityMeasureLabel then
    rarityMeasureLabel = g_ui.createWidget('TextMessageWordLabel', messagesPanel)
    rarityMeasureLabel:setVisible(false)
  end
  rarityMeasureLabel:setText(text)
  return rarityMeasureLabel:getWidth(), rarityMeasureLabel:getHeight()
end

local function getSpaceWidth()
  if not raritySpaceWidth then
    raritySpaceWidth = measureWord(' ')
  end
  return raritySpaceWidth
end

--- Buduje wiersze slow w 'rowsContainer' (kontener z layoutem verticalBox).
--- Kazde slowo to osobna etykieta w wierszu (layout horizontalBox) - kolor
--- ustawiony wprost na danej etykiecie, wiec nie ma jak sie rozjechac.
--- Wiersz dostaje 'fixed-size: true' (patrz otui), zeby zewnetrzny
--- verticalBox go WYSRODKOWAL zamiast rozciagac na cala szerokosc panelu -
--- dlatego szerokosc/wysokosc wiersza ustawiamy sami (row:setWidth/setHeight),
--- a nie czekamy na fit-children z horizontalBox (ono i tak odpala sie z
--- opoznieniem przez g_dispatcher - do wyliczenia lamania linii uzywamy
--- wlasnego, synchronicznego licznika szerokosci).
local function buildMessageRows(rowsContainer, plainText, segments, color)
  rowsContainer:destroyChildren()

  local words = splitWords(plainText)
  if #words == 0 then return end

  local spaceWidth = getSpaceWidth()
  local maxRowWidth = rowsContainer:getParent():getWidth()

  local row, rowWidth, rowHeight

  local function startRow()
    row = g_ui.createWidget('TextMessageRow', rowsContainer)
    row:getLayout():setSpacing(spaceWidth)
    rowWidth = 0
    rowHeight = 0
  end

  startRow()

  for _, word in ipairs(words) do
    local wordWidth, wordHeight = measureWord(word.text)
    local extra = (rowWidth > 0) and (spaceWidth + wordWidth) or wordWidth

    -- nowy wiersz tylko gdy w obecnym jest juz jakies slowo (nigdy nie
    -- zostawiamy pustego wiersza) i doszlyby slowo by go przekroczylo -
    -- lamanie zawsze wypada na granicy slowa
    if rowWidth > 0 and rowWidth + extra > maxRowWidth then
      startRow()
      extra = wordWidth
    end

    local tier = tierForWord(word, segments)
    local wordLabel = g_ui.createWidget('TextMessageWordLabel', row)
    wordLabel:setText(word.text)
    wordLabel:setColor(tier and modules.game_console.RARITY_COLORS[tier] or color)

    rowWidth = rowWidth + extra
    rowHeight = math.max(rowHeight, wordHeight)
    row:setWidth(rowWidth)
    row:setHeight(rowHeight)
  end
end

function displayMessage(mode, text)
  if not g_game.isOnline() then return end

  local msgtype = MessageTypes[mode]
  if not msgtype then
    return
  end

  if msgtype == MessageSettings.none then return end

  if msgtype.consoleTab ~= nil and (msgtype.consoleOption == nil or modules.client_options.getOption(msgtype.consoleOption)) then
    modules.game_console.addText(text, msgtype, tr(msgtype.consoleTab))
    --TODO move to game_console
  end

  if msgtype.screenTarget then
    local label = messagesPanel:recursiveGetChildById(msgtype.screenTarget)
    local rowsContainer = messagesPanel:recursiveGetChildById(msgtype.screenTarget .. 'Rows')

    local plain, segments = modules.game_console.parseRarityMarkup(text)
    local visibleTime = calculateVisibleTime(text)

    if segments and rowsContainer then
      -- wiadomosc ma rzadkie nazwy - zamiast jednej etykiety pokazujemy
      -- wiersze slow z kolorem w miejscu (patrz buildMessageRows)
      label:setVisible(false)
      removeEvent(label.hideEvent)

      buildMessageRows(rowsContainer, plain, segments, msgtype.color)
      rowsContainer:setVisible(true)
      removeEvent(rowsContainer.hideEvent)
      rowsContainer.hideEvent = scheduleEvent(function() rowsContainer:setVisible(false) end, visibleTime)
    else
      -- brak gwiazdek - dokladnie ta sama sciezka co przed wprowadzeniem
      -- kolorowania rzadkosci na ekranie, wyglad bez zmian
      if rowsContainer then
        rowsContainer:setVisible(false)
        removeEvent(rowsContainer.hideEvent)
      end

      label:setText(plain)
      label:setColor(msgtype.color)
      label:setVisible(true)
      removeEvent(label.hideEvent)
      label.hideEvent = scheduleEvent(function() label:setVisible(false) end, visibleTime)
    end
  end
end

function displayPrivateMessage(text)
  displayMessage(254, text)
end

function displayStatusMessage(text)
  displayMessage(MessageModes.Status, text)
end

function displayFailureMessage(text)
  displayMessage(MessageModes.Failure, text)
end

function displayGameMessage(text)
  displayMessage(MessageModes.Game, text)
end

function displayBroadcastMessage(text)
  displayMessage(MessageModes.Warning, text)
end

function clearMessages()
  for _i,child in pairs(messagesPanel:recursiveGetChildren()) do
    if child:getId():match('Label') then
      child:hide()
      removeEvent(child.hideEvent)
      -- kontenery wierszy z rzadkimi nazwami (np. 'highCenterLabelRows') maja
      -- dynamicznie tworzone wiersze i etykiety slow w srodku - sprzataj je
      -- calkowicie, zeby nic nie zostawalo do nastepnego zalogowania
      if child:getId():match('Rows$') then
        child:destroyChildren()
      end
    end
  end
end

function LocalPlayer:onAutoWalkFail(player)
  modules.game_textmessage.displayFailureMessage(tr('There is no way.'))
end
