local fn  = vim.fn
local api = vim.api

local lsp_status = ""
local gstatus    = { ahead = 0, behind = 0 }

local update_status = function()
  local Job = require 'plenary.job'
  Job:new({
    command = 'git',
    args    = { 'rev-list', '--left-right', '--count', 'HEAD...@{upstream}' },
    on_exit = function(exit_job, _)
      local res = exit_job:result()[1]
      if type(res) ~= 'string' then
        gstatus = { ahead = 0, behind = 0 };
        return
      end
      local ok, ahead, behind = pcall(string.match, res, "(%d+)%s*(%d+)")
      if not ok then
        ahead, behind = 0, 0
      end
      gstatus = { ahead = ahead, behind = behind }
    end,
  }):start()

  if vim.lsp.status then
    lsp_status = vim.lsp.status()
  end
end

if _G.Gstatus_timer == nil then
  _G.Gstatus_timer = vim.uv.new_timer()
  _G.Gstatus_timer:start(0, 1000, vim.schedule_wrap(update_status))
else
  _G.Gstatus_timer:stop()
end

local M = {}

local colors = {
  line_bg  = '#21242b',
  bg       = '#000000',
  fg       = '#dobfa1',
  yellow   = '#fabd2f',
  cyan     = '#008080',
  darkblue = '#081633',
  green    = '#afd700',
  orange   = '#FF8800',
  purple   = '#5d4d7a',
  magenta  = '#c678dd',
  blue     = '#51afef',
  red      = '#ec5f67'
}

local function highlight(group, fg, bg)
  api.nvim_set_hl(0, group, { fg = fg, bg = bg })
end

highlight("Window"      , colors.red    , colors.bg)
highlight("Mode"        , colors.green  , colors.bg)
highlight("Filename"    , colors.blue   , colors.bg)
highlight("Fileformat"  , colors.purple , colors.bg)

highlight("GitBranch"   , colors.orange , colors.bg)
highlight("GitStatus"   , colors.magenta, colors.bg)
highlight("DiffAdded"   , colors.green  , colors.bg)
highlight("DiffChanged" , colors.orange , colors.bg)
highlight("DiffRemoved" , colors.red    , colors.bg)

highlight("DiagnosticsE", colors.red    , colors.bg)
highlight("DiagnosticsW", colors.yellow , colors.bg)
highlight("DiagnosticsI", colors.cyan   , colors.bg)
highlight("DiagnosticsH", colors.orange , colors.bg)

highlight("Location"    , colors.yellow , colors.bg)
highlight("Progress"    , colors.magenta, colors.bg)
highlight("WinbarFile"  , colors.magenta, colors.bg)
highlight("LspProgress" , colors.orange , colors.bg)

M.trunc_width = setmetatable({
  filename   = 140,
  git_status = 60,
  lsp_status = 80,
}, {
  __index = function()
    return 80
  end,
})

M.is_truncated = function(_, width)
  return api.nvim_win_get_width(0) < width
end

M.get_window_number = function()
  return string.format(" %s%d", "%#Window#", api.nvim_win_get_number(0))
end

M.modes = setmetatable({
  ["n" ] = "N"  ,
  ["no"] = "N·P",
  ["v" ] = "V"  ,
  ["V" ] = "V·L",
  [""] = "V·B", -- this is not ^V, but it's , they're different
  ["s" ] = "S"  ,
  ["S" ] = "S·L",
  [""] = "S·B", -- same with this one, it's not ^S but it's 
  ["i" ] = "I"  ,
  ["ic"] = "I"  ,
  ["R" ] = "R"  ,
  ["Rv"] = "V·R",
  ["c" ] = "C"  ,
  ["cv"] = "V·E",
  ["ce"] = "E"  ,
  ["r" ] = "P"  ,
  ["rm"] = "RM" ,
  ["r?"] = "C"  ,
  ["!" ] = "S"  ,
  ["t" ] = "T"  ,
}, {
  __index = function()
    return "U"
  end,
})

M.get_current_mode = function(self)
  local current_mode = api.nvim_get_mode().mode
  return string.format(" %s%s", "%#Mode#", self.modes[current_mode])
end

M.get_git_branch = function(self)
  if self:is_truncated(self.trunc_width.git_status) then
    return ""
  end

  return string.format(" %s %s", "%#GitBranch#", fn.FugitiveHead())
end

M.get_git_status = function(self)
  local is_git = fn.FugitiveHead() ~= ""
  local signs  = vim.b.minidiff_summary or { add = 0, change = 0, delete = 0 }

  return is_git
    and string.format(
      " %s+%s %s~%s %s-%s",
      "%#DiffAdded#",
      signs.add,
      "%#DiffChanged#",
      signs.change,
      "%#DiffRemoved#",
      signs.delete
    )
    or ""
