--!A cross-platform build utility based on Lua
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
--     http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific tool governing permissions and
-- limitations under the License.
-- 
-- Copyright (C) 2015-2020, TBOOX Open Source Group.
--
-- @author      ruki
-- @file        tool.lua
--

-- define module
local tool      = tool or {}
local _instance = _instance or {}

-- load modules
local os            = require("base/os")
local path          = require("base/path")
local utils         = require("base/utils")
local table         = require("base/table")
local string        = require("base/string")
local config        = require("project/config")
local sandbox       = require("sandbox/sandbox")
local platform      = require("platform/platform")
local import        = require("sandbox/modules/import")

-- new an instance
function _instance.new(kind, name, program)

    -- import "core.tools.xxx"
    local toolclass = nil
    if os.isfile(path.join(os.programdir(), "modules", "core", "tools", name .. ".lua")) then
        toolclass = import("core.tools." .. name)
    end

    -- not found?
    if not toolclass then
        return nil, string.format("cannot import \"core.tool.%s\" module!", name)
    end

    -- new an instance
    local instance = table.inherit(_instance, toolclass)

    -- save name, kind and program
    instance._NAME    = name
    instance._KIND    = kind
    instance._PROGRAM = program
    instance._INFO    = {}

    -- init instance
    if instance.init then
        local ok, errors = sandbox.load(instance.init, instance)
        if not ok then
            return nil, errors
        end
    end

    -- ok
    return instance
end

-- get the tool name
function _instance:name()
    return self._NAME
end

-- get the tool kind
function _instance:kind()
    return self._KIND
end

-- get the tool program
function _instance:program()
    return self._PROGRAM
end

-- set the value to the platform info
function _instance:set(name, ...)
    self._INFO[name] = table.unwrap({...})
end

-- add the value to the platform info
function _instance:add(name, ...)
    local info = table.wrap(self._INFO[name])
    self._INFO[name] = table.unwrap(table.join(info, ...))
end

-- get the platform configure
function _instance:get(name)
    if self._super_get then
        local value = self:_super_get(name)
        if value ~= nil then
            return value
        end
    end
    return self._INFO[name]
end

-- has the given flag?
function _instance:has_flags(flags, flagkind, opt)

    -- init options
    opt = opt or {}
    opt.program = opt.program or self:program()
    opt.toolkind = opt.toolkind or self:kind()
    opt.flagkind = opt.flagkind or flagkind

    -- get system flags
    opt.sysflags = opt.sysflags or self:get(self:kind() .. 'flags')
    if not opt.sysflags and flagkind then
        opt.sysflags = self:get(flagkind)
    end

    -- import has_flags()
    self._has_flags = self._has_flags or import("lib.detect.has_flags", {anonymous = true})

    -- has flags?
    return self._has_flags(self:name(), flags, opt)
end

-- load the given tool from the given kind
--
-- @param kind      the tool kind e.g. cc, cxx, mm, mxx, as, ar, ld, sh, ..
-- @param program   the tool program, e.g. /xxx/arm-linux-gcc, gcc@mipscc.exe
--
function tool.load(kind, program)

    -- init key
    local key = kind .. (program or "") .. (config.get("arch") or os.arch())

    -- get it directly from cache dirst
    tool._TOOLS = tool._TOOLS or {}
    if tool._TOOLS[key] then
        return tool._TOOLS[key]
    end

    -- contain toolname? parse it, e.g. 'gcc@xxxx.exe'
    local toolname = nil
    if program then
        local pos = program:find('@', 1, true)
        if pos then
            toolname = program:sub(1, pos - 1)
            program = program:sub(pos + 1)
        end
    end

    -- get the tool program and name
    if not program then
        program, toolname = platform.tool(kind)
    end
    if not program then
        return nil, string.format("cannot get program for %s", kind)
    end

    -- import find_toolname()
    tool._find_toolname = tool._find_toolname or import("lib.detect.find_toolname")

    -- get the tool name from the program
    local ok, name_or_errors = sandbox.load(tool._find_toolname, toolname or program, {program = program})
    if not ok then
        return nil, name_or_errors
    end

    -- get name
    local name = name_or_errors
    if not name then
        return nil, string.format("cannot find known tool script for %s", toolname or program)
    end

    -- new an instance
    local instance, errors = _instance.new(kind, name, program)
    if not instance then
        return nil, errors
    end

    -- save instance to the cache
    tool._TOOLS[key] = instance

    -- ok
    return instance
end

-- return module
return tool
