-- Copyright (c) 2020-2021 shadmansaleh
-- MIT license, see LICENSE for more details.

local helpers = require('tests.helpers')

local eq = assert.are.same
local neq = assert.are_not.same
local assert_component = helpers.assert_component
local build_component_opts = helpers.build_component_opts
local stub = require('luassert.stub')

describe('Component:', function()
  it('can select separators', function()
    local opts = build_component_opts()
    local comp = require('lualine.components.special.function_component')(opts)
    -- correct for lualine_c
    eq('', comp.options.separator)
    local opts2 = build_component_opts { self = { section = 'lualine_y' } }
    local comp2 = require('lualine.components.special.function_component')(opts2)
    -- correct for lualine_u
    eq('', comp2.options.separator)
  end)

  it('can provide unique identifier', function()
    local opts1 = build_component_opts()
    local comp1 = require('lualine.components.special.function_component')(opts1)
    local opts2 = build_component_opts()
    local comp2 = require('lualine.components.special.function_component')(opts2)
    neq(comp1.component_no, comp2.component_no)
  end)

  it('create option highlights', function()
    local color = { fg = '#224532', bg = '#892345' }
    local opts1 = build_component_opts { color = color }
    local hl = require('lualine.highlight')
    stub(hl, 'create_component_highlight_group')
    hl.create_component_highlight_group.returns('MyCompHl')
    local comp1 = require('lualine.components.special.function_component')(opts1)
    eq('MyCompHl', comp1.options.color_highlight)
    -- color highlight wan't in options when create_comp_hl was
    -- called so remove it before assert
    comp1.options.color_highlight = nil
    assert.stub(hl.create_component_highlight_group).was_called_with(color, comp1.options.component_name, comp1.options)
    hl.create_component_highlight_group:revert()
    color = 'MyHl'
    local opts2 = build_component_opts { color = color }
    stub(hl, 'create_component_highlight_group')
    hl.create_component_highlight_group.returns('MyCompLinkedHl')
    local comp2 = require('lualine.components.special.function_component')(opts2)
    eq('MyCompLinkedHl', comp2.options.color_highlight)
    -- color highlight wan't in options when create_comp_hl was
    -- called so remove it before assert
    comp2.options.color_highlight = nil
    assert.stub(hl.create_component_highlight_group).was_called_with(color, comp2.options.component_name, comp2.options)
    hl.create_component_highlight_group:revert()
  end)

  it('can draw', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component(nil, opts, 'test')
  end)

  it('can apply separators', function()
    local opts = build_component_opts { padding = 0 }
    assert_component(nil, opts, 'test')
  end)

  it('can apply default highlight', function()
    local opts = build_component_opts { padding = 0, hl = '%#My_highlight#' }
    assert_component(nil, opts, '%#My_highlight#test')
    opts = build_component_opts {
      function()
        return '%#Custom_hl#test'
      end,
      padding = 0,
      hl = '%#My_highlight#',
    }
    assert_component(nil, opts, '%#Custom_hl#test%#My_highlight#')
    opts = build_component_opts {
      function()
        return 'in middle%#Custom_hl#test'
      end,
      padding = 0,
      hl = '%#My_highlight#',
    }
    assert_component(nil, opts, '%#My_highlight#in middle%#Custom_hl#test%#My_highlight#')
  end)

  describe('Global options:', function()
    it('left_padding', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = { left = 5 },
      }
      assert_component(nil, opts, '     test')
    end)

    it('right_padding', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = { right = 5 },
      }
      assert_component(nil, opts, 'test     ')
    end)

    it('padding', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 5,
      }
      assert_component(nil, opts, '     test     ')
    end)

    it('icon', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        icon = '0',
      }
      assert_component(nil, opts, '0 test')
    end)

    it('icons_enabled', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        icons_enabled = true,
        icon = '0',
      }
      assert_component(nil, opts, '0 test')
      local opts2 = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        icons_enabled = false,
        icon = '0',
      }
      assert_component(nil, opts2, 'test')
    end)

    it('separator', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        separator = '|',
      }
      assert_component(nil, opts, 'test|')
    end)

    it('fmt', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        fmt = function(data)
          return data:sub(1, 1):upper() .. data:sub(2, #data)
        end,
      }
      assert_component(nil, opts, 'Test')
    end)

    it('cond', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        cond = function()
          return true
        end,
      }
      assert_component(nil, opts, 'test')
      local opts2 = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        cond = function()
          return false
        end,
      }
      assert_component(nil, opts2, '')
    end)

    it('color', function()
      local opts = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        color = 'MyHl',
      }
      local comp = require('lualine.components.special.function_component')(opts)
      local custom_link_hl_name = 'lualine_' .. comp.options.component_name .. '_no_mode'
      eq('%#' .. custom_link_hl_name .. '#test', comp:draw(opts.hl))
      local opts2 = build_component_opts {
        component_separators = { left = '', right = '' },
        padding = 0,
        color = { bg = '#230055', fg = '#223344' },
      }
      local hl = require('lualine.highlight')
      stub(hl, 'component_format_highlight')
      hl.component_format_highlight.returns('%#MyCompHl#')
      local comp2 = require('lualine.components.special.function_component')(opts2)
      assert_component(nil, opts2, '%#MyCompHl#test')
      assert.stub(hl.component_format_highlight).was_called_with(comp2.options.color_highlight)
      hl.component_format_highlight:revert()
    end)
  end)