end

M.get_filename = function()
  local filetype = vim.bo.filetype
  local special_ft = { 'diff', 'fugitive', 'git', 'qf' }
  for _, v in pairs(special_ft) do
    if v == filetype then
      return string.format(" %s%s", "%#Filename#", filetype)
    end
  end

  return string.format(" %s%s", "%#Filename#", '%t%m%r')
end

M.get_fileformat = function()
  return string.format("%s%s", "%#Fileformat#", vim.o.fileformat):lower()
end

M.diagnostic_status = function()
  local diagnostic = ''

  local num_errors = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.ERROR })
  if num_errors > 0 then
    diagnostic = string.format("%s%s%d", diagnostic, ' %#DiagnosticsE#E:', num_errors)
  end

  local num_warnings = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.WARN })
  if num_warnings > 0 then
    diagnostic = string.format("%s%s%d", diagnostic, ' %#DiagnosticsW#W:', num_warnings)
  end

  local num_infos = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.INFO })
  if num_infos > 0 then
    diagnostic = string.format("%s%s%d", diagnostic, ' %#DiagnosticsI#I:', num_infos)
  end

  local num_hints = #vim.diagnostic.get(0, { severity = vim.diagnostic.severity.HINT })
  if num_hints > 0 then
    diagnostic = string.format("%s%s%d", diagnostic, ' %#DiagnosticsH#H:', num_hints)
  end

  return diagnostic
end

M.get_line_col = function()
  return string.format(" %s%s", "%#Location#", "%l:%c")
end

M.progress = function()
  return string.format(" %s%s", "%#Progress#", "%3p%% ")
end

M.git_ahead_behind_status = function()
  local is_git = fn.FugitiveHead() ~= ""

  return
    is_git
    and
    string.format(" %s%s%d%s%d", '%#GitStatus#', '  ', gstatus.behind, '  ', gstatus.ahead)
    or ""
end

M.get_lsp_status = function(self)
  local trunc_width = self.trunc_width.lsp_status
  if self:is_truncated(trunc_width) then
    return ""
  end

  if vim.lsp.status then
    return string.format(" %s%s", '%#LspProgress#', string.sub(lsp_status, 1, trunc_width))
  end

  return ""
end

M.set_active = function(self)
  return table.concat {
    self:get_filename(),
    self:get_git_status(),
    self:git_ahead_behind_status(),
    "%=",
    self:get_lsp_status(),
    self:diagnostic_status(),
    self:get_line_col(),
    self:progress(),
  }
end

M.set_inactive = function(self)
  return table.concat {
    "%#Window#",
    -- Putting this in a function like the others seems to not work. And takes
    -- the window number of the active window. Same for filename.
    " %{winnr()}",
    self:get_git_branch(),
    "%=",
    self:get_fileformat(),
    self:get_line_col(),
    self:progress(),
  }
end

_G.Statusline = setmetatable(M, {
  __call = function(self, mode)
    return self["set_" .. mode](self)
  end,
})

local status_id = api.nvim_create_augroup("Statusline", {clear = true})
local hide_status_id = api.nvim_create_augroup("HideStatusline", {clear = true})

api.nvim_create_autocmd({"WinEnter", "BufEnter"}, {
  group   = status_id,
  pattern = "*",
  command = "setlocal statusline=%!v:lua.Statusline('active')"
})
api.nvim_create_autocmd({"WinLeave" , "BufLeave"}, {
  group   = status_id,
  pattern = "*",
  command = "setlocal statusline=%!v:lua.Statusline('inactive')"
})
api.nvim_create_autocmd({"BufWinEnter", "BufFilePost"}, {
  group    = status_id,
  callback = function()
    -- Skip floating windows
    -- Idea taken from https://github.com/itchyny/lightline.vim/pull/552
    if api.nvim_win_get_config(0).relative ~= '' then
      return
    end

    local winbar_filetype_exclude = {
      "diff",
      "fugitive",
      "git",
      "help",
      "packer",
      "qf"
    }

    if vim.tbl_contains(winbar_filetype_exclude, vim.bo.filetype) then
      vim.opt_local.winbar = nil
      return
    end

    vim.opt_local.winbar = string.format(" %s%s", "%#WinbarFile#", '%f%m%r')
  end,
})
api.nvim_create_autocmd({"TermOpen", "TermEnter"}, {
  group   = hide_status_id,
  pattern = "*",
  command = "set laststatus=0 noshowmode noruler"
})
api.nvim_create_autocmd({"TermLeave"}, {
  group   =  hide_status_id,
  pattern = "*",
  command = "set laststatus=2 showmode ruler"
})
