--=============================================================================
-- a.lua --- alternate plugin for SpaceVim
-- Copyright (c) 2016-2019 Wang Shidong & Contributors
-- Author: Wang Shidong < wsdjeg@outlook.com >
-- URL: https://spacevim.org
-- License: GPLv3
--=============================================================================


local M = {}
local sp = require('spacevim')
local cmp = require('spacevim.api').import('vim.compatible')
local cmd = require('spacevim').cmd
local sp_file = require('spacevim.api').import('file')
local sp_opt = require('spacevim.opt')
local sp_json = require('spacevim.api').import('data.json')
local logger = require('spacevim.logger').derive('a.vim')
local fn = vim.fn or require('spacevim').fn

local alternate_conf = {}
alternate_conf['_'] = '.project_alt.json'

local cache_path = sp_file.unify_path(sp_opt.data_dir, ':p') .. 'SpaceVim/a.json'


local project_config = {}

local function cache()
    logger.debug('write cache into file:' .. cache_path)
    local ok, errors = pcall(fn.writefile, {sp_json.json_encode(project_config)}, cache_path)
    if not ok then
        logger.debug('cache failed!')
    else
        logger.debug('cache succeeded!')
    end
end

local function get_type_path(a, f, b)
    logger.debug('run get_type_path function')
    logger.debug(fn.string(a))
    logger.debug(f)
    logger.debug(b)
    local begin_len = fn.strlen(a[1])
    local end_len = fn.strlen(a[2])
    local r = fn.substitute(b, '{}', string.sub(f, begin_len + 1,  (end_len+1) * -1), 'g')
    logger.debug(r)
    return r
end


local function load_cache()
    logger.info('Try to load alt cache from:' .. cache_path)
    local cache_context = fn.join(fn.readfile(cache_path, ''), '')
    if cache_context ~= '' then
        project_config = sp_json.json_decode(cache_context)
    end
end

function M.set_config_name(path, name)
    alternate_conf[path] = name
end

function M.alt(request_parse, ...)
    local argvs=...
    local alt_type = 'alternate'
    if argvs ~= nil then
        alt_type = argvs[1] or alt_type
    end
    local alt = nil
    if fn.exists('b:alternate_file_config') ~= 1 then
        local conf_file_path = M.getConfigPath()
        local file = sp_file.unify_path(fn.bufname('%'), ':.')
        alt = M.get_alt(file, conf_file_path, request_parse, alt_type)
    end
    logger.debug('alt is:' .. alt)
    if alt ~= nil and alt ~= '' then
        cmd('e ' .. alt)
    else
        print('failed to find alternate file!')
    end
end

local function get_project_config(conf_file)
    logger.info('read context from:' .. conf_file)
    local context = fn.join(fn.readfile(conf_file), "\n")
    local conf = sp_json.json_decode(context)
    logger.debug(context)
    if type(conf) ~= type({}) then
        conf = {}
    end
    local root = sp_file.unify_path(conf_file, ':p:h')
    return {
        ['root'] = root,
        ['config'] = conf
    }
end

-- we need to sort the keys in config
--

local function _keys(val)
    local new_keys = {}
    for k,v in pairs(val) do
        table.insert(new_keys, k)
    end
    return new_keys
end
local function _comp(a, b)
    if (string.match(a, '*') == '*' and string.match(b, '*') == '*') then
        return #a < #b
    elseif string.match(a, '*') == '*' then
        return true
    elseif string.match(b, '*') == '*' then
        return false
    else
        local _, al =  string.gsub(a, "/", "")
        local _, bl =  string.gsub(b, "/", "")
        return al < bl
    end

end

local function parse(alt_config_json)
    logger.info('Start to parse alternate file for:' .. alt_config_json.root)
    project_config[alt_config_json.root] = {}
    local keys = _keys(alt_config_json.config)
    table.sort(keys, _comp)
    for _, key in pairs(keys) do
        local searchpath = key
        if string.match(searchpath, '*') == '*' then
            searchpath = string.gsub(searchpath, '*', '**/*')
        end
        logger.debug('run globpath for: '.. searchpath)
        for _,file in pairs(cmp.globpath('.', searchpath)) do
            file = sp_file.unify_path(file, ':.')
            project_config[alt_config_json.root][file] = {}
            if alt_config_json.config[file] ~= nil then
                for alt_type, type_v in pairs(alt_config_json.config[file]) do
                    project_config[alt_config_json.root][file][alt_type] = type_v
                end
            else
                for a_type, _ in pairs(alt_config_json.config[key]) do
                    local begin_end = fn.split(key, '*')
                    if #begin_end == 2 then
                        project_config[alt_config_json.root][file][a_type] =
                        get_type_path(
                        begin_end,
                        file,
                        alt_config_json.config[key][a_type]
                        )
                    end
                end
            end
        end
    end
    logger.info('Paser done, try to cache alternate info')
    cache()
end

local function is_config_changed(conf_path)
    if fn.getftime(conf_path) > fn.getftime(cache_path) then
        logger.info('alt config file('
        .. conf_path
        .. ')')
        return 1
    end
end

function M.get_alt(file, conf_path, request_parse, a_type)
    logger.info('getting alt file for:' .. file)
    logger.info('  >   type: ' .. a_type)
    logger.info('  >  parse: ' .. request_parse)
    logger.info('  > config: ' .. conf_path)
    alt_config_json = get_project_config(conf_path)
    if project_config[alt_config_json.root] == nil
        and is_config_changed(conf_path) == 0
        and request_parse == 0 then
        load_cache()
        if project_config[alt_config_json.root] == nil
            or project_config[alt_config_json.root][file] == nil then
            parse(alt_config_json)
        end
    else
        parse(alt_config_json)
    end
    if project_config[alt_config_json.root] ~= nil
        and project_config[alt_config_json.root][file] ~= nil
        and project_config[alt_config_json.root][file][a_type] ~= nil then
        return project_config[alt_config_json.root][file][a_type]
    else
        return ''
    end
end

function M.getConfigPath()
    logger.debug('getConfigPath')
    local pwd = fn.getcwd()
    logger.debug(pwd)
    local p = alternate_conf['_']
    logger.debug(p)
    if alternate_conf[pwd] ~= nil then
        p = alternate_conf[pwd]
    end
    return sp_file.unify_path(p, ':p')
end

function M.complete(arglead, cmdline, cursorpos)
    local file = sp_file.unify_path(fn.bufname('%'), ':.')
    local conf_file_path = M.getConfigPath()
    local alt_config_json = get_project_config(conf_file_path)

    M.get_alt(file, conf_file_path, 0, '')
    local a = project_config[alt_config_json.root][file]
    if a ~= nil then
        return fn.join(fn.keys(a), "\n")
    else
        return ''
    end
end

return M