-- repair possible if item condition is less than this
local repair_treshold        = 0.99

-- only items that cost at least this (or "unique" items) can be repaired
-- NOTE: this is the cost as configured in the item's .ltx file, not the trader's price for same item
local min_item_cost          = 0

-- repair not possible if item condition is less than this
-- for modified items set lower treshold as these are harder to replace
local fubar_treshold         = 0.33

-- default profit used if trader does not have this item
local default_profit         = 2

-- fraction of (item cost * profit) added to repair cost
local base_cost_factor       = 0.3


-- local data set up when the actor asks for repairs

-- profit values by slot, multiply by item cost to guestimate the trader's item price
local profit_by_slot = {}

-- discounts by slot number in percent
local discount_by_slot = {}
local giving_discount = false

-- gifts given by the npc when repairing, based on reputation
local non_ammo_gift_items = {"grenade_f1", "medkit", "medkit_army", "medkit_scientic", "antirad"}
local gift_item_by_slot = {}


-- random gift, set up when an item is repaired by the trader
local gift_item_to_give



-- debug help functions, remove when not needed anymore

local function show_hmsg(fmt, ...)
    local msgtext = string.format(fmt, ...)
    db.actor:give_game_news(msgtext, "ui\\ui_icons_task", Frect():set(0, 250, 50, 50), 0, 0)
end


local function show_npc_info(actor, npc)
    local npc_cc = npc:character_community()
    local st     = db.storage[npc:id()]
    local str = utils.cfg_get_string(st.ini, st.section_logic, "trade", nil, false, "", "misc\\trade_generic.ltx")
    show_hmsg("NPC info:\\n,name:%s\\ntrade:%s\\ncommunity:%s\\ngoodwill:%d\\ncommunity goodwill:%d",
              npc.name(),
              str,
              npc_cc,
              npc:goodwill(actor),
              relation_registry.community_goodwill(npc_cc, actor:id()))
end


-- these are the slots for repairable items
local equipment_slots = {1, 2, 6}


-- functions NOT directly referenced by dialog xml

local function item_is_damaged(actor, slot)
    local item = actor:item_in_slot(slot)
    return item ~= nil and item:condition() <= repair_treshold
end


local function item_is_worth_repairing(actor, slot)
    local item = actor:item_in_slot(slot)
    if item ~= nil then
        -- if cost is sufficiently high or the item is a unique item
        if item:cost() >= min_item_cost or string.find(item:section(), "_m%d$") ~= nil then
            return true
        end
    end
    return false
end


local function item_is_repairable(actor, slot)
    local item = actor:item_in_slot(slot)
    if item ~= nil and item:condition() <= repair_treshold then
        -- if not too damaged regular item or a unique item
        if item:condition() >= fubar_treshold or string.find(item:section(), "_m%d$") ~= nil then
            return true
        end
    end
    return false
end


-- setup profit factors for items in the player's slots
-- if an entry for an item can be found in the trader's trade config file for current sell condition
-- then the profit is taken from that entry. Otherwise the default profit value is used.

local function setup_profit(actor, npc)
    local st = db.storage[npc:id()]
    local str = utils.cfg_get_string(st.ini, st.section_logic, "trade", nil, true, "")
    local ini = ini_file(str)
    str = utils.cfg_get_string(ini, "trader", "sell_condition", nil, true, "")
    local condlst = xr_logic.parse_condlist(npc, "trade_manager", "sell_condition", str)
    local sell_section = xr_logic.pick_section_from_condlist(actor, npc, condlst)

    for _, slot in ipairs(equipment_slots) do

        profit_by_slot[slot] = default_profit
        local item = actor:item_in_slot(slot)

        if item ~= nil then
            local item_section = item:section()

            -- see if this item is modified i.e. name ends with _m1 or _m2 etc.
            local mX_pos = string.find(item_section, "_m%d$")

            -- first try to lookup the sell markup for the exact item name
            str = utils.cfg_get_string(ini, sell_section, item_section, nil, false, "", "")

            -- if fails try to lookup the corresponding unmodified item
            if (str == nil or str == "") and mX_pos ~= nil then
                item_section = string.sub(item_section, 1, mX_pos - 1)
                str = utils.cfg_get_string(ini, sell_section, item_section, nil, false, "", "")
            end

            -- if found use the markup for profit
            if str ~= nil and str ~= "" then
                -- calculate median value of the numbers in the config line
                local i = 0
                local profit = 0
                for number in string.gmatch(str, "(%d+%.?%d*)") do
                    profit = profit + number
                    i = i + 1
                end
                profit_by_slot[slot] = profit / i
            end

        end
    end
end


-- setup discounts by slot based on relation to npc and its community
-- max discount is 50% or no profit
-- the discount is calculated in steps of 5%
-- precondition: setup_profit() must have been called

local function setup_discounts(actor, npc)
    -- don't give discounts less than this
    local discount_treshold = 10

    local npc_cc = npc:character_community()
    local goodwill = npc:goodwill(actor)
                   + relation_registry.community_goodwill(npc_cc, actor:id())

    discount_by_slot[1] = 0
    discount_by_slot[2] = 0
    discount_by_slot[6] = 0
    giving_discount = false
    
    if goodwill > 1000 then
        goodwill = 1000
    elseif goodwill <= 0 then
        goodwill = 0
    end

    local max_discount = (goodwill / 1000) * 50

    if max_discount >= discount_treshold then
        for _, slot in ipairs(equipment_slots) do
            local profit = profit_by_slot[slot]
            if profit > 1 and item_is_repairable(actor, slot) then
                local discount_profit = (1.0 - max_discount / 100) * profit
                local discount = max_discount
                if discount_profit < 1.0 then
                    discount = discount * discount_profit
                end
                discount = math.floor(discount / 5) * 5
                if discount > discount_treshold then
                    giving_discount = true
                    discount_by_slot[slot] = discount
                end
            end
        end
    end
