-- execute(dofile) this script at the end of
-- of 'DCS World\MissionEditor\modules\me_mission.lua'
-- base.dofile([[D:\dev\_VEAF\VEAF-Mission-Creation-Tools\src\scripts\veaf\dcsDataExport.lua]])

-------------------------------------------------------------------------------
-- settings
-------------------------------------------------------------------------------

--local export_path = [[c:\Users\dpier\Saved Games\DCS\Logs\ObjectDB\]]
local export_path = [[.\]]

-------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Logging
-------------------------------------------------------------------------------------------------------------------------------------------------------------
DcsDataExport = {}
DcsDataExport.loggers = {}
DcsDataExport.loggers.dict = {}

DcsDataExport.Logger =
{
    -- technical name
    name = nil,
    -- logging level
    level = nil,
}
DcsDataExport.Logger.__index = DcsDataExport.Logger

DcsDataExport.Logger.LEVEL = {
    ["error"]=1,
    ["warning"]=2,
    ["info"]=3,
    ["debug"]=4,
    ["trace"]=5,
}

function DcsDataExport.Logger:new(name, level)
    local self = setmetatable({}, DcsDataExport.Logger)
    self:setName(name)
    self:setLevel(level)
    return self
end

function DcsDataExport.Logger:setName(value)
    self.name = value
    return self
end

function DcsDataExport.Logger:getName()
    return self.name
end

function DcsDataExport.Logger:setLevel(value, force)
    if DcsDataExport.ForcedLogLevel then
        value = DcsDataExport.ForcedLogLevel
    end
    local level = value
    if type(level) == "string" then
        level = DcsDataExport.Logger.LEVEL[level:lower()]
    end
    if not level then 
        level = DcsDataExport.Logger.LEVEL["info"]
    end
    if DcsDataExport.BaseLogLevel and DcsDataExport.BaseLogLevel < level and not force then
        level = DcsDataExport.BaseLogLevel
    end
    self.level = level
    return self
end

function DcsDataExport.Logger:getLevel()
    return self.level
end

function DcsDataExport.Logger.splitText(text)
    local tbl = {}
    while text:len() > 4000 do
        local sub = text:sub(1, 4000)
        text = text:sub(4001)
        table.insert(tbl, sub)
    end
    table.insert(tbl, text)
    return tbl
end

function DcsDataExport.Logger.formatText(text, ...)
    if not text then 
        return "" 
    end
    if type(text) ~= 'string' then
        text = DcsDataExport.p(text)
    else
        if arg and #arg > 0 then
            text = text:format(unpack(arg))
        end            
    end
    local fName = nil
    local cLine = nil
    if debug then
        local dInfo = debug.getinfo(3)
        fName = dInfo.name
        cLine = dInfo.currentline
        -- local fsrc = dinfo.short_src
        --local fLine = dInfo.linedefined
    end
    if fName and cLine then
        return fName .. '|' .. cLine .. ': ' .. text
    elseif cLine then
        return cLine .. ': ' .. text
    else
        return ' ' .. text
    end
end

function DcsDataExport.Logger:print(level, text)
    local texts = DcsDataExport.Logger.splitText(text)
    local levelChar = 'E'
    if level == DcsDataExport.Logger.LEVEL["warning"] then
        levelChar = 'W'
    elseif level == DcsDataExport.Logger.LEVEL["info"] then
        levelChar = 'I'
    elseif level == DcsDataExport.Logger.LEVEL["debug"] then
        levelChar = 'D'
    elseif level == DcsDataExport.Logger.LEVEL["trace"] then
        levelChar = 'T'
    end
    for i = 1, #texts do
        if i == 1 then
            print(self.name .. '|' .. levelChar .. '|' .. texts[i])
        else
            print(texts[i])
        end
    end
end

function DcsDataExport.Logger:error(text, ...)
    if self.level >= 1 then
        text = DcsDataExport.Logger.formatText(text, unpack(arg))
        self:print(1, text)
    end
end

