# -*- coding: utf-8 -*-

import os
from item import *

class Generator(Exception):
    pass

class Generator():

    def __init__(self, list = []):
        self.wand = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.passive = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.armor = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.weapon = {"s":[],"a":[],"b":[],"c":[],"d":[]}
        self.all = {"wand":self.wand, "passive":self.passive, "armor":self.armor, "weapon":self.weapon}
        self.add(list)

    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):
        if e.type == "wand":
            self.wand[e.rank.lower()].append(e)
        elif e.type == "armor":
            self.armor[e.rank.lower()].append(e)
        elif e.type == "weapon":
            self.weapon[e.rank.lower()].append(e)
        elif e.type == "passive":
            self.passive[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, rank, type)
                    self.book_register_item(item, rank, type, i)

                self.book_rank_root(type, rank)
                self.item_rank(type, rank)

            self.book_type_root(type)
            self.item_type(type)

        self.item_global()

    def paths(self, type, rank):
        paths = []
        paths.append('../items_py/advancements/{0}/{1}'.format(type, rank))
        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/shop_display/{0}'.format(type))
        paths.append('../items_py/functions/buy/{0}/all'.format(type))

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

# -------- Book part -------
    def book_register_item(self, item, rank, type, i):
        if item.name == None:
            fpath = 'items_py:{0}/{1}'.format(type, str(item.Icount))
        else:
            fpath = 'items_py:item_book/{0}/{1}/{2}'.format(type, rank, item.name_plain_())

        parent_path = "items_py:{0}/{1}/".format(type, rank)
        if i%6 == 0:
            parent_path += 'root'
        else:
            parent_path += '{0}_{1}'.format(i-1, self.all[type][rank][i-1].name_plain_())

        color = ',display:{color:'+str(item.color)+'}' if item.color != None else ''

        data = '''
{{
    "display": {{
        "title": {0},
        "description": {1},
        "icon": {{
            "item": "minecraft:{2}",
            "nbt": "{{CustomModelData:{3}{7}}}"
        }},
        "frame": "task",
        "show_toast": true,
        "announce_to_chat": false,
        "hidden": true
    }},
    "criteria": {{
        "{4}": {{
            "trigger": "minecraft:inventory_changed",
            "conditions": {{
                "items": [
                    {{
                        "item": "minecraft:{2}",
                        "nbt": "{{name:{4}}}",
                        "count": {{
                            "min": 1,
                            "max": 1
                        }}
                    }}
                ]
            }}
        }}
    }},
    "rewards": {{
       "function": "{5}"
    }},
    "parent": "{6}"
}}
'''.format(item.name.get_book(name = True), item.lore.get_book(), item.id, item.model, item.name_plain_(), fpath, parent_path, color)

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

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

    def book_rank_root(self, type, rank):
        if len(self.all[type][rank]) > 0:
            color = {"s":'dark_green', 'a':'dark_red', 'b':'dark_purple', 'c':'gold', 'd':'red'}
            model = {"s":5001, "a":5002, "b":5003, "c":5004, 'd':5005}
            fpath = 'items_py:item_book/{0}/{1}'.format(type, rank)
            parent_path = 'items_py:{0}/root'.format(type)

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

            data = '''
{{
    "display": {{
        "title": {{
            "text": "Rank {0}",
            "color": "{1}"
        }},
        "description": {{
            "text": ""
        }},
        "icon": {{
            "item": "minecraft:slime_ball",
            "nbt": "{{CustomModelData:{2}}}"
        }},
        "frame": "goal",
        "show_toast": true,
        "announce_to_chat": false,
        "hidden": false
    }},
    "criteria": {{ {3}
    }},
    "rewards": {{
         "function": "{4}"
    }},
    "parent": "{5}"
}}
'''.format(rank.upper(), color[rank], model[rank], criteria, fpath, parent_path)
            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 book_type_root(self, type):
        for rank in self.all[type]:
            if len(self.all[type][rank]) > 0:
                model = {"wand":1401, "passive":2103, "armor":0, "weapon":3401}
                background = {"wand":"cyan_wool.png", "passive":"stripped_spruce_log.png", "armor":"iron_block.png", "weapon":"red_wool.png"}
                id = {"wand":"carrot_on_a_stick", "passive":"stick", "armor":"iron_chestplate", "weapon":"wooden_sword"}
                data = '''
{{
    "display": {{
        "title": {{
            "text": "{0}",
            "color": "gold"
        }},
        "description": {{
            "text": ""
        }},
        "icon": {{
            "item": "minecraft:{1}",
            "nbt": "{{CustomModelData:{2}}}"
        }},
        "frame": "challenge",
        "show_toast": false,
        "announce_to_chat": false,
        "hidden": false,
        "background": "minecraft:textures/block/{3}"
    }},
    "criteria": {{
        "null": {{"trigger": "minecraft:tick"}}
    }}
}}
'''.format(type.capitalize(), id[type], model[type], background[type])
                f = open('../items_py/advancements/{0}/root.json'.format(type),'w')
                f.write(data)
                f.close()
                break

