-- Rush Duel 融合
RushDuel = RushDuel or {}

-- 内部方法: 生成融合素材的函数
function RushDuel._private_make_fusion_material(card, sub, insf, mat, codes, ...)
    local fun = {}
    local unspecified = false
    for _, val in ipairs {...} do
        local val_type = type(val)
        if val_type == "number" then
            table.insert(fun, RushDuel._private_make_fusion_material_code(val))
            if not mat[val] then
                mat[val] = true
                table.insert(codes, val)
            end
        elseif val_type == "function" then
            table.insert(fun, RushDuel._private_make_fusion_material_func(val))
            unspecified = true
        elseif val_type == "table" then
            local funs = RushDuel._private_make_fusion_material(card, sub, insf, mat, codes, table.unpack(val))
            table.insert(fun, RushDuel._private_make_fusion_material_mix(funs))
        end
    end
    return fun, unspecified
end

-- 内部方法: 融合素材 - 卡名
function RushDuel._private_make_fusion_material_code(code)
    return function(c, fc, sub, mg, sg)
        return c:IsFusionCode(code) or (sub and c:CheckFusionSubstitute(fc))
    end
end

-- 内部方法: 融合素材 - 条件
function RushDuel._private_make_fusion_material_func(func)
    return function(c, fc, sub, mg, sg)
        return func(c, fc, sub, mg, sg) and not c:IsHasEffect(6205579)
    end
end

-- 内部方法: 融合素材 - 混合
function RushDuel._private_make_fusion_material_mix(funs)
    return function(c, fc, sub, mg, sg)
        for _, fun in ipairs(funs) do
            if fun(c, fc, sub, mg, sg) then
                return true
            end
        end
        return false
    end
end

-- 内部方法: 添加融合素材数据
function RushDuel._private_set_fusion_material_data(card, mat, min, max, unspecified, codes)
    local mt = getmetatable(card)
    -- 卡名记述的素材
    if mt.material == nil then
        mt.material = mat
    end
    -- 素材的数量
    if mt.material_count == nil then
        mt.material_count = {min, max}
    end
    -- 包含不指定卡名的融合素材
    if unspecified ~= nil then
        mt.unspecified_funsion = unspecified
    end
    -- 素材的卡名
    if codes ~= nil then
        mt.material_codes = codes
    end
end

-- 内部方法: 创建融合效果
function RushDuel._private_create_fusion_effect(card, insf, sub, checker, min, max, ...)
    local e = Effect.CreateEffect(card)
    e:SetType(EFFECT_TYPE_SINGLE)
    e:SetCode(EFFECT_FUSION_MATERIAL)
    e:SetProperty(EFFECT_FLAG_CANNOT_DISABLE + EFFECT_FLAG_UNCOPYABLE)
    e:SetCondition(RushDuel.FusionProcedureCondition(insf, sub, checker, min, max, ...))
    e:SetOperation(RushDuel.FusionProcedureOperation(insf, sub, checker, min, max, ...))
    card:RegisterEffect(e)
    return e
end