function DcsDataExport.Logger:warn(text, ...)
    if self.level >= 2 then
        text = DcsDataExport.Logger.formatText(text, unpack(arg))
        self:print(2, text)
    end
end

function DcsDataExport.Logger:info(text, ...)
    if self.level >= 3 then
        text = DcsDataExport.Logger.formatText(text, unpack(arg))
        self:print(3, text)
    end
end

function DcsDataExport.Logger:debug(text, ...)
    if self.level >= 4 then
        text = DcsDataExport.Logger.formatText(text, unpack(arg))
        self:print(4, text)
    end
end

function DcsDataExport.Logger:trace(text, ...)
    if self.level >= 5 then
        text = DcsDataExport.Logger.formatText(text, unpack(arg))
        self:print(5, text)
    end
end

function DcsDataExport.loggers.setBaseLevel(level) 
    DcsDataExport.BaseLogLevel = level
    -- reset all loggers level if lower than the base level
    for name, logger in pairs(DcsDataExport.loggers.dict) do
        logger:setLevel(logger:getLevel())
    end
end

function DcsDataExport.loggers.new(loggerId, level) 
    if not loggerId or #loggerId == 0 then
        return nil
    end
    local result = DcsDataExport.Logger:new(loggerId:upper(), level)
    DcsDataExport.loggers.dict[loggerId:lower()] = result
    return result
end

function DcsDataExport.loggers.get(loggerId) 
    local result = nil
    if loggerId and #loggerId > 0 then
        result = DcsDataExport.loggers.dict[loggerId:lower()]
    end
    if not result then 
        result = DcsDataExport.loggers.get("DcsDataExport")
    end
    return result
end

function DcsDataExport.p(obj, maxLevel, skip, serializeInLua)
    local skip = skip
    if skip and type(skip)=="table" then
        for _, value in ipairs(skip) do
            skip[value]=true
        end
    end
    return DcsDataExport._p(nil, obj, maxLevel, 0, skip, serializeInLua)
end