end)

describe('Encoding component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local tmp_path = 'tmp.txt'
    local tmp_fp = io.open(tmp_path, 'w')
    tmp_fp:write('test file')
    tmp_fp:close()
    vim.cmd('e ' .. tmp_path)
    assert_component('encoding', opts, 'utf-8')
    vim.cmd('bd!')
    os.remove(tmp_path)
  end)
end)

describe('Fileformat component', function()
  it('works with icons', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local fmt = vim.bo.fileformat
    vim.bo.fileformat = 'unix'
    assert_component('fileformat', opts, '')
    vim.bo.fileformat = 'dos'
    assert_component('fileformat', opts, '')
    vim.bo.fileformat = 'mac'
    assert_component('fileformat', opts, '')
    vim.bo.fileformat = fmt
  end)
  it('works without icons', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      icons_enabled = false,
    }
    assert_component('fileformat', opts, vim.bo.fileformat)
  end)
end)

describe('Filetype component', function()
  local filetype

  before_each(function()
    filetype = vim.bo.filetype
    vim.bo.filetype = 'lua'
  end)

  after_each(function()
    vim.bo.filetype = filetype
  end)

  it('does not add icon when library unavailable', function()
    local old_require = _G.require
    function _G.require(...)
      if select(1, ...) == 'nvim-web-devicons' then
        error('Test case not suppose to have web-dev-icon 👀')
      end
      return old_require(...)
    end
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('filetype', opts, 'lua')
    _G.require = old_require
  end)

  it('colors nvim-web-devicons icons', function()
    package.loaded['nvim-web-devicons'] = {
      get_icon = function()
        return '*', 'test_highlight_group'
      end,
    }

    local hl = require('lualine.highlight')
    local utils = require('lualine.utils.utils')
    stub(hl, 'create_component_highlight_group')
    stub(utils, 'extract_highlight_colors')
    hl.create_component_highlight_group.returns('MyCompHl')
    utils.extract_highlight_colors.returns('#000')

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      colored = true,
      icon_only = false,
    }
    assert_component('filetype', opts, '%#MyCompHl_normal#*%#lualine_c_normal# lua')
    assert.stub(utils.extract_highlight_colors).was_called_with('test_highlight_group', 'fg')
    assert.stub(hl.create_component_highlight_group).was_called_with({ fg = '#000' }, 'test_highlight_group', opts)
    hl.create_component_highlight_group:revert()
    utils.extract_highlight_colors:revert()
    package.loaded['nvim-web-devicons'] = nil
  end)

  it("Doesn't color when colored is false", function()
    package.loaded['nvim-web-devicons'] = {
      get_icon = function()
        return '*', 'test_highlight_group'
      end,
    }
    local hl = require('lualine.highlight')
    local utils = require('lualine.utils.utils')
    stub(hl, 'create_component_highlight_group')
    stub(utils, 'extract_highlight_colors')
    hl.create_component_highlight_group.returns('MyCompHl')
    utils.extract_highlight_colors.returns('#000')
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      colored = false,
    }
    assert_component('filetype', opts, '* lua')
    hl.create_component_highlight_group:revert()
    utils.extract_highlight_colors:revert()
    package.loaded['nvim-web-devicons'] = nil
  end)

  it('displays only icon when icon_only is true', function()
    package.loaded['nvim-web-devicons'] = {
      get_icon = function()
        return '*', 'test_highlight_group'
      end,
    }

    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      colored = false,
      icon_only = true,
    }
    assert_component('filetype', opts, '*')
    package.loaded['nvim-web-devicons'] = nil
  end)