# -------- Item part -------
# Item
    def item(self, item, rank, type):
        i_name_ = item.name_plain_()
    # ======= Give =========
        g = open('../items_py/functions/give/{0}/all/{1}.mcfunction'.format(type, i_name_),'w')
        g.write('give @s '+item.id+item.data())
        g.close()

    # ======= Holder =========
        h = open('../items_py/functions/holder/{0}/{1}/{2}.mcfunction'.format(type, rank, i_name_),'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[tag=ingame_items,limit=1] add {3}
scoreboard players add {3} times_appeared 1""".format(item.id, item.data(), item.name.get(), i_name_))
        h.close()

    # ======= Display =========
        cost = {"s":15,"a":12,"b":9,"c":6,"d":3}
        if item.holder and not item.bought:
            sd = open('../items_py/functions/shop_display/{0}/{1}.mcfunction'.format(type, i_name_),'w')
            sd.write("""
data merge entity @s {{Tags:["lobby","selector","buy","shop_d","{0}","{2}","{6}","{7}"]}}

data merge entity @e[tag=name,sort=nearest,limit=1] {{CustomName:{1}}}
data merge entity @e[tag=cost,sort=nearest,limit=1] {{CustomName:'["",{{"text":"{6}","color":"dark_green","bold":true}},{{"text":"\\\\uF81d","color":"gray"}}]'}}

data merge entity @e[type=item,tag=shop_d,sort=nearest,limit=1] {{Item:{{id:"minecraft:{3}",tag:{{CustomModelData:{4},display:{{color:{5}}}}}}}}}
""".format(i_name_, item.name.get(), rank, item.id,item.model,item.color, cost[rank], type))
            sd.close()

    # ======= Buy =========
        if item.holder and not item.bought:
            b = open('../items_py/functions/buy/{0}/all/{1}.mcfunction'.format(type, i_name_),'w')
            b.write("""
function items_py:holder/times_appeared/count_shop_active
scoreboard players operation {0} times_appeared = min times_appeared
scoreboard players set #{0} shop 1
tag @s[tag=shop_d] remove {0}
""".format(i_name_))
            b.write("scoreboard players remove coins shop {0}".format(cost[rank]))

# Rank
    def item_rank(self, type, rank):
    # ======= Give =========
        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()

    # ======= Buy =========
        b = open('../items_py/functions/buy/{0}/{1}.mcfunction'.format(type, rank),'w')
        for item in self.all[type][rank]:
            if item.holder and not item.bought:
                b.write("execute if entity @s[tag={1}] run function items_py:buy/{0}/all/{1}\n".format(type, item.name_plain_()))
        b.close()

    # ======= HOLDER =========
        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 item_rand {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]

tag @s[nbt=!{{HandItems:[{{id:"minecraft:barrier"}}]}}] add filled
tag @s[nbt={{HandItems:[{{id:"minecraft:barrier"}}]}}] add no_{2}
tag @s[nbt={{HandItems:[{{id:"minecraft:barrier"}}]}}] 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('execute if entity @e[tag=ingame_items,tag={0}] run kill @e[tag={1},tag=rand_item]\n'.format(item.name_plain_(), num_))
                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}/count
execute if score active shop matches 0 run function items_py:holder/{0}/{1}/rmv
execute if score active shop matches 1 run function items_py:holder/{0}/{1}/count_shop_active
execute if score active shop matches 1 run function items_py:holder/{0}/{1}/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 0\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:holedr/{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:holedr/{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
function rng:percentage
execute if score tmp times_appeared > out rng 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
    def item_type(self, type):
    # ======= Give =========
        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()

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

    # ======= Holder =========
        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()

# Global
    def item_global(self):
    # ======= Give =========
        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()

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

    # ======= Random for Holder =========
        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 item_rand matches {0}.. run summon area_effect_cloud 1 47 6 {{Tags:["rand_item","{0}"],Duration:2147483647}}'.format(i+1))
        r.close()

    # ======= Display pre_random =========
        dpr = open('../items_py/functions/shop_display/pre_random.mcfunction','w')
        to_display = 0
        for type in self.all:
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                    if item.holder and not item.bought:
                        to_display +=1
        dpr.write("""
scoreboard players set min rng 1
scoreboard players set max rng {}
function rng:interval
scoreboard players operation choose shop = out rng

scoreboard players set choose_loop shop 0
function items_py:shop_n_buy

execute if score can_buy_n shop matches 3.. run function items_py:shop_display/random
execute if score can_buy_n shop matches ..2 run function items:shop/display_empty
""".format(to_display))
        dpr.close()

    # ======= Others =========
    # equipped, ingame_items, buy, shop_init, times_appeared_init, shop_display
        e = open('../items_py/functions/equipped.mcfunction','w')
        i = open('../items_py/functions/ingame_items.mcfunction','w')
        si = open('../items_py/functions/shop_init.mcfunction','w')
        ti = open('../items_py/functions/times_appeared_init.mcfunction','w')
        dr = open('../items_py/functions/shop_display/random.mcfunction','w')
        nb = open('../items_py/functions/shop_n_buy.mcfunction','w')
        files = [e, i, si, ti, dr, nb]

        nb.write("scoreboard players set can_buy_n shop 0\n")
        to_display_ = to_display
        for type in self.all:
        # Type header
            header = '\n# ------{ ' + type.upper() + ' }------\n'
            for file in files:
                file.write(header)
        # The rest
            for rank in self.all[type]:
                for item in self.all[type][rank]:
                    i_name_ = item.name_plain_()
                # Equipped
                    slot = ''
                    if item.type == 'armor':
                        dic = {'boots':'100b','leggings':'101b','chestplate':'102b','helmet':'103b'}
                        slot = item.id.split('_')[1]
                        slot = ',Slot:' + dic[slot]
                    if item.custom_on == True:
                        e.write("execute if entity @s[nbt={{Inventory:[{{tag:{{name:{0}}}{3}}}]}},tag=!{0}] run function items:{1}/{2}/{0}/on\n".format(i_name_, type, rank, slot))
                    if item.custom_off == True:
                        e.write("execute if entity @s[nbt=!{{Inventory:[{{tag:{{name:{0}}}{3}}}]}},tag={0}] run function items:{1}/{2}/{0}/off\n".format(i_name_, type, rank, slot))
                    if item.set != None:
                        e.write("execute if entity @s[nbt={{Inventory:[{{tag:{{name:{0}}}{1}}}]}},tag=!{0}] run scoreboard players add @s {2} 1\n".format(i_name_, slot, item.set))
                        e.write("execute if entity @s[nbt=!{{Inventory:[{{tag:{{name:{0}}}{1}}}]}},tag={0}] run scoreboard players remove @s {2} 1\n".format(i_name_, slot, item.set))
                    e.write("tag @s[nbt={{Inventory:[{{tag:{{name:{0}}}{1}}}]}},tag=!{0}] add {0}\n".format(i_name_, slot))
                    e.write("tag @s[nbt=!{{Inventory:[{{tag:{{name:{0}}}{1}}}]}},tag={0}] remove {0}\n".format(i_name_, slot))

                # Ingame items
                    i.write("execute if entity @s[nbt={{Inventory:[{{tag:{{name:{0}}}}}]}}] run tag @e[tag=ingame_items] add {0}\n".format(i_name_, slot))
                    i.write("execute if entity @s[nbt=!{{Inventory:[{{tag:{{name:{0}}}}}]}}] run tag @e[tag=ingame_items] remove {0}\n".format(i_name_, slot))

                # Shop Init
                    if item.holder and item.bought:
                        si.write("scoreboard players set #{0} shop 1\n".format(i_name_))
                    else:
                        si.write("scoreboard players operation #{0} shop = #{0} shop\n".format(i_name_))

                # Times Appeared Init
                    if item.holder:
                        ti.write("scoreboard players set {0} times_appeared 0\n".format(i_name_))

                # Bougth count
                    if item.holder and not item.bought:
                        nb.write("execute if score #{} shop matches 0 run scoreboard players add can_buy_n shop 1\n".format(i_name_))

                # Random display
                    if item.holder and not item.bought:
                        dr.write("""
execute if score choose shop matches {2} if score #{0} shop matches 1 run scoreboard players remove choose shop 1
execute if score choose shop matches {2} if entity @e[tag={0},tag=shop_d] run scoreboard players remove choose shop 1
execute if score choose shop matches {2} run function items_py:shop_display/{1}/{0}
""".format(i_name_, type, to_display_))
                        to_display_ -= 1

        dr.write("# --- { Loop and guard }---\n")
        dr.write("execute if score out rng matches 0 run scoreboard players set choose shop {}\n".format(to_display))
        dr.write("execute if score out rng matches 0 run scoreboard players set choose_loop shop 1\n")
        dr.write("execute if score out rng matches 0 run function items_py:shop_display/random")
        for file in files:
            file.close()