function DcsDataExport._p(objKey, objValue, maxLevel, level, skip, serializeInLua)
    local function getSerializationForSingle(value)
        if value == nil then
            if serializeInLua then
                return "nil"
            else
                return getSerializationForSingle("[nil]")
            end
        elseif serializeInLua and type(value)=="string" then
            return "\"" .. string.gsub(value, [["]], [[\"]]) .. "\""
        else
            return tostring(value)
        end
    end

    local function alphanumsort(o)
        local function conv(s)
           local res, dot = "", ""
           for n, m, c in tostring(s):gmatch"(0*(%d*))(.?)" do
              if n == "" then
                 dot, c = "", dot..c
              else
                 res = res..(dot == "" and ("%03d%s"):format(#m, m)
                                       or "."..n)
                 dot, c = c:match"(%.?)(.*)"
              end
              res = res..c:gsub(".", "\0%0")
           end
           return res
        end
        table.sort(o,
           function (a, b)
              local ca, cb = conv(a), conv(b)
              return ca < cb or ca == cb and a < b
           end)
        return o
     end
     
    local MAX_LEVEL = 20
    if level == nil then level = 0 end
    if level > MAX_LEVEL then 
        logError("max depth reached in p : "..tostring(MAX_LEVEL))
        return ""
    end

    local text = ""
    if (type(objValue) == "table") then
        if 
        (maxLevel and level >= maxLevel) 
        or
        (skip and skip[objKey])
        then
            text = getSerializationForSingle("[table]")
        else
            if level > 0 then
                text = text .. "\n"
            end
            local keys = {}
            local realKeys = {}
            for realKey, _ in pairs(objValue) do
                local key = tostring(realKey)
                realKeys[key] = realKey
                table.insert(keys, key)
            end
            keys = alphanumsort(keys)
        
            local firstElement = true
            for _, key in pairs(keys) do
                local padding = ""
                for i=0, level do
                    if serializeInLua then
                        padding = padding .. "    "
                    else
                        padding = padding .. "---|"
                    end
                end
                local value = objValue[realKeys[key]]
                local carriageReturn = "\n"
                local result, wasTable = DcsDataExport._p(key, value, maxLevel, level+1, skip, serializeInLua)
                if wasTable then
                    carriageReturn = ""
                end
                if serializeInLua then
                    text = text .. padding
                    if not firstElement then
                        text = text .. ","
                    end
                    local realKey = realKeys[key]
                    if type(realKey) == "number" then
                        text = text .. "[" .. key .."] = "
                    else
                        text = text .. "[\"" .. key .."\"] = "
                    end
                    if wasTable then
                        text = text .. "{" .. result .. carriageReturn
                        text = text .. padding .. "}\n"
                    else
                        text = text .. result .. carriageReturn
                    end
                else
                    text = text .. padding .. ".".. key.."=".. result .. carriageReturn
                end
                firstElement = false
            end
            return text, true
        end
    elseif (type(objValue) == "function") then
        text = getSerializationForSingle("[function]")
    elseif (type(objValue) == "boolean") then
        if objValue == true then 
            text = getSerializationForSingle("[true]")
        else
            text = getSerializationForSingle("[false]")
        end
    else
        text = getSerializationForSingle(objValue)
    end
    return text, false
end


--DcsDataExport.loggers.setBaseLevel(DcsDataExport.Logger.LEVEL["trace"])
DcsDataExport.Id = "DCSEXPORT"
DcsDataExport.loggers.new(DcsDataExport.Id, "trace")

-------------------------------------------------------------------------------
-- helper functions
-------------------------------------------------------------------------------

local function writeln(file, text)
    file:write(text.."\r\n")
end

local function safe_name(name)
    local safeName = name
    safeName = string.gsub(safeName, "[-()/., *'+`#%[%]]", "_")
    safeName = string.gsub(safeName, "_*$", "")  -- strip __ from end
    safeName = string.gsub(safeName, "^([0-9])", "_%1")
    if safeName == 'None' then
        safeName = 'None_'
    end
    return safeName
end

local function has_value(tab, val)
    for index, value in ipairs(tab) do
        if value == val then
            return true
        end
    end

    return false
end

--- Serializes the give variable to a string. Stolen from MisT
-- borrowed from slmod
-- @param var variable to serialize
-- @treturn string variable serialized to string
function DcsDataExport.basicSerialize(var)
    if var == nil then
        return "\"\""
    else
        if ((type(var) == 'number') or
                (type(var) == 'boolean') or
                (type(var) == 'function') or
                (type(var) == 'table') or
                (type(var) == 'userdata') ) then
            return tostring(var)
        elseif type(var) == 'string' then
            var = string.format('%q', var)
            return var
        end
    end
end

--- Serialize value
-- borrowed from slmod (serialize_slmod)
-- @param name
-- @param value value to serialize
-- @param level
function DcsDataExport.serialize(name, value, level)
	--Based on ED's serialize_simple2
	local function basicSerialize(o)
		if type(o) == "number" then
			return tostring(o)
		elseif type(o) == "boolean" then
			return tostring(o)
		else -- assume it is a string
			return DcsDataExport.basicSerialize(o)
		end
	end

	local function serializeToTbl(name, value, level)
		local var_str_tbl = {}
		if level == nil then
			level = ""
		end
		if level ~= "" then 
			level = level.."" 
		end
		table.insert(var_str_tbl, level .. name .. " = ")

		if type(value) == "number" or type(value) == "string" or type(value) == "boolean" then
			table.insert(var_str_tbl, basicSerialize(value) ..	",\n")
		elseif type(value) == "table" then
			table.insert(var_str_tbl, "\n"..level.."{\n")

			for k,v in pairs(value) do -- serialize its fields
				local key
				if type(k) == "number" then
					key = string.format("[%s]", k)
				else
					key = string.format("[%q]", k)
				end
				table.insert(var_str_tbl, DcsDataExport.serialize(key, v, level.."	"))

			end
			if level == "" then
				table.insert(var_str_tbl, level.."} -- end of "..name.."\n")

			else
				table.insert(var_str_tbl, level.."}, -- end of "..name.."\n")

			end
		else
			log:error('Cannot serialize a $1', type(value))
		end
		return var_str_tbl
	end

	local t_str = serializeToTbl(name, value, level)

	return table.concat(t_str)
end

local function _sortUnits(u1,u2)
    if u1 and u1.category and u2 and u2.category then
        if string.lower(u1.category) == string.lower(u2.category) then 
            if u1 and u1.type and u2 and u2.type then
                return string.lower(u1.type) < string.lower(u2.type)
            elseif u1 and u1.name and u2 and u2.name then
                return string.lower(u1.name) < string.lower(u2.name)
            else
                return string.lower(u1) < string.lower(u2)
            end
        else
            return string.lower(u1.category) < string.lower(u2.category)
        end
    else
        return string.lower(u1) < string.lower(u2)
    end
end

local function browseUnits(out, database, defaultCategory, fullDcsUnit, exportAllAttributes)
    for _, unit in pairs(database) do
        if fullDcsUnit then
            out[unit["type"]] = unit
        else
            out[unit["type"]] = {}
            local u = out[unit["type"]]
            u.category = unit["category"]
            if not u.category then 
                u.category = defaultCategory
            end
            u.type = unit["type"]
            u.name = unit["Name"]
            u.description = unit["DisplayName"]
            u.aliases = {}
            if unit["Aliases"] then 
                for _, alias in pairs(unit["Aliases"]) do
                    table.insert(u.aliases, alias)
                end
            end
            if unit["attribute"] then
                if exportAllAttributes then
                    u.attribute = {}
                    for _, attribute in pairs(unit["attribute"]) do
                        u.attribute[attribute] = true
                    end
                end
                for _, attr in pairs(unit["attribute"]) do
                    if type(attr) == "string" then
                        if attr:lower() == "ships" then u.naval = true end
                        if attr:lower() == "air" then u.air = true end
                        if attr:lower() == "infantry" then u.infantry = true end
                        if attr:lower() == "vehicles" then u.vehicle = true end
                    end
                end
            end
            if unit["isPutToWater"] and type(unit["isPutToWater"]) == 'boolean' then
                u.isPutToWater = unit["isPutToWater"]
            end
            if u.category == "Cargo" then
                u.defaultMass = 0
                if u.category == "Cargo" and unit["mass"] and type(unit["mass"]) == 'number' then
                    u.defaultMass = unit["mass"]
                end
                u.desc = {}
                if u.category == "Cargo" and unit["maxMass"] and unit["minMass"] and type(unit["maxMass"]) == 'number' and type(unit["minMass"]) == 'number' then
                    u.desc.minMass = unit["minMass"]
                    u.desc.maxMass = unit["maxMass"]
                end
            end

        end
    end
end

-- export all units as a lua file
local file = io.open(export_path.."db.Units.lua", "w")
writeln(file, "db={\n    [\"Units\"] = {" .. DcsDataExport.p(db.Units, nil, nil, true).."}\n}")
if file then file:close() end

local units = {}
local fullDcsUnit = false
local exportAllAttributes = true
browseUnits(units, db.Units.Animals.Animal, "Animal", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Cargos.Cargo, "Cargo", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Cars.Car, "Vehicle", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Effects.Effect, "Effect", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Fortifications.Fortification, "Fortification", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.GrassAirfields.GrassAirfield, "GrassAirfield", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.GroundObjects.GroundObject, "GroundObject", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Helicopters.Helicopter, "Helicopter", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Heliports.Heliport, "Heliport", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Personnel.Personnel, "Personnel", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Planes.Plane, "Plane", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Ships.Ship, "Ship", fullDcsUnit, exportAllAttributes)
browseUnits(units, db.Units.Warehouses.Warehouse, "Warehouse", fullDcsUnit, exportAllAttributes)
local values = {}
if fullDcsUnit then
    values = units    
else
    for _,v in pairs(units) do
        table.insert(values,v)
    end
    table.sort(values, _sortUnits)
end
file = io.open(export_path.."dcsUnits.lua", "w")
writeln(file, DcsDataExport.serialize("units", values))
if file then file:close() end