# -*- coding: utf-8 -*-
import os
from item import *

class Generator(Exception):
    pass

class Generator():

    def __init__(self, items = []):
        self.wand:dict[str,list[Item]] = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.passive:dict[str,list[Item]] = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.armor:dict[str,list[Item]] = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.weapon:dict[str,list[Item]] = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.shard:dict[str,list[Item]] = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.all = {"wand":self.wand, "passive":self.passive, "armor":self.armor, "weapon":self.weapon, "shard":self.shard}
        self.add(items)
        self.prev_book = 0

    def add(self, l):
        if isinstance(l, list):
            for i,e in enumerate(l):
                if isinstance(e, list):
                    self.add(e)
                elif isinstance(e, Item):
                    self.sort(e)
                else:
                    raise Generator('Element in l[{0}] is not an Item'.format(i))
        elif isinstance(l, Item):
            self.sort(l)
        else:
            raise Generator("must be give a list of Items, an Item or a list with list of Items")

    def sort(self, e:Item):
        if e.type == "wand":
            self.wand[str(e.rank).lower()].append(e)
        elif e.type == "armor":
            self.armor[str(e.rank).lower()].append(e)
        elif e.type == "weapon":
            self.weapon[str(e.rank).lower()].append(e)
        elif e.type == "passive":
            self.passive[str(e.rank).lower()].append(e)
        elif e.type == "shard":
            self.shard[str(e.rank).lower()].append(e)
        else:
            raise Generator("{} Item has no self.type (Itype) place for the generator".format(e))

    def make(self):
        for type in self.all:
            for rank in self.all[type]:
                self.paths(type, rank)
                for i,item in enumerate(self.all[type][rank]):
                    self.item(item, i)
                self.rank(type, rank)
            self.type(type)
        self.global_()

    def paths(self, type:str, rank:str):
        paths = []
        paths.append('../items_py/advancements/{0}/{1}'.format(type, rank))
        if type != "shard":
            paths.append('../items_py/functions/holder/{0}/{1}/times_appeared/rmv'.format(type, rank))
        paths.append('../items_py/functions/item_book/{0}/{1}'.format(type, rank))
        paths.append('../items_py/functions/give/{0}/all'.format(type))
        paths.append('../items_py/functions/mshop/display/{0}'.format(type))
        paths.append('../items_py/functions/mshop/buy/{0}/all'.format(type))
        paths.append('../items_py/functions/shop/display/{0}/all'.format(type))
        paths.append('../items_py/functions/shop/buy/{0}'.format(type))
        paths.append('../items_py/functions/rnd_shard_drop/all')
        paths.append('../items_py/functions/shard_break/all')
        paths.append('../items_py/functions/shard_break/size')
        paths.append('../items_py/functions/equip_3d')
        paths.append('../items_py/predicates/in_inventory')
        paths.append('../items_py/predicates/selected')
        paths.append('../items_py/predicates/equipped')
        paths.append('../items_py/predicates/equipped/3d')

        for p in paths:
            if not os.path.exists(p):
                os.makedirs(p)
                print("Directory ", p , " Created ")