end)

describe('Hostname component', function()
  it('works', function()
    stub(vim.loop, 'os_gethostname')
    vim.loop.os_gethostname.returns('localhost')
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('hostname', opts, 'localhost')
    vim.loop.os_gethostname:revert()
  end)
end)

describe('Location component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('location', opts, '%3l:%-2v')
  end)
end)

describe('Progress component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('progress', opts, '%3p%%')
  end)
end)

describe('Mode component', function()
  it('works', function()
    stub(vim.api, 'nvim_get_mode')
    vim.api.nvim_get_mode.returns { mode = 'n', blocking = false }
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    assert_component('mode', opts, 'NORMAL')
    vim.api.nvim_get_mode:revert()
  end)
end)

describe('FileSize component', function()
  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local fname = 'test-file.txt'
    local f = io.open(fname, 'w')
    f:write(string.rep('........................................\n', 200))
    f:close()
    vim.cmd(':edit ' .. fname)
    assert_component('filesize', opts, '8.0k')
    vim.cmd(':bdelete!')
    os.remove(fname)
  end)
end)

describe('Filename component', function()
  local function shorten_path(path, target)
    target = target and target or 40
    local sep = package.config:sub(1, 1)
    local winwidth = vim.fn.winwidth(0)
    local segments = select(2, string.gsub(path, sep, ''))
    for _ = 0, segments do
      if winwidth <= 84 or #path > winwidth - target then
        path = path:gsub(string.format('([^%s])[^%s]+%%%s', sep, sep, sep), '%1' .. sep, 1)
      end
    end
    return path
  end

  it('works', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 0,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, 'test-file.txt')
    vim.cmd(':bdelete!')
  end)

  it('can show file_status', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = true,
      path = 0,
    }
    vim.cmd(':e test-file.txt')
    vim.bo.modified = false
    assert_component('filename', opts, 'test-file.txt')
    vim.bo.modified = true
    assert_component('filename', opts, 'test-file.txt[+]')
    vim.bo.modified = false
    vim.bo.ro = true
    assert_component('filename', opts, 'test-file.txt[-]')
    vim.cmd(':bdelete!')
  end)

  it('can show relative path', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 1,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, shorten_path(vim.fn.expand('%:~:.')))
    vim.cmd(':bdelete!')
  end)

  it('can show full path', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
      file_status = false,
      path = 2,
    }
    vim.cmd(':e test-file.txt')
    assert_component('filename', opts, shorten_path(vim.fn.expand('%:p')))
    vim.cmd(':bdelete!')
  end)
end)