end


-- setup table of gift items based on what actor has in the various slots
-- for weapons this will randomly select one of applicable ammo types
-- for other item types one of the non_ammo_gift_items elemets will be used

local function setup_gift_items(actor, npc)
    for _, slot in ipairs(equipment_slots) do
        local item = actor:item_in_slot(slot)
        if item ~= nil then
            local ammo_list = utils.cfg_get_string(system_ini(), item:section(), "ammo_class", nil, false, "", nil)
            if ammo_list ~= nil then
                local ammo_table = {}
                for ammo in string.gfind(ammo_list, "([^,%s]+)") do
                    table.insert(ammo_table, ammo)
                end
                gift_item_by_slot[slot] = ammo_table[math.random(1, #ammo_table)]
            else
                gift_item_by_slot[slot] = non_ammo_gift_items[math.random(1, #non_ammo_gift_items)]
            end
        end
    end
end


local function item_repair_cost(actor, npc, slot)
    local item = actor:item_in_slot(slot)
    local cost = item:cost()
               * profit_by_slot[slot]
               * (base_cost_factor + 1.0 - item:condition())
               * (100 - discount_by_slot[slot]) / 100

    return math.floor(cost)
end


local function can_repair_item(actor, npc, slot)
    return item_is_repairable(actor, slot)
           and item_is_worth_repairing(actor, slot)
           and actor:money() >= item_repair_cost(actor, npc, slot)
end


local function any_repairs_possible(npc, actor)
    for _, slot in ipairs(equipment_slots) do
        local item = actor:item_in_slot(slot)
        if item ~= nil and item_is_repairable(actor, slot)
           and item_is_worth_repairing(actor, slot) then
            return true
        end
    end
    return false
end


local function repair_item(actor, npc, slot)
    local item = actor:item_in_slot(slot)
    local max_gift_probability = 50

    if item ~= nil then
        dialogs.relocate_money(npc, item_repair_cost(actor, npc, slot), "out")
        if slot == 6 and amk_mod then
            amk_mod.repair_armor(1)
        else
            item:set_condition(1)
        end
    end

    local reputation = actor:character_reputation()

    if reputation < 0 then
        gift_item_to_give = nil
        return
    elseif reputation > 5000 then
        reputation = 5000
    end

    local gift_probability = math.ceil(max_gift_probability * reputation / 5000)

    if math.random(0, 100) <= gift_probability then
        gift_item_to_give = gift_item_by_slot[slot]
    end
end


--- functions directly referenced from dialog xml


function show_repair_costs(npc, actor)
    local cost_texture, cost_rect = get_texture_info("ui_iconsTotal_lost_money")
    local fubar_texture, fubar_rect = get_texture_info("ui_hud_icon_weapon")
    local std_fmt = game.translate_string("list_repair_cost_format")
    local dsc_fmt = game.translate_string("list_repair_cost_with_discount_format")
    local fubar_fmt = game.translate_string("list_repair_fubar_format")
    for _, slot in ipairs(equipment_slots) do
        if item_is_damaged(actor, slot) and item_is_worth_repairing(actor, slot) then
            local type = game.translate_string("repair_item_type_slot_" .. slot)
            local name = game.translate_string(news_manager.get_inv_name(actor:item_in_slot(slot):section()))
            if item_is_repairable(actor, slot) then
                local cost = item_repair_cost(actor, npc, slot)
                local cost_msg
                if discount_by_slot[slot] > 0 then
                    cost_msg = string.format(dsc_fmt, type, name, cost, discount_by_slot[slot])
                else
                    cost_msg = string.format(std_fmt, type, name, cost)
                end
                actor:give_talk_message(cost_msg, cost_texture, cost_rect, "iconed_trade_info")
            else
                local fubar_msg = string.format(fubar_fmt, type, name)
                actor:give_talk_message(fubar_msg, fubar_texture, fubar_rect, "iconed_trade_info")
            end
        end
    end
end


function check_if_damaged_equipment(actor, npc)
    for _, slot in ipairs(equipment_slots) do
        local item = actor:item_in_slot(slot)
        if item ~= nil and item_is_damaged(actor, slot)
           and item_is_worth_repairing(actor, slot) then
            return true
        end
    end
    return false
end


function repairs_at_normal_price_possible(npc, actor)
    return any_repairs_possible(npc, actor) and not giving_discount
end


function repairs_at_discount_price_possible(npc, actor)
    return any_repairs_possible(npc, actor) and giving_discount
end


function all_fubar(npc, actor)
    return not any_repairs_possible(npc, actor)
end


function has_gift_to_give(npc, actor)
    return gift_item_to_give ~= nil
end


function has_no_gift_to_give(npc, actor)
    return not has_gift_to_give(npc, actor)
end


function give_gift(npc, actor)
    dialogs.relocate_item_section(actor, gift_item_to_give, "in")
    gift_item_to_give = nil
end


function init_repair_data(actor, npc)
    setup_profit(actor, npc)
    setup_discounts(actor, npc)
    setup_gift_items(actor, npc)
end


function can_repair_weapon1(actor, npc)
    return can_repair_item(actor, npc, 1)
end


function can_repair_weapon2(actor, npc)
    return can_repair_item(actor, npc, 2)
end


function can_repair_outfit(actor, npc)
    return can_repair_item(actor, npc, 6)
end


function repair_weapon1(actor, npc)
    repair_item(actor, npc, 1)
end


function repair_weapon2(actor, npc)
    repair_item(actor, npc, 2)
end


function repair_outfit(actor, npc)
    repair_item(actor, npc, 6) 
end