# ========== Item part ============
    def item(self, item:Item, i:int):
        if item.book:
            self.book_item(item, i)
        self.give_item(item)
        if item.type != "shard":
            self.holder_item(item)
        else:
            self.shard_drop(item)
            self.shard_break(item)
        self.shop_display_item(item)
        if not item.bought:
            self.mshop_display_item(item)
            self.mshop_buy_item(item)
        self.equipped_item(item)
        self.in_inventory(item)
        if item.type in ["weapon", "wand"]:
            self.selected(item)
        if item.type == "armor":
            self.item_equipped(item)
        if item.model_3d:
            self.item_armor_replace(item)

    def book_item(self, item:Item, i:int):
        color = ',display:{color:'+str(item.color)+'}' if item.color != None else ''
        fpath = 'items_py:item_book/{0}/{1}/{2}'.format(item.type, item.rank, item.name_plain_())
        if i == 0:
            ppath = "items_py:{0}/{1}/root".format(item.type, item.rank)
        else:
            ppath = "items_py:{0}/{1}/{2}".format(item.type, item.rank, self.all[item.type][str(item.rank)][self.prev_book].name_plain_())
        self.prev_book = i


        t = open("template/item_book.json")
        data = t.read()
        t.close()

        dic = {
            "[name]"    : item.name.json('advancement').replace('"bold":true', '"bold":false').replace('"underlined":true', '"underlined":false').replace(']', ',{"text":"                                                   ","underlined":false}]'),
            "[lore]"    : item.lore.json('advancement'),
            "[item_id]" : item.id,
            "[model]"   : item.model,
            "[color]"   : color,
            "[nbt]"     : item.name_plain_(),
            "[fpath]"   : fpath,
            "[ppath]"   : ppath
            }
        for i in dic:
            data = data.replace(i, str(dic[i]))

        f = open('../items_py/advancements/{0}/{1}/{2}.json'.format(item.type, item.rank, item.name_plain_()),'w')
        f.write(data)
        f.close()

        f = open('../items_py/functions/item_book/{0}/{1}/{2}.mcfunction'.format(item.type, item.rank, item.name_plain_()),'w')
        f.write("advancement grant @a only items_py:{0}/{1}/root {2}".format(item.type, item.rank, item.name_plain_()))
        f.close()

    def give_item(self, item:Item):
        g = open('../items_py/functions/give/{0}/all/{1}.mcfunction'.format(item.type, item.name_plain_()),'w')
        g.writelines([
            'give @s '+item.id+item.data(),
            '\n',
            f'scoreboard players remove ngive items_generic 1\n',
            f'execute if score ngive items_generic matches 1.. run function items_py:give/{item.type}/all/{item.name_plain_()}\n',
            f'scoreboard players set ngive items_generic 1'
        ])
        g.close()

    def holder_item(self, item:Item):
        h = open('../items_py/functions/holder/{0}/{1}/{2}.mcfunction'.format(item.type, item.rank, item.name_plain_()),'w')
        h.write("""
data merge entity @s {{HandItems:[{{id:{0},Count:1b,tag:{1}}},{{}}]}}\n
data merge entity @s {{DisabledSlots:4144703,ShowArms:1b,CustomNameVisible:1b,CustomName:{2}}}\n
tag @e[type=marker,tag=ingame_items,limit=1] add {3}
scoreboard players add {3} times_appeared 1""".format(item.id, item.data(), item.name.json(), item.name_plain_()))
        h.close()

    def shop_display_item(self, item:Item):
        sd = open('../items_py/functions/shop/display/{0}/all/{1}.mcfunction'.format(item.type, item.name_plain_()),'w')
        if item.holder:
            sd.write("tag @e[type=marker,tag=ingame_items,limit=1] add {}\n".format(item.name_plain_()))
        sd.write("""
tag @s add {0}
tag @s add {2}
tag @s add {6}
scoreboard players set @s shop 0
scoreboard players set #display_done shop 1
data modify entity @e[tag=cost,sort=nearest,limit=1] text set value '["",{{"text":"?","color":"dark_green"}},{{"text":"\\\\uF81d","color":"gray"}}]'

data modify entity @e[tag=name,sort=nearest,limit=1] text set value {1}
data merge entity @e[type=item,tag=shop_d,sort=nearest,limit=1] {{Item:{{id:"{3}",tag:{{CustomModelData:{4},display:{{color:{5}}}}}}}}}
""".format(item.name_plain_(), item.name.json().replace(',"underlined":true',""), item.rank, item.id,item.model,item.color, item.type))
        sd.close()

    def mshop_display_item(self, item:Item):
        
        sd = open('../items_py/functions/mshop/display/{0}/{1}.mcfunction'.format(item.type, item.name_plain_()),'w')
        sd.write("""
function items_py:shop/display/{0}/all/{1}
scoreboard players set @s shop {2}
data modify entity @e[tag=cost,sort=nearest,limit=1] text set value '["",{{"text":"{2}","color":"dark_green"}},{{"text":"\\\\uF81d","color":"gray"}}]'
""".format(item.type, item.name_plain_(), item.price))
        sd.close()

    def mshop_buy_item(self, item:Item):
        b = open('../items_py/functions/mshop/buy/{0}/all/{1}.mcfunction'.format(item.type, item.name_plain_()),'w')
        b.write("function items_py:holder/{0}/{1}/times_appeared/count_shop_active".format(item.type,item.rank))
        b.write("""
scoreboard players operation {0} times_appeared = min times_appeared
scoreboard players set #{0} shop 1
tag @s remove {0}

scoreboard players remove mcoins shop {1}
""".format(item.name_plain_(), item.price))

    def equipped_item(self, item:Item):
        if item.custom_on or item.custom_off:
            path = "../items_py/functions/equipped/{0}/{1}".format(item.type, item.name_plain_())
            if not os.path.exists(path):
                os.makedirs(path)

        if item.custom_on:
            en = open('../items_py/functions/equipped/{0}/{1}/on.mcfunction'.format(item.type, item.name_plain_(), item.rank),'w')
            en.write('tag @s add {}\n'.format(item.name_plain_()))
            en.write("function items:{}/{}/on\n".format(item.type, item.name_plain_()))

        if item.custom_off:
            ef = open('../items_py/functions/equipped/{0}/{1}/off.mcfunction'.format(item.type, item.name_plain_(), item.rank),'w')
            ef.write('tag @s remove {}\n'.format(item.name_plain_()))
            ef.write("function items:{}/{}/off\n".format(item.type, item.name_plain_()))

    def shard_drop(self, item:Shard):
        with open(f'../items_py/functions/rnd_shard_drop/all/{item.name_plain_()}.mcfunction','w') as  sd:
            sd.write('summon item ~ ~ ~ {{Item:{{id:"minecraft:amethyst_shard",Count:1b,tag:{0}}}}}'.format(item.data()))

    def shard_break(self, item:Shard):
        name = item.name_plain_()
        smaller_name = name.replace("medium","small").replace("big","medium")
        with open(f'../items_py/functions/shard_break/all/{name}.mcfunction','w') as s:
            prob = 80 if item.size == ShardSize.big else 70 if item.size == ShardSize.medium else 60
            lines = [
                'execute store result score % items_generic run random value 1..100\n',
                f'execute if score % items_generic matches ..{prob} run clear @s amethyst_shard{{name:"{name}"}} 1\n',
                f'execute if score % items_generic matches ..{prob} run scoreboard players set #broken items_generic 1\n',
                f'execute if score % items_generic matches ..{prob} run function items_py:give/shard/all/{smaller_name}\n\n',
                'scoreboard players remove n_items items_generic 1\n',
                f'execute unless score n_items items_generic matches 0 run function items_py:shard_break/all/{name}'
            ]
            if item.size == ShardSize.small or 'spinning' in name:
                del lines[3]
            s.writelines(lines)

    def in_inventory(self, item:Item):
        p = open('../items_py/predicates/in_inventory/{0}.json'.format(item.name_plain_()),'w')
        p.write("""
{{
    "condition": "minecraft:entity_properties",
    "entity": "this",
    "predicate": {{
        "nbt": "{{Inventory:[{{tag:{{name:{0}}}}}]}}"
    }}
}}""".format(item.name_plain_()))
        p.close()

    def selected(self, item:Item):
        p = open('../items_py/predicates/selected/{0}.json'.format(item.name_plain_()),'w')
        p.write("""
{
	"condition": "minecraft:entity_properties",
	"entity": "this",
	"predicate": {
		"equipment": {
			"mainhand": {
                "items": [
                    "minecraft:ITEM"
                ],
				"nbt": "{name:wand}"
			}
		}
	}
}""".replace( 'wand', item.name_plain_()).replace( 'ITEM', item.id ) )
        p.close()

    def item_equipped(self, item:Item):
        p = open('../items_py/predicates/equipped/{0}.json'.format(item.name_plain_()),'w')
        p.write("""
{{
	"condition": "minecraft:entity_properties",
	"entity": "this",
	"predicate": {{
		"equipment": {{
			"{0}": {{
                "items": [
                    "minecraft:{1}"
                ],
				"nbt": "{{name:{2}}}"
			}}
		}}
	}}
}}
""".format(item.armor_piece, item.id, item.name_plain_()))
        p.close()

        if item.model_3d:
            p = open('../items_py/predicates/equipped/3d/{0}.json'.format(item.name_plain_()),'w')
            p.write("""
{{
    "condition": "minecraft:entity_properties",
    "entity": "this",
    "predicate": {{
        "equipment": {{
            "{0}": {{
                "items": [
                    "minecraft:carved_pumpkin"
                ],
                "nbt": "{{name:{2}}}"
            }}
        }}
    }}
}}
""".format(item.armor_piece, item.id, item.name_plain_()))
            p.close()

    def item_armor_replace(self, item:Item):
        p = open('../items_py/functions/equip_3d/{0}.mcfunction'.format(item.name_plain_()),'w')
        p.write("item replace entity @s armor.head with carved_pumpkin"+item.data())
        p.close()


