function isTransformed(entity) {
    return (entity.getData("tmf:dyn/transformed") & 0x80) == 0;
}

function setTransformed(manager, entity, playlist, index) {
    manager.setDataWithNotify(entity, "tmf:dyn/transformed", index & 15 | (playlist & 7) << 4);
}

var canMsgHappen = true;

function sendClientOnly(condition, message) {
    if (PackLoader.getSide() == "CLIENT") {
        if (condition && canMsgHappen) {
            PackLoader.printChat(message);
            canMsgHappen = false;
        } else if (!canMsgHappen) {
            canMsgHappen = true;
        }
    }
}

function create(instance, aliens) {
    var externals = [];
    for (var playlist = 0; playlist < aliens.length; ++playlist) {
        var array = [];
        Object.keys(aliens[playlist]).forEach(key => {
            var ext = instance.implement("tmf:external/" + key);
            ext.id = key;
            if (ext.hasOwnProperty("IMPLEMENTS") && typeof ext.IMPLEMENTS === "object") {
                Object.keys(ext.IMPLEMENTS).forEach(k => {
                    ext[k] = instance.implement(ext.IMPLEMENTS[k]);
                });
            }
            array.push(ext);
        });
        externals.push(array);
    }

    var getAlien = entity => {
        var b = entity.getData("tmf:dyn/transformed");
        if ((b & 0x80) != 0) {
            return null;
        }
        return {
            index: b & 15,
            playlist: (b >> 4) & 7,
            ext: externals[(b >> 4) & 7][b & 15]
        };
    };

    var tickHandler = (entity, manager) => {
        var alien = getAlien(entity);
        var nbt = entity.getWornChestplate().nbt();
//Skill System
        if (alien != null && alien.ext.id) {
            var SL = nbt.getByte(alien.ext.id);
            if (SL < 60) {
                if (entity.getHeldItem().name() == 'minecraft:bedrock') {
                    manager.setByte(nbt, alien.ext.id, 15);
                }
                if (entity.getHeldItem().name() == 'fisktag:barrier') {
                    manager.setByte(nbt, alien.ext.id, 59);
                }
                if (SL > 15 && entity.getData('tmf:dyn/levelUp') == 1) {
                    manager.setData(entity, "tmf:dyn/levelUp", 0);
                }
                else {
                    var time_worn = entity.getData("tmf:dyn/time_worn");
                    if (time_worn > 1200) {
                        time_worn = 0;
                        manager.setByte(nbt, alien.ext.id, SL + 1);
                    }
                    manager.setData(entity, "tmf:dyn/time_worn", time_worn + 1);
                }
            }
        
            manager.incrementData(entity, "tmf:dyn/levelUp", 20, SL == 15 || SL == 59);

            if (entity.getData("tmf:dyn/levelUp") == 1 && SL == 59) {
                manager.setByte(nbt, alien.ext.id, 60);
            }

            //if (entity.ticksExisted() % 100 == 0) { PackLoader.printChat(SL); }
        }
//Binding
        if (nbt.getBoolean("Used") == false && entity.as("DISPLAY").getDisplayType() != "HOLOGRAM") {
            sendClientOnly((nbt.getBoolean("Used") == false && entity.as("DISPLAY").getDisplayType() != "HOLOGRAM"), "\u00A7a\u00A7lThe Omnitrix \u00A7rhas been genetically bound to \u00A75\u00A7l" + entity.getName());
            var display = nbt.getCompoundTag("display");
            manager.setBoolean(nbt, "Used", true);
            manager.setString(nbt, "userUUID", entity.getUUID());

            manager.setCompoundTag(nbt, "display", display);
            manager.setString(display, "Name", "The Omnitrix Prototype");
        }

//selecting animations and timers
        var selected = entity.getData('tmf:dyn/selected');
        manager.incrementData(entity, "tmf:dyn/selecting_timer", 5, entity.getData("fiskheroes:gravity_manip"));
        manager.incrementData(entity, "tmf:dyn/selecting_timer2", 10, !entity.getData('tmf:dyn/selecting') && entity.getData("fiskheroes:gravity_manip") && ((entity.getData('tmf:dyn/selected2') != selected) || entity.getData("tmf:dyn/selecting_timer2") != 0));
        manager.setData(entity, "tmf:dyn/selected2", selected);
        if (entity.getData("tmf:dyn/selecting_timer2") == 1) {
            manager.setData(entity, "tmf:dyn/selecting", true);
        }
        if (entity.getData("tmf:dyn/selecting_timer2") == 0) {
            manager.setData(entity, "tmf:dyn/selecting", false);
        }
//detransformation
        if (isTransformed(entity) && entity.getInterpolatedData("tmf:dyn/transformation_cooldown") > 0.985 && !entity.getData("tmf:dyn/timeout")) {
            manager.setData(entity, "tmf:dyn/timeout", true);
            manager.setData(entity, "tmf:dyn/timeout2", true);
            return true;
        }
        if (entity.getData("tmf:dyn/timeout") == true) {
            manager.incrementData(entity, "tmf:dyn/timeout_timer", 70, 1);
            if (entity.getData("tmf:dyn/timeout_timer") == 1) {
                manager.setData(entity, "tmf:dyn/timeout", false);
                manager.setData(entity, "tmf:dyn/timeout_timer", 0);
                manager.setData(entity, "tmf:dyn/transformation", false);
                manager.setData(entity, "tmf:dyn/transformed", -1);
            }
        }
//fisk stuff
        if (alien == null) {
            if (entity.getData("fiskheroes:gravity_manip")) {
                if (entity.getData("tmf:dyn/reset_selected")) {
                    manager.setData(entity, "fiskheroes:gravity_amount", 0);
                    manager.setData(entity, "tmf:dyn/reset_selected", false);
                }
                var playlist = 0;
                var size = externals[playlist].length;
                var f = entity.getData("fiskheroes:gravity_amount");
                if (f != 0) {
                    var sel = entity.getData("tmf:dyn/selected");
                    sel = (sel + Math.round(f * 3)) % size;
                    while (sel < 0) {
                        sel += size;
                    }
                    manager.setData(entity, "tmf:dyn/selected", sel);
                    manager.setData(entity, "tmf:dyn/reset_selected", true);
                }
                if (entity.isPunching() && entity.getData("tmf:dyn/transformation_timer") == 0) {
                    manager.setData(entity, "tmf:dyn/transformation", true);
                    entity.playSound("tmf:omnitrix.transformation", 1, 0.9 + 0.2 * Math.random());
                    manager.setData(entity, "tmf:dyn/p_1", false);
                    manager.setData(entity, "tmf:dyn/p_2", false);
                    manager.setData(entity, "tmf:dyn/p_2.2", false);
                    manager.setData(entity, "tmf:dyn/p_3", false);
                    manager.setData(entity, "tmf:dyn/p_4", false);
                    manager.setData(entity, "tmf:dyn/p_5", false);
                    manager.setData(entity, "tmf:dyn/pt_1", 0);
                    manager.setData(entity, "tmf:dyn/pt_2", 0);
                    manager.setData(entity, "tmf:dyn/pt_3", 0);
                    manager.setData(entity, "tmf:dyn/pt_4", 0)
                    manager.setData(entity, "tmf:dyn/pt_5", 0)
                    manager.setData(entity, "tmf:dyn/pc_1", 0);
                    manager.setData(entity, "tmf:dyn/pc_2", 0);
                    manager.setData(entity, "tmf:dyn/pc_3", 0);
                    manager.setData(entity, "tmf:dyn/pc_4", 0);
                    manager.setData(entity, "tmf:dyn/pc_5", 0);
                    manager.setData(entity, "tmf:dyn/time_worn", 0);
                    manager.setData(entity, "tmf:dyn/levelUp", 0);
                    manager.setData(entity, "tmf:dyn/upgrade_liquified", false);
                    manager.setData(entity, "tmf:dyn/upgrade_liquified_timer", 0);
                    manager.setData(entity, "tmf:dyn/upgrade_liquified_cooldown", 0);
                    manager.setData(entity, "tmf:dyn/upgrade", null);
                }
            }
            manager.incrementData(entity, "tmf:dyn/transformation_cooldown", 3000, entity.getData("tmf:dyn/transformation_timer") != 0);

            if (entity.getData("tmf:dyn/transformation") == true && entity.getData("tmf:dyn/transformation_timer") > 0.7) {
                var sel = entity.getData("tmf:dyn/selected");
                setTransformed(manager, entity, playlist, Math.min(Math.max(sel, 0), size - 1));
            }
            return;
        }
        externals.forEach((array, playlist) => {
            array.forEach((ext, index) => {
                if (ext.hasOwnProperty("tick")) {
                    ext.tick(entity, manager, alien.playlist == playlist && alien.index == index);
                }
            });
        });
        return false;
    };

    var getAttributeProfile = entity => {
        var alien = getAlien(entity);
        if (alien == null) {
            return null;
        }
        if (typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("getAttributeProfile")) {
            return alien.ext.getAttributeProfile(entity);
        }
        return null;
    };

    var getDamageProfile = entity => {
        var alien = getAlien(entity);
        if (alien == null) {
            return null;
        }
        if (typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("getDamageProfile")) {
            return alien.ext.getDamageProfile(entity);
        }
        return null;
    };

    var hasPermission = (entity, permission) => {
        var alien = getAlien(entity);
        if (alien == null) {
            return false;
        }
        return typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("hasPermission") && alien.ext.hasPermission(entity, permission);
    };

    var hasProperty = (entity, property) => {
        var alien = getAlien(entity);
        if (alien == null) {
            return false;
        }
        return typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("hasProperty") && alien.ext.hasProperty(entity, property);
    };

    var isModifierEnabled = (entity, modifier) => {
        if (modifier.id().startsWith("always_")) {
            return true;
        }
        var alien = getAlien(entity);
        if (alien == null) {
            return modifier.id().startsWith("omnitrix_");
        }
        if (modifier.id().startsWith("transformation_")) {
            return true;
        }
        if (modifier.id().startsWith("healing")) {
            return (!entity.getData('tmf:dyn/transformation') && entity.getData('tmf:dyn/transformation_timer') != 0) || (entity.getData('tmf:dyn/transformation') && entity.getData('tmf:dyn/transformation_timer') != 1);
        }
        if (typeof alien.ext !== "undefined" && modifier.id().startsWith(alien.ext.id + "_")) {
            return !alien.ext.hasOwnProperty("isModifierEnabled") || alien.ext.isModifierEnabled(entity, modifier);
        }
        return false;
    };

    var isKeyBindEnabled = (entity, keyBind) => {
        var alien = getAlien(entity);
        var UUID = entity.getUUID();
        var UUIDList = (UUID == "ff3ffbbd-a969-4f65-9e16-93efec2ccf2a" || UUID == "31959ac8-edfb-46c1-bba9-8699d9c8c111" || UUID == "9e0e5a82-3275-4f99-acac-95e62285dc25" || UUID == "62e2746c-3f6a-40b9-b54b-630a9f731c49" || UUID == "4c6f2582-7625-4209-a7b1-e708109ce278" || UUID == "770f9407-4e08-4472-9d3a-1c4cca2267e4" || UUID == "5520e168-ad03-4af8-8ec2-174b4ebcfa37" || UUID == "fa4f3e19-a02b-4416-8f7e-819d7358a697" || UUID == "b139f1a5-0cba-440b-b1c6-3dafe3ffcda5" || UUID == "07459db6-35fb-45b7-809c-0f39b976ed56" || UUID == "ba30e09e-5d08-4518-94ef-85da3082ef3d" || UUID == "70397a8c-6c52-49f0-8cd8-b844269a90f0" || UUID == "ea29c89a-6ec7-4c96-a0d7-14f4e46c3302" || UUID == "88dd4a11-2f91-489b-b616-0ff096886a2c" || UUID == "dbe72e2e-9a7b-4282-aac6-994d96f839c6" || UUID == "1c5e1f64-3896-467a-9572-5611b2fc595e" || UUID == "5f555826-cfa2-4604-9cda-5718bc702224" || UUID == "a22f551e-559a-4d0c-a0f6-9e8b0a2f2bc7" || UUID == "7c8d7c79-f641-42e4-8532-677daf4a7b65" || UUID == "7b81eb5e-c995-4688-bc5d-f9b89e0d3e57" || UUID == "ea0e078e-cf78-4530-a221-17e8bf7e813e" || UUID == "26af4156-e897-488c-add2-25ad5c6a95b8" || UUID == "9158afac-7d2f-4d0d-968b-ac5751b27560" || UUID == "f0925d3c-11e2-4bcb-9bd2-0c99dfae7173" || UUID == "199590c0-738d-41a0-900b-2216cd4b6c59" || UUID == "bdc4f0d8-486f-408d-ae34-e57c7b98786f" || UUID == "a5ec9493-7a1a-4d4d-9edd-08d5e15733da" || UUID == "d5175a0b-5d75-408a-a7bb-81cd3ae284f7" || UUID == "7f6cd36c-ac89-46c8-9dbe-9365685756c0");
        if (alien == null) {
            return (keyBind == "func_COLOR" && entity.isSneaking() && UUIDList) || ((entity.getInterpolatedData("tmf:dyn/transformation_cooldown") <= 0.005 || entity.as("PLAYER").isCreativeMode()) && (keyBind == "GRAVITY_MANIPULATION" && !entity.isSneaking() && entity.getHeldItem().isEmpty() && (entity.getWornChestplate().nbt().getString("userUUID") == entity.getUUID())));
        }
        if (keyBind == "func_REVERT") {
            return entity.getData("tmf:dyn/timeout") == false && entity.as("PLAYER").isCreativeMode();
        }
        if (keyBind == "AIM") {
            return typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("canAim") && alien.ext.canAim(entity);
        }
        return typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("isKeyBindEnabled") && alien.ext.isKeyBindEnabled(entity, keyBind);
    };

    var canAim = entity => {
        var alien = getAlien(entity);
        if (alien == null) {
            return false;
        }
        return typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("canAim") && alien.ext.canAim(entity);
    };

    var getDefaultScale = entity => {
        var alien = getAlien(entity);
        if (alien != null && typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("getDefaultScale")) {
            return alien.ext.getDefaultScale(entity);
        }
        return 1.0;
    };

    var getTierOverride = entity => {
        var alien = getAlien(entity);
        if (alien != null && typeof alien.ext !== "undefined" && alien.ext.hasOwnProperty("getTierOverride")) {
            return alien.ext.getTierOverride(entity);
        }
        return 0;
    };

    return {
        init: hero => {
            hero.addKeyBind("GRAVITY_MANIPULATION", "\u00A7aSelect Alien", 5);
            hero.addKeyBindFunc("func_REVERT", revertKey, "\u00A7aRevert", 5);
            hero.addKeyBindFunc("func_COLOR", colorKey, "\u00A7aChange Color", 5);

            externals.forEach(key => key.forEach(k => k.init(hero)));

            hero.setModifierEnabled(isModifierEnabled);
            hero.setKeyBindEnabled(isKeyBindEnabled);
            hero.setHasPermission(hasPermission);
            hero.setHasProperty(hasProperty);
            hero.setDefaultScale(getDefaultScale);
            hero.supplyFunction("canAim", canAim);

            hero.setAttributeProfile(getAttributeProfile);
            hero.setDamageProfile(getDamageProfile);
            hero.setTierOverride(getTierOverride);

            hero.setTickHandler(tickHandler);
        }
    };
}

function revertKey(player, manager) {
    if (isTransformed(player)) {
        manager.setData(player, "tmf:dyn/timeout", true);
        manager.setData(player, "tmf:dyn/timeout2", true);
        return true;
    }
    return false;
}

function colorKey(player, manager) {
    var nbt = player.getWornChestplate().nbt();
    var currentColor = nbt.getInteger("Color");
    manager.setInteger(nbt, "Color", currentColor == 360 ? 0 : currentColor + 6);
    return true;
}