-- 添加融合手续: 指定卡名/条件, 固定数量
function RushDuel.AddFusionProcedure(card, sub, insf, ...)
    if card:IsStatus(STATUS_COPYING_EFFECT) then
        return
    end
    local vals = {...}
    -- 简易融合
    if type(insf) ~= "boolean" then
        table.insert(vals, 1, insf)
        insf = true
    end
    -- 融合素材替代
    if type(sub) ~= "boolean" then
        table.insert(vals, 1, sub)
        sub = true
    end
    -- 有卡名记述的融合素材
    local mat = {}
    local codes = {}
    local funs, unspecified = RushDuel._private_make_fusion_material(card, sub, insf, mat, codes, table.unpack(vals))
    RushDuel._private_set_fusion_material_data(card, mat, #funs, #funs, unspecified, codes)
    local checker = RushDuel.FusionProcedureChecker(RushDuel.FusionProcedureCheckStart, nil)
    return RushDuel._private_create_fusion_effect(card, insf, sub, checker, #funs, #funs, table.unpack(funs))
end
-- 融合手续 - 检测: 指定卡名/条件, 固定数量
function RushDuel.FusionProcedureCheckStart(mg, fc, sub, ...)
    local sg = Group.CreateGroup()
    return mg:IsExists(RushDuel.FusionProcedureCheckStep, 1, nil, mg, sg, fc, sub, ...)
end
function RushDuel.FusionProcedureCheckStep(c, mg, sg, fc, sub, fun1, fun2, ...)
    if fun2 then
        sg:AddCard(c)
        local res = false
        if fun1(c, fc, false, mg, sg) then
            res = mg:IsExists(RushDuel.FusionProcedureCheckStep, 1, sg, mg, sg, fc, sub, fun2, ...)
        elseif sub and fun1(c, fc, true, mg, sg) then
            res = mg:IsExists(RushDuel.FusionProcedureCheckStep, 1, sg, mg, sg, fc, false, fun2, ...)
        end
        sg:RemoveCard(c)
        return res
    else
        return fun1(c, fc, sub, mg, sg)
    end
end

-- 添加融合手续: 指定条件, 不固定数量
function RushDuel.AddFusionProcedureSP(card, matfilter, matcheck, min, max, sub, insf)
    if card:IsStatus(STATUS_COPYING_EFFECT) then
        return
    end
    local insf = (insf ~= false)
    local sub = (sub ~= false)
    local checker = RushDuel.FusionProcedureChecker(RushDuel.FusionProcedureCheckSP, matcheck)
    return RushDuel._private_create_fusion_effect(card, insf, sub, checker, min, max, matfilter)
end
-- 融合手续 - 检测: 指定条件, 不固定数量
function RushDuel.FusionProcedureCheckSP(mg, fc, sub, matfilter)
    local ct = mg:FilterCount(matfilter, nil, fc, false)
    if not sub then
        return ct == mg:GetCount()
    else
        return ct >= mg:GetCount() - 1
    end
end

-- 手动添加融合素材列表
function RushDuel.SetFusionMaterial(card, codes, min, max)
    if card:IsStatus(STATUS_COPYING_EFFECT) then
        return
    end
    local mat = {}
    for _, code in ipairs(codes) do
        mat[code] = true
    end
    RushDuel._private_set_fusion_material_data(card, mat, min, max)
end

-- 手动添加传说卡融合素材列表
function RushDuel.SetFusionLegendMaterial(card, codes, descs)
    if card:IsStatus(STATUS_COPYING_EFFECT) then
        return
    end
    local mt = getmetatable(card)
    if mt.legend_material_codes == nil then
        local legends = {}
        for i in ipairs(codes) do
            table.insert(legends, {codes[i], descs[i]})
        end
        mt.legend_material_codes = legends
    end
end

-- 获取融合素材的卡名
function RushDuel.GetFusionMaterialCodes(card)
    return card.material_codes or {}
end

-- 融合手续 - 检测
function RushDuel.FusionProcedureChecker(condition, checker)
    return function(sg, tp, fc, sub, chkfnf, ...)
        local chkf = chkfnf & 0xff
        local concat_fusion = chkfnf & 0x200 > 0
        if not concat_fusion and sg:IsExists(Auxiliary.TuneMagicianCheckX, 1, nil, sg, EFFECT_TUNE_MAGICIAN_F) then
            return false
        end
        if not Auxiliary.MustMaterialCheck(sg, tp, EFFECT_MUST_BE_FMATERIAL) then
            return false
        end
        return condition(sg, fc, sub, ...) and (chkf == PLAYER_NONE or Duel.GetLocationCountFromEx(tp, tp, sg, fc) > 0) and (not checker or checker(sg, tp, fc, chkf)) and
                   (not Auxiliary.FCheckAdditional or Auxiliary.FCheckAdditional(tp, sg, fc)) and (not Auxiliary.FGoalCheckAdditional or Auxiliary.FGoalCheckAdditional(tp, sg, fc))
    end
end
-- 融合手续 - 条件
function RushDuel.FusionProcedureCondition(insf, sub, checker, min, max, ...)
    local funs = {...}
    return function(e, g, gc, chkfnf)
        if g == nil then
            return insf and Auxiliary.MustMaterialCheck(nil, e:GetHandlerPlayer(), EFFECT_MUST_BE_FMATERIAL)
        end
        local c = e:GetHandler()
        local tp = c:GetControler()
        local notfusion = chkfnf & 0x100 > 0
        local concat_fusion = chkfnf & 0x200 > 0
        local sub = (sub or notfusion) and not concat_fusion
        local mg = g:Filter(Auxiliary.FConditionFilterMix, c, c, sub, concat_fusion, table.unpack(funs))
        if gc then
            if not mg:IsContains(gc) then
                return false
            end
            Duel.SetSelectedCard(gc)
        end
        return mg:CheckSubGroup(checker, min, max, tp, c, sub, chkfnf, table.unpack(funs))
    end
end
-- 融合手续 - 操作
function RushDuel.FusionProcedureOperation(insf, sub, checker, min, max, ...)
    local funs = {...}
    return function(e, tp, eg, ep, ev, re, r, rp, gc, chkfnf)
        local c = e:GetHandler()
        local tp = c:GetControler()
        local notfusion = chkfnf & 0x100 > 0
        local concat_fusion = chkfnf & 0x200 > 0
        local sub = (sub or notfusion) and not concat_fusion
        local mg = eg:Filter(Auxiliary.FConditionFilterMix, c, c, sub, concat_fusion, table.unpack(funs))
        if gc then
            Duel.SetSelectedCard(gc)
        end
        Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_FMATERIAL)
        local sg = mg:SelectSubGroup(tp, checker, true, min, max, tp, c, sub, chkfnf, table.unpack(funs))
        if sg == nil then
            sg = Group.CreateGroup()
        end
        Duel.SetFusionMaterial(sg)
    end
end

-- 创建效果: 融合术/结合 召唤
function RushDuel.CreateFusionEffect(card, matfilter, spfilter, exfilter, s_range, o_range, mat_check, mat_move, target_action, operation_action, including_self, limit_action)
    local self_range = s_range or 0
    local opponent_range = o_range or 0
    local move = mat_move or RushDuel.FusionToGrave
    local include = including_self or false
    local e = Effect.CreateEffect(card)
    e:SetTarget(RushDuel.FusionTarget(matfilter, spfilter, exfilter, self_range, opponent_range, mat_check, include, target_action))
    e:SetOperation(RushDuel.FusionOperation(matfilter, spfilter, exfilter, self_range, opponent_range, mat_check, move, include, operation_action, limit_action))
    return e
end
-- 融合效果 - 素材过滤
function RushDuel.FusionMaterialFilter(c, filter, e)
    return (not filter or filter(c)) and (not e or not c:IsImmuneToEffect(e))
end
-- 融合效果 - 融合召唤的怪兽过滤
function RushDuel.FusionSpecialSummonFilter(c, e, tp, m, f, gc, chkf, filter)
    return c:IsType(TYPE_FUSION) and (not filter or filter(c, e, tp, m, f, chkf)) and (not f or f(c)) and c:IsCanBeSpecialSummoned(e, SUMMON_TYPE_FUSION, tp, false, false) and
               c:CheckFusionMaterial(m, gc, chkf)
end
-- 融合效果 - 确认素材过滤
function RushDuel.ConfirmCardFilter(c)
    return c:IsLocation(LOCATION_HAND) or (c:IsLocation(LOCATION_MZONE) and c:IsFacedown())
end
-- 融合效果 - 获取融合素材与融合怪兽
function RushDuel.GetFusionSummonData(e, tp, matfilter, spfilter, exfilter, s_range, o_range, including_self, except, effect)
    local chkf = tp
    local mg = Duel.GetFusionMaterial(tp):Filter(RushDuel.FusionMaterialFilter, except, matfilter, effect)
    if s_range ~= 0 or o_range ~= 0 then
        local ext = Duel.GetMatchingGroup(aux.NecroValleyFilter(exfilter), tp, s_range, o_range, except, effect)
        mg:Merge(ext)
    end
    local gc = nil
    if including_self then
        gc = e:GetHandler()
    end
    local sg = Duel.GetMatchingGroup(RushDuel.FusionSpecialSummonFilter, tp, LOCATION_EXTRA, 0, nil, e, tp, mg, nil, gc, chkf, spfilter)
    local list = {}
    local fusionable = false
    if #sg > 0 then
        table.insert(list, {nil, mg, sg})
        fusionable = true
    end
    local effects = {Duel.IsPlayerAffectedByEffect(tp, EFFECT_CHAIN_MATERIAL)}
    for _, effect in ipairs(effects) do
        local target = effect:GetTarget()
        local mg2 = target(effect, e, tp, mg)
        if #mg2 > 0 then
            local mf = effect:GetValue()
            local sg2 = Duel.GetMatchingGroup(RushDuel.FusionSpecialSummonFilter, tp, LOCATION_EXTRA, 0, nil, e, tp, mg2, mf, gc, chkf, spfilter)
            if #sg2 > 0 then
                table.insert(list, {effect, mg2, sg2})
                fusionable = true
            end
        end
    end
    return fusionable, list, chkf, gc
end
-- 融合效果 - 进行融合召唤
function RushDuel.ExecuteFusionSummon(e, tp, list, chkf, gc, mat_move)
    local sg = Group.CreateGroup()
    for _, data in ipairs(list) do
        sg:Merge(data[3])
    end
    ::cancel::
    Duel.Hint(HINT_SELECTMSG, tp, HINTMSG_SPSUMMON)
    local fc = sg:Select(tp, 1, 1, nil):GetFirst()
    local options = {}
    for _, data in ipairs(list) do
        local ce, sg = data[1], data[3]
        if sg:IsContains(fc) then
            if ce then
                table.insert(options, {true, ce:GetDescription(), data})
            else
                table.insert(options, {true, 1169, data})
            end
        end
    end
    local data = options[1][3]
    if #options > 1 then
        data = aux.SelectFromOptions(tp, table.unpack(options))
    end
    local ce, mg = data[1], data[2]
    local mat = Duel.SelectFusionMaterial(tp, fc, mg, gc, chkf)
    if #mat < 2 then
        goto cancel
    end
    local cg = mat:Filter(RushDuel.ConfirmCardFilter, nil)
    if #cg > 0 then
        Duel.ConfirmCards(1 - tp, cg)
    end
    fc:SetMaterial(mat)
    if ce then
        local fop = ce:GetOperation()
        fop(ce, e, tp, fc, mat)
    else
        mat_move(tp, mat, e)
    end
    Duel.BreakEffect()
    Duel.SpecialSummon(fc, SUMMON_TYPE_FUSION, tp, tp, false, false, POS_FACEUP)
    fc:CompleteProcedure()
    return fc, mat
end
-- 判断条件: 是否可以进行融合召唤
function RushDuel.IsCanFusionSummon(e, tp, matfilter, spfilter, exfilter, s_range, o_range, mat_check, including_self, except)
    Auxiliary.FGoalCheckAdditional = mat_check
    local fusionable = RushDuel.GetFusionSummonData(e, tp, matfilter, spfilter, exfilter, s_range, o_range, including_self, except)
    Auxiliary.FGoalCheckAdditional = nil
    return fusionable
end
-- 融合效果 - 目标
function RushDuel.FusionTarget(matfilter, spfilter, exfilter, s_range, o_range, mat_check, including_self, action)
    return function(e, tp, eg, ep, ev, re, r, rp, chk)
        if chk == 0 then
            return RushDuel.IsCanFusionSummon(e, tp, matfilter, spfilter, exfilter, s_range, o_range, mat_check, including_self, nil)
        end
        if action ~= nil then
            action(e, tp, eg, ep, ev, re, r, rp)
        end
        Duel.SetOperationInfo(0, CATEGORY_SPECIAL_SUMMON, nil, 1, tp, LOCATION_EXTRA)
    end
end
-- 融合效果 - 操作
function RushDuel.FusionOperation(matfilter, spfilter, exfilter, s_range, o_range, mat_check, mat_move, including_self, action, limit)
    return function(e, tp, eg, ep, ev, re, r, rp)
        Auxiliary.FGoalCheckAdditional = mat_check
        local fusionable, list, chkf, gc = RushDuel.GetFusionSummonData(e, tp, matfilter, spfilter, exfilter, s_range, o_range, including_self, nil, e)
        if fusionable then
            local fc, mat = RushDuel.ExecuteFusionSummon(e, tp, list, chkf, gc, mat_move)
            if action ~= nil then
                action(e, tp, eg, ep, ev, re, r, rp, mat, fc)
            end
        end
        Auxiliary.FGoalCheckAdditional = nil
        if limit ~= nil then
            limit(e, tp, eg, ep, ev, re, r, rp)
        end
    end
end

-- 强制进行融合术召唤
function RushDuel.FusionSummon(matfilter, spfilter, exfilter, s_range, o_range, mat_check, mat_move, e, tp, break_effect, including_self)
    Auxiliary.FGoalCheckAdditional = mat_check
    local fusionable, list, chkf, gc = RushDuel.GetFusionSummonData(e, tp, matfilter, spfilter, exfilter, s_range, o_range, including_self, nil, e)
    local fc = nil
    if fusionable then
        if break_effect then
            Duel.BreakEffect()
        end
        fc = RushDuel.ExecuteFusionSummon(e, tp, list, chkf, gc, mat_move)
    end
    aux.FGoalCheckAdditional = nil
    return fc
end

-- 可以进行融合术召唤
function RushDuel.CanFusionSummon(desc, matfilter, spfilter, exfilter, s_range, o_range, mat_check, mat_move, e, tp, break_effect, including_self)
    Auxiliary.FGoalCheckAdditional = mat_check
    local fusionable, list, chkf, gc = RushDuel.GetFusionSummonData(e, tp, matfilter, spfilter, exfilter, s_range, o_range, including_self, nil, e)
    local fc = nil
    if fusionable and Duel.SelectYesNo(tp, desc) then
        if break_effect then
            Duel.BreakEffect()
        end
        fc = RushDuel.ExecuteFusionSummon(e, tp, list, chkf, gc, mat_move)
    end
    aux.FGoalCheckAdditional = nil
    return fc
end

-- 素材去向: 墓地
function RushDuel.FusionToGrave(tp, mat)
    Duel.SendtoGrave(mat, REASON_EFFECT + REASON_MATERIAL + REASON_FUSION)
end
-- 素材去向: 卡组
function RushDuel.FusionToDeck(tp, mat)
    local g = mat:Filter(Card.IsFacedown, nil)
    if g:GetCount() > 0 then
        Duel.ConfirmCards(1 - tp, g)
    end
    Duel.SendtoDeck(mat, nil, SEQ_DECKSHUFFLE, REASON_EFFECT + REASON_MATERIAL + REASON_FUSION)
end
-- 素材去向: 卡组下面
function RushDuel.FusionToDeckBottom(tp, mat)
    local g = mat:Filter(Card.IsFacedown, nil)
    if g:GetCount() > 0 then
        Duel.ConfirmCards(1 - tp, g)
    end
    Auxiliary.PlaceCardsOnDeckBottom(tp, mat, REASON_EFFECT + REASON_MATERIAL + REASON_FUSION)
end

-- 可以进行接触融合术
function RushDuel.EnableContactFusion(card, desc)
    local e = aux.AddContactFusionProcedure(card, RushDuel.ContactFusionMaterialFilter, LOCATION_ONFIELD, 0, aux.tdcfop(card))
    e:SetDescription(desc)
    e:SetValue(SUMMON_TYPE_FUSION)
    return e
end
-- 接触融合术: 素材过滤
function RushDuel.ContactFusionMaterialFilter(card)
    return card:IsFaceup() and card:IsAbleToDeckOrExtraAsCost()
end

-- 宣言融合素材的卡名
function RushDuel.AnnounceFusionMaterialCode(player, card)
    local legends = card.legend_material_codes or {}
    local codes = card.material_codes or {}
    local normal_count = #codes - #legends
    local type = 0
    if normal_count == 0 then
        -- 只有传说怪兽素材
        type = 2
    elseif #legends == 0 then
        -- 只有常规怪兽素材
        type = 1
    elseif #codes > 0 then
        -- 选择宣言的种类
        type = Duel.SelectOption(player, aux.Stringid(120000001, 0), aux.Stringid(120000001, 1)) + 1
    end
    if type == 1 then
        -- 宣言常规怪兽
        return RushDuel.AnnounceCodes(player, codes)
    elseif type == 2 then
        -- 宣言传说怪兽
        local legend, desc = {}, {}
        for _, value in ipairs(legends) do
            table.insert(legend, value[1])
            table.insert(desc, value[2])
        end
        Duel.Hint(HINT_SELECTMSG, player, HINTMSG_CODE)
        local index = Duel.SelectOption(player, table.unpack(desc)) + 1
        return legend[index]
    end
    return nil
end