# ========== Rank part ============
    def rank(self, type:str, rank:str):
        self.book_rank_root(type, rank)
        self.give_rank(type, rank)
        self.mshop_buy_rank(type, rank)
        self.shop_buy_rank(type, rank)
        if type != "shard":
            self.holder_rank(type, rank)

    def book_rank_root(self, type:str, rank:str):
        color = {"s":'dark_green', 'a':'dark_red', 'b':'dark_purple', 'c':'gold', 'd':'red'}
        model = {"s":6010, "a":6011, "b":6012, "c":6013, 'd':6014}
        fpath = 'items_py:item_book/{0}/{1}'.format(type, rank)
        ppath = 'items_py:{0}/root'.format(type)

        to_do = []
        for item in self.all[type][rank]:
            if item.book:
                to_do.append(item)

        criteria = ''
        for i,item in enumerate(to_do):
            criteria += '\n\t\t\t"{0}":{{"trigger": "minecraft:impossible"}}'.format(item.name_plain_())
            if i+1 < len(to_do):
                criteria += ','

        t = open("template/rank_root_book.json")
        data = t.read()
        t.close()

        dic = {
            "[rank]"        : rank.upper(),
            "[color]"       : color[rank],
            "[model]"       : model[rank],
            "[criteria]"    : criteria,
            "[fpath]"       : fpath,
            "[ppath]"       : ppath
            }
        for i in dic:
            data = data.replace(i, str(dic[i]))

        f = open("../items_py/advancements/{0}/{1}/root.json".format(type, rank),'w')
        f.write(data)
        f.close()

        f = open('../items_py/functions/item_book/{0}/{1}.mcfunction'.format(type, rank),'w')
        f.write("advancement grant @a only items_py:{0}/root {0}_all_{1}".format(type, rank))
        f.close()

    def give_rank(self, type:str, rank:str):
        g = open('../items_py/functions/give/{0}/{1}.mcfunction'.format(type, rank),'w')
        for item in self.all[type][rank]:
            g.write("function items_py:give/{0}/all/{1}\n".format(type, item.name_plain_()))
        g.close()

    def shop_buy_rank(self, type:str, rank:str):
        with open('../items_py/functions/shop/buy/{0}/{1}.mcfunction'.format(type, rank),'w') as b:
            for item in self.all[type][rank]:
                if (item.holder == True) or (type == 'shard'):
                    b.write("execute if entity @s[tag={1}] as @p[gamemode=!spectator] run function items_py:give/{0}/all/{1}\n".format(type, item.name_plain_()))
                if type == 'shard':
                    b.write("tag @s[tag={0}] remove {0}\n".format(item.name_plain_()))
            b.write("tag @s remove {0}\n".format(rank))   

    def mshop_buy_rank(self, type:str, rank:str):
        with open('../items_py/functions/mshop/buy/{0}/{1}.mcfunction'.format(type, rank),'w') as b:
            for item in self.all[type][rank]:
                if not item.bought:
                    b.write("execute if entity @s[tag={1}] run function items_py:mshop/buy/{0}/all/{1}\n".format(type, item.name_plain_()))
            b.write("tag @s remove {}".format(rank))

    def holder_rank(self, type:str, rank:str):
        holder_pre = "../items_py/functions/holder/{0}/{1}/".format(type, rank)
        r       = open(holder_pre + '0_random.mcfunction', 'w')
        g       = open(holder_pre + '1_rmv_ingame.mcfunction', 'w')
        cnt     = open(holder_pre + 'times_appeared/count.mcfunction', 'w')
        rmv     = open(holder_pre + 'times_appeared/rmv.mcfunction', 'w')
        cnt_s   = open(holder_pre + 'times_appeared/count_shop_active.mcfunction', 'w')
        rmv_s   = open(holder_pre + 'times_appeared/rmv_shop_active.mcfunction', 'w')
        rmv_t   = open(holder_pre + '2_rmv_times_appeared.mcfunction', 'w')
        b       = open(holder_pre + '3_rmv_not_bought.mcfunction', 'w')
        c       = open(holder_pre + '4_choose.mcfunction', 'w')

        num = 0
        for item in self.all[type][rank]:
            if item.holder:
                num += 1

    # ======= Main (0_random) =========
        r.write("""
scoreboard players set max_rand items_generic {0}
function items_py:holder/random

function items_py:holder/{1}/{2}/1_rmv_ingame
function items_py:holder/{1}/{2}/2_rmv_times_appeared
execute if score active shop matches 1 run function items_py:holder/{1}/{2}/3_rmv_not_bought

execute as @e[sort=random,limit=1,tag=rand_item] run function items_py:holder/{1}/{2}/4_choose
kill @e[tag=rand_item]

execute unless predicate items:selected/barrier run tag @s add filled
execute if predicate items:selected/barrier run tag @s add no_{2}
execute if predicate items:selected/barrier run tag @s remove {3}""".format(num, type, rank, rank.upper()))
        r.close()

    # ======= 1_rmv_ingame =========
        num_ = num
        for item in self.all[type][rank]:
            if item.holder:
                g.write(f'execute if entity @e[type=marker,tag=ingame_items,tag={item.name_plain_()},limit=1] run kill @e[tag={num_},tag=rand_item]\n')
                num_ -= 1
        g.close()

    # ======= 2_rmv_times_appeared =========
    # Remove Times appeared
        rmv_t.write("""
execute if score active shop matches 0 run function items_py:holder/{0}/{1}/times_appeared/count
execute if score active shop matches 0 run function items_py:holder/{0}/{1}/times_appeared/rmv
execute if score active shop matches 1 run function items_py:holder/{0}/{1}/times_appeared/count_shop_active
execute if score active shop matches 1 run function items_py:holder/{0}/{1}/times_appeared/rmv_shop_active
""".format(type, rank))
        rmv_t.close()

    # Count
        first = True
        for item in self.all[type][rank]:
            if item.holder:
                if first:
                    cnt.write("scoreboard players operation min times_appeared = {} times_appeared\n".format(item.name_plain_()))
                    first = False
                else:
                    cnt.write("execute if score {0} times_appeared < min times_appeared run scoreboard players operation min times_appeared = {0} times_appeared\n".format(item.name_plain_()))
        cnt.close()

    # Count shop active
        cnt_s.write("scoreboard players set min times_appeared -1\n")
        for item in self.all[type][rank]:
            if item.holder:
                cnt_s.write("""
execute if score #{0} shop matches 1 if score min times_appeared matches -1 run scoreboard players operation min times_appeared = {0} times_appeared
execute if score #{0} shop matches 1 if score {0} times_appeared < min times_appeared run scoreboard players operation min times_appeared = {0} times_appeared
""".format(item.name_plain_()))
        cnt_s.write("execute if score min times_appeared matches -1 run scoreboard players set min times_appeared 0")
        cnt_s.close()

    # Remove
        num_ = num
        for item in self.all[type][rank]:
            if item.holder:
                rmv.write("function items_py:holder/{0}/{1}/times_appeared/rmv/{2}_{3}\n".format(type, rank, num_, item.name_plain_()))
                num_ -= 1
        rmv.close()

    # Remove shop active
        num_ = num
        for item in self.all[type][rank]:
            if item.holder:
                rmv_s.write("execute if score #{3} shop matches 1 run function items_py:holder/{0}/{1}/times_appeared/rmv/{2}_{3}\n".format(type, rank, num_, item.name_plain_()))
                num_ -= 1
        rmv_s.close()

    # Times appeared item specific
        num_ = num
        for item in self.all[type][rank]:
            if item.holder:
                ta = open('../items_py/functions/holder/{0}/{1}/times_appeared/rmv/{2}_{3}.mcfunction'.format(type, rank, num_, item.name_plain_()),'w')
                ta.write("""
scoreboard players operation #tmp times_appeared = {} times_appeared
scoreboard players operation #tmp times_appeared -= min times_appeared
scoreboard players operation #tmp times_appeared *= #factor times_appeared
execute store result score % times_appeared run random value 1..100
execute if score #tmp times_appeared > % times_appeared run kill @e[tag={},tag=rand_item]
""".format(item.name_plain_(),num_))
                ta.close()
                num_ -= 1

    # ======= 3_rmv_not_bought =========
        num_ = num
        for item in self.all[type][rank]:
            if item.holder:
                b.write('execute if score #{0} shop matches 0 run kill @e[tag={1},tag=rand_item]\n'.format(item.name_plain_(), num_))
                num_ -= 1
        b.close()

    # ======= 4_choose =========
        num_ = num
        for item in self.all[type][rank]:
            if item.holder:
                c.write('execute if entity @s[tag={0}] as @e[tag=holder,distance=0] run function items_py:holder/{1}/{2}/{3}\n'.format(num_, type, rank, item.name_plain_()))
                num_ -= 1
        c.close()