describe('vim option & variable component', function()
  local opts = build_component_opts {
    component_separators = { left = '', right = '' },
    padding = 0,
  }

  local function assert_vim_var_component(name, options, result)
    options[1] = name
    assert_component('special.vim_var_component', options, result)
    opts[1] = nil
  end
  it('works with variable', function()
    assert_vim_var_component('g:gvar', opts, '')
    vim.g.gvar = 'var1'
    assert_vim_var_component('g:gvar', opts, 'var1')
    vim.g.gvar = 'var2'
    assert_vim_var_component('g:gvar', opts, 'var2')
    vim.b.gvar = 'bvar1'
    assert_vim_var_component('b:gvar', opts, 'bvar1')
    vim.w.gvar = 'wvar1'
    assert_vim_var_component('w:gvar', opts, 'wvar1')
  end)
  it('can index dictionaries', function()
    vim.g.gvar = { a = { b = 'var-value' } }
    assert_vim_var_component('g:gvar.a.b', opts, 'var-value')
  end)
  it('works with options', function()
    local old_number = vim.wo.number
    vim.wo.number = false
    assert_vim_var_component('wo:number', opts, 'false')
    vim.wo.number = old_number
    local old_tw = vim.go.tw
    vim.go.tw = 80
    assert_vim_var_component('go:tw', opts, '80')
    vim.go.tw = old_tw
  end)
end)

describe('Vim option & variable component', function()
  local opts = build_component_opts {
    component_separators = { left = '', right = '' },
    padding = 0,
  }

  local function assert_vim_var_component(name, options, result)
    options[1] = name
    assert_component('special.eval_func_component', options, result)
    opts[1] = nil
  end

  it('works with vim function', function()
    vim.cmd([[
      func! TestFunction() abort
        return "TestVimFunction"
      endf
    ]])
    assert_vim_var_component('TestFunction', opts, 'TestVimFunction')
    vim.cmd('delfunction TestFunction')
  end)

  it('works with lua expression', function()
    _G.TestFunction = function()
      return 'TestLuaFunction'
    end
    assert_vim_var_component('TestFunction()', opts, 'TestLuaFunction')
    _G.TestFunction = nil
  end)
end)

describe('Branch component', function()
  local tmpdir
  local file
  local git = function(...)
    return vim.fn.system('git -C ' .. tmpdir .. ' ' .. string.format(...))
  end
  local assert_comp_ins = helpers.assert_component_instence

  before_each(function()
    tmpdir = os.tmpname()
    os.remove(tmpdir)
    file = tmpdir .. '/test.txt'
    vim.fn.mkdir(tmpdir, 'p')
    git('init -b test_branch')
    vim.cmd([[aug lualine
    au!
    aug END
  ]])
  end)

  after_each(function()
    os.remove(tmpdir)
  end)

  it('works with regular branches', function()
    local opts = build_component_opts {
      component_separators = { left = '', right = '' },
      padding = 0,
    }
    local branch_comp = helpers.init_component('branch', opts)
    vim.cmd('e ' .. file)
    assert_comp_ins(branch_comp, ' test_branch')
    git('checkout -b test_branch2')
    vim.cmd('e k')
    vim.cmd('bd')
    vim.cmd('e ' .. file)
    opts.icons_enabled = false
    assert_comp_ins(branch_comp, 'test_branch2')
  end)

  -- TODO: Figure out why this test fails in CI
  -- it('works in detached head mode', function()
  --   local opts = build_component_opts {
  --     component_separators = { left = '', right = '' },
  --     icons_enabled = false,
  --     padding = 0,
  --   }
  --   git 'checkout -b test_branch2'
  --   git 'commit --allow-empty -m "test commit1"'
  --   git 'commit --allow-empty -m "test commit2"'
  --   git 'commit --allow-empty -m "test commit3"'
  --   git('checkout HEAD~1')
  --   vim.cmd('e ' .. file)
  --   local rev = git('rev-parse --short=6 HEAD'):sub(1, 6)
  --   assert_component('branch', opts, rev)
  -- end)
end)