# ========== Type part ============
    def type(self, type:str):
        self.book_type_root(type)
        self.give_type(type)
        self.mshop_buy_type(type)
        self.shop_buy_type(type)
        if type != "shard":
            self.holder_type(type)
        self.type_shop_display(type)

    def book_type_root(self, type:str):
        model = {"wand":1401, "passive":2103, "armor":0, "weapon":3504, "shard":0}
        background = {"wand":"cyan_wool", "passive":"stripped_spruce_log", "armor":"iron_block", "weapon":"red_wool", "shard":"amethyst_block"}
        id = {"wand":"carrot_on_a_stick", "passive":"stick", "armor":"iron_chestplate", "weapon":"wooden_sword", "shard":"amethyst_shard"}

        t = open("template/type_root_book.json")
        data = t.read()
        t.close()

        dic = {
            "[type]"        : type,
            "[item_id]"     : id[type],
            "[model]"       : model[type],
            "[background]"  : background[type] + ".png"
            }
        for i in dic:
            data = data.replace(i, str(dic[i]))

        f = open('../items_py/advancements/{0}/root.json'.format(type),'w')
        f.write(data)
        f.close()

    def give_type(self, type:str):
        f = open('../items_py/functions/give/{0}.mcfunction'.format(type),'w')
        for rank in self.all[type]:
            f.write("function items_py:give/{0}/{1}\n".format(type, rank))
        f.close()

    def shop_buy_type(self, type:str):
        with open('../items_py/functions/shop/buy/{0}.mcfunction'.format(type),'w') as b:
            for rank in self.all[type]:
                b.write("execute if entity @s[tag={1}] run function items_py:shop/buy/{0}/{1}\n".format(type, rank))

    def mshop_buy_type(self, type:str):
        with open('../items_py/functions/mshop/buy/{0}.mcfunction'.format(type),'w') as b:
            for rank in self.all[type]:
                b.write("execute if entity @s[tag={1}] run function items_py:mshop/buy/{0}/{1}\n".format(type, rank))
            b.write("tag @s remove {}".format(type))

    def holder_type(self, type:str):
        f = open('../items_py/functions/holder/{0}/rank.mcfunction'.format(type),'w')
        for rank in self.all[type]:
            f.write('\nexecute if entity @s[tag={0}] run function items_py:holder/{1}/{2}/0_random'.format(rank.upper(), type, rank))
        f.close()

    def type_shop_display(self, type:str):
        base_path = f'../items_py/functions/shop/display/{type}'

        r = open(f'{base_path}.mcfunction','w')
        r.writelines([
            f"function items_py:shop/display/{type}/summon_pool\n",
            f"execute if score active shop matches 1 run function items_py:shop/display/{type}/remove_not_mbought\n",
            f"tag @e[type=marker,tag=shop_pool,sort=random,limit=1] add choosen\n",
            f"function items_py:shop/display/{type}/fill\n",
            f"kill @e[type=marker,tag=shop_pool]\n"
        ])
        if type == 'shard':
            r.write("tag @s add restock")
        r.close()

        s = open(f'{base_path}/summon_pool.mcfunction','w')
        r = open(f'{base_path}/remove_not_mbought.mcfunction','w')
        f = open(f'{base_path}/fill.mcfunction','w')
    
        f.write(f"\nscoreboard players set #display_done shop 0")

        for rank in self.all[type]:
            for item in self.all[type][rank]:
                if item.holder or (type == 'shard'):
                    name = item.name_plain_()
                    s.write(f"\nexecute unless entity @e[type=marker,tag=ingame_items,tag={name},limit=1] run summon marker 1 47 6 {{Tags:['shop_pool','{name}']}}")
                    r.write(f"\nexecute if score #{name} shop matches 0 run kill @e[type=marker,tag=shop_pool,tag={name},limit=1]")
                    f.write(f"\nexecute if score #display_done shop matches 0 if entity @e[type=marker,tag=shop_pool,tag=choosen,tag={name},limit=1] run function items_py:shop/display/{type}/all/{name}")
        s.close()
        r.close()
        f.close()


# ========== Global part ============
    def global_(self):
        # mshop
        self.mshop_buy()
        self.mshop_display()
        # shop
        self.shop_init()
        self.shop_buy()

        self.give_all()
        self.holder_random()
        self.equipped()
        self.set_count()
        self.ingame_items()
        self.times_appeared_init()
        self.rnd_shard_drop()
        self.shards_break()
        self.head_2d_to_3d()
        self.inv_2d_to_3d()

    def give_all(self):
        f = open('../items_py/functions/give.mcfunction','w')
        for type in self.all:
            f.write("function items_py:give/{0}\n".format(type))
        f.close()

    def shop_buy(self):
        b = open('../items_py/functions/shop/buy.mcfunction','w')
        for type in self.all:
            b.write("execute if entity @s[tag={0}] run function items_py:shop/buy/{0}\n".format(type))
        b.close()

    def mshop_buy(self):
        b = open('../items_py/functions/mshop/buy.mcfunction','w')
        for type in self.all:
            b.write("execute if entity @s[tag={0}] run function items_py:mshop/buy/{0}\n".format(type))
        b.write("function items:shop/display/set/sold_out\n")
        b.close()

    def mshop_display(self):
        with open('../items_py/functions/mshop/display.mcfunction','w') as d:
            d.writelines([
                "function items_py:mshop/count_left_to_buy\n",
                "execute if score left_to_buy shop matches 1.. run function items_py:mshop/display/random\n"
            ])

        with open('../items_py/functions/mshop/display/random.mcfunction','w') as r:
            r.writelines([
                f"function items_py:mshop/display/summon_pool\n",
                f"tag @e[type=marker,tag=shop_pool,sort=random,limit=1] add choosen\n"
                f"function items_py:mshop/display/fill\n",
                f"kill @e[type=marker,tag=shop_pool]\n"
            ])
        
        c = open('../items_py/functions/mshop/count_left_to_buy.mcfunction','w')
        s = open('../items_py/functions/mshop/display/summon_pool.mcfunction','w')
        f = open('../items_py/functions/mshop/display/fill.mcfunction','w')

        c.write("scoreboard players set left_to_buy shop 0\n")
        f.write(f"scoreboard players set #display_done shop 0\n")

        for type in self.all:
            c.write('\n# ------{ ' + type.upper() + ' }------\n')
            s.write('\n# ------{ ' + type.upper() + ' }------\n')
            f.write('\n# ------{ ' + type.upper() + ' }------\n')
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                    if not item.bought:
                        name = item.name_plain_()
                        c.write(f"execute if score #{name} shop matches 0 run scoreboard players add left_to_buy shop 1\n")
                        s.write(f"execute unless score #{name} shop matches 1 run summon marker 1 47 6 {{Tags:['shop_pool','{name}']}}\n")
                        f.write(f"execute if score #display_done shop matches 0 if entity @e[type=marker,tag=shop_pool,tag=choosen,tag={name},limit=1] run function items_py:mshop/display/{type}/{name}\n")
        c.close() 
        s.close()
        f.close()

    def holder_random(self):
        r = open('../items_py/functions/holder/random.mcfunction','w')
        r.write('kill @e[tag=rand_item]')
        max = 0
        for type in self.all.values():
            for rank in type.values():
                if len(rank) > max:
                    max = len(rank) + 1
        for i in range(max):
            r.write('\nexecute if score max_rand items_generic matches {0}.. run summon marker 1 47 6 {{Tags:["rand_item","{0}"]}}'.format(i+1))
        r.close()

        for type in self.all:
            if type != "shard":
                r = open(f'../items_py/functions/holder/{type}/print_times_appeared.mcfunction','w')
                for rank in self.all[type]:
                    r.write(f'\ntellraw @s "====  Rank {rank} ===="')
                    for item in self.all[type][rank]:
                        if item.holder:
                            r.write('\ntellraw @s [{{ "score":{{"name":"{0}","objective":"times_appeared"}}}},"  {0}"]'.format(item.name_plain_()))
        r.close()

    def equipped(self):
        e = open('../items_py/functions/equipped.mcfunction','w')
        for type in self.all:
            e.write('\n# ------{ ' + type.upper() + ' }------\n')
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                    if item.give_tag or item.custom_on or item.custom_off:
                        name_plain_ = item.name_plain_()

                        condition3 = 'if predicate items_py:in_inventory/{}'.format(name_plain_)
                        if item.type == "armor":
                            condition3 = condition3.replace('in_inventory','equipped')
                        elif item.type in ["weapon", "wand"]:
                            condition3 = condition3.replace('in_inventory','selected')
                        condition1 = condition3 + ' if entity @s[tag=!{}]'.format(name_plain_)
                        condition2 = condition3.replace('if', 'unless') + ' if entity @s[tag={}]'.format(name_plain_)


                        if item.custom_on:
                            e.write("execute {} run function items_py:equipped/{}/{}/on\n".format(condition1, item.type, name_plain_))
                        elif item.custom_off or item.give_tag:
                            e.write("execute {} run tag @s add {}\n".format(condition1, name_plain_))

                        if item.custom_off:
                                e.write("execute {} run function items_py:equipped/{}/{}/off\n".format(condition2, item.type, name_plain_))
                        elif item.custom_on or item.give_tag:
                            e.write("execute {} run tag @s remove {}\n".format(condition2, name_plain_))
        e.close()

    def set_count(self):
        sets, lines = [], []
        for type in self.all:
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                    if item.set != None:

                        if (item.set not in sets):
                            sets.append(item.set)

                        name = item.name_plain_()
                        check = 'in_inventory' if item.type != "armor" else 'equipped'
                        if not item.model_3d:
                            lines.append(f"execute if predicate items_py:{check}/{name} run scoreboard players add @s {item.set} 1\n")
                        else:
                            lines.append(f"execute if predicate items_py:{check}/3d/{name} run scoreboard players add @s {item.set} 1\n")
        
        lines = [f'scoreboard players operation pre {set} = @s {set}\n' for set in sets] + ['\n'] + [f'scoreboard players set @s {set} 0\n' for set in sets] + ['\n'] + lines

        with open('../items_py/functions/set_count.mcfunction','w') as e:
            e.writelines(lines)

    def ingame_items(self):
        i = open('../items_py/functions/ingame_items.mcfunction','w')
        i.write("kill @e[type=marker,tag=ingame_items]\n")
        i.write('summon marker ~ ~ ~ {Tags:["ingame_items","unspellable"]}\n')
        for type in self.all:
            i.write('\n# ------{ ' + type.upper() + ' }------\n')
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                    if item.type != 'shard':
                        i.write("execute as @a if predicate items_py:in_inventory/{0} run tag @e[type=marker,tag=ingame_items,limit=1] add {0}\n".format(item.name_plain_()))
        i.close()

    def shop_init(self):
        si = open('../items_py/functions/mshop/init.mcfunction','w')
        for type in self.all:
            si.write('\n# ------{ ' + type.upper() + ' }------\n')
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                # Shop Init
                    if item.bought:
                        si.write("scoreboard players set #{0} shop 1\n".format(item.name_plain_()))
                    else:
                        si.write("scoreboard players operation #{0} shop = #{0} shop\n".format(item.name_plain_()))
        si.close()

    def times_appeared_init(self):
        ti = open('../items_py/functions/times_appeared_init.mcfunction','w')
        for type in self.all:
            ti.write('\n# ------{ ' + type.upper() + ' }------\n')
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                    if item.holder:
                        ti.write("scoreboard players operation {0} times_appeared = {0} times_appeared\n".format(item.name_plain_()))
        ti.close()

    def rnd_shard_drop(self):
        sd = open('../items_py/functions/rnd_shard_drop/drop.mcfunction','w')
        for rank in self.shard:
            for item in self.shard[rank]:
                times = 1
                if "small" in item.name_plain_():
                    times = 2
                for i in range(times):
                    sd.write('execute if score #{0} shop matches 1 run summon marker ~ ~ ~ {{Tags:["{0}","rnd_shard_drop","unspellable"]}}\n'.format(item.name_plain_()))
        sd.write('\nexecute as @e[tag=rnd_shard_drop,limit=1,sort=random] run function items_py:rnd_shard_drop/sort\n')
        sd.write('\nkill @e[tag=rnd_shard_drop]\n')
        sd.write('\ntag @e[type=item,nbt={Item:{tag:{type:"shard"}}},tag=!no_timeout,sort=nearest,limit=1] add no_timeout\n')
        sd.close()

        sd = open('../items_py/functions/rnd_shard_drop/sort.mcfunction','w')
        for rank in self.shard:
            for item in self.shard[rank]:
                sd.write('execute if entity @s[tag={0}] run function items_py:rnd_shard_drop/all/{0}\n'.format(item.name_plain_()))
        sd.close()

    def shards_break(self):
        with open(f'../items_py/functions/shard_break/break.mcfunction','w') as s:
            lines = [ 
                'scoreboard players set #broken items_generic 0\n',
                '# Order matters\n',
                'execute if predicate items:in_inventory/shard_size/small run function items_py:shard_break/size/small\n',
                'execute if predicate items:in_inventory/shard_size/medium run function items_py:shard_break/size/medium\n',
                'execute if predicate items:in_inventory/shard_size/big run function items_py:shard_break/size/big\n',
                'execute positioned ~ ~1.7 ~ run tag @e[type=item,nbt={Item:{tag:{type:"shard"}}},distance=..0.4] add no_timeout\n',
                'execute if score #broken items_generic matches 1 run function items:shard/break_sound'
            ]
            s.writelines(lines)

        s = open(f'../items_py/functions/shard_break/size/small.mcfunction','w')
        m = open(f'../items_py/functions/shard_break/size/medium.mcfunction','w')
        b = open(f'../items_py/functions/shard_break/size/big.mcfunction','w')
        for rank in self.shard:
            for item in self.shard[rank]:
                file = s if item.size == ShardSize.small else m if item.size == ShardSize.medium else b
                name = item.name_plain_()
                file.writelines([
                    f'execute store result score n_items items_generic run clear @s amethyst_shard{{name:"{name}"}} 0\n'
                    f'execute unless score n_items items_generic matches 0 run function items_py:shard_break/all/{name}\n'
                ])
        s.close(), m.close(), b.close()

    def head_2d_to_3d(self):
        s = open('../items_py/functions/head_2d_to_3d.mcfunction','w')
        for rank in self.armor:
            for item in self.armor[rank]:
                if item.model_3d:
                    s.write(f'execute if predicate items_py:equipped/{item.name_plain_()} run function items_py:equip_3d/{item.name_plain_()}\n')
        s.close()

    def inv_2d_to_3d(self):
        s = open('../items_py/functions/inv_3d_to_2d.mcfunction','w')
        for rank in self.armor:
            for item in self.armor[rank]:
                if item.model_3d:
                    s.writelines([
                        f'scoreboard players set ngive items_generic 0\n',
                        f'execute unless predicate items_py:equipped/3d/{item.name_plain_()} store result score ngive items_generic run clear @s minecraft:carved_pumpkin{{CustomModelData:{item.model}}}\n',
                        f'execute if score ngive items_generic matches 1.. run function items_py:give/armor/all/{item.name_plain_()}\n'
                    ])
        s.close()