package com.boydti.fawe.bukkit.wrapper;

import com.boydti.fawe.FaweAPI;
import com.boydti.fawe.bukkit.v0.BukkitQueue_0;
import com.boydti.fawe.object.FaweQueue;
import com.boydti.fawe.object.HasFaweQueue;
import com.boydti.fawe.object.RunnableVal;
import com.boydti.fawe.object.queue.DelegateFaweQueue;
import com.boydti.fawe.util.SetQueue;
import com.boydti.fawe.util.StringMan;
import com.boydti.fawe.util.TaskManager;
import com.sk89q.worldedit.bukkit.WorldEditPlugin;
import com.sk89q.worldedit.bukkit.adapter.BukkitImplAdapter;
import com.sk89q.worldedit.function.operation.Operation;
import com.sk89q.worldedit.world.biome.BaseBiome;
import java.io.File;
import java.lang.reflect.Field;
import java.util.Collection;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.bukkit.BlockChangeDelegate;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.ChunkSnapshot;
import org.bukkit.Difficulty;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.Sound;
import org.bukkit.SoundCategory;
import org.bukkit.TreeType;
import org.bukkit.World;
import org.bukkit.WorldBorder;
import org.bukkit.WorldCreator;
import org.bukkit.WorldType;
import org.bukkit.block.Biome;
import org.bukkit.block.Block;
import org.bukkit.entity.Arrow;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.FallingBlock;
import org.bukkit.entity.Item;
import org.bukkit.entity.LightningStrike;
import org.bukkit.entity.LivingEntity;
import org.bukkit.entity.Player;
import org.bukkit.generator.BlockPopulator;
import org.bukkit.generator.ChunkGenerator;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.metadata.MetadataValue;
import org.bukkit.plugin.Plugin;
import org.bukkit.util.Consumer;
import org.bukkit.util.Vector;

/**
 * Modify the world from an async thread<br>
 *  - Use world.commit() to execute all the changes<br>
 *  - Any Chunk/Block/BlockState objects returned should also be safe to use from the same async thread<br>
 *  - Only block read,write and biome write are fast, other methods will perform slower async<br>
 *  -
 *  @see #wrap(org.bukkit.World)
 *  @see #create(org.bukkit.WorldCreator)
 */
public class AsyncWorld extends DelegateFaweQueue implements World, HasFaweQueue {

    private World parent;
    private FaweQueue queue;
    private BukkitImplAdapter adapter;

    @Override
    public <T> void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6, T t) {
        parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, v6, t);
    }

    /**
     * @deprecated use {@link #wrap(org.bukkit.World)} instead
     * @param parent Parent world
     * @param autoQueue
     */
    @Deprecated
    public AsyncWorld(World parent, boolean autoQueue) {
        this(parent, FaweAPI.createQueue(parent.getName(), autoQueue));
    }

    public AsyncWorld(String world, boolean autoQueue) {
        this(Bukkit.getWorld(world), autoQueue);
    }

    /**
     * @deprecated use {@link #wrap(org.bukkit.World)} instead
     * @param parent
     * @param queue
     */
    @Deprecated
    public AsyncWorld(World parent, FaweQueue queue) {
        super(queue);
        this.parent = parent;
        this.queue = queue;
        if (queue instanceof BukkitQueue_0) {
            this.adapter = (BukkitImplAdapter) ((BukkitQueue_0) queue).getAdapter();
        } else {
            try {
                WorldEditPlugin instance = (WorldEditPlugin) Bukkit.getPluginManager().getPlugin("WorldEdit");
                Field fieldAdapter = WorldEditPlugin.class.getDeclaredField("bukkitAdapter");
                fieldAdapter.setAccessible(true);
                this.adapter = (BukkitImplAdapter) fieldAdapter.get(instance);
            } catch (Throwable e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * Wrap a world for async usage
     * @param world
     * @return
     */
    public static AsyncWorld wrap(World world) {
        if (world instanceof AsyncWorld) {
            return (AsyncWorld) world;
        }
        return new AsyncWorld(world, false);
    }

    public void changeWorld(World world, FaweQueue queue) {
        this.parent = world;
        if (queue != this.queue) {
            if (this.queue != null) {
                final FaweQueue oldQueue = this.queue;
                TaskManager.IMP.async(new Runnable() {
                    @Override
                    public void run() {
                        oldQueue.flush();
                    }
                });
            }
            this.queue = queue;
        }
        setParent(queue);
    }

    @Override
    public String toString() {
        return super.toString() + ":" + queue.toString();
    }

    public World getBukkitWorld() {
        return parent;
    }

    public FaweQueue getQueue() {
        return queue;
    }

    /**
     * Create a world async (untested)
     *  - Only optimized for 1.10
     * @param creator
     * @return
     */
    public synchronized static AsyncWorld create(final WorldCreator creator) {
        BukkitQueue_0 queue = (BukkitQueue_0) SetQueue.IMP.getNewQueue(creator.name(), true, false);
        World world = queue.createWorld(creator);
        return wrap(world);
    }

    public Operation commit() {
        flush();
        return null;
    }

    public void flush() {
        if (queue != null) {
            queue.flush();
        }
    }

    @Override
    public WorldBorder getWorldBorder() {
        return TaskManager.IMP.sync(new RunnableVal<WorldBorder>() {
            @Override
            public void run(WorldBorder value) {
                this.value = parent.getWorldBorder();
            }
        });
    }

    @Override
    public void spawnParticle(Particle particle, Location location, int i) {
        parent.spawnParticle(particle, location, i);
    }

    @Override
    public void spawnParticle(Particle particle, double v, double v1, double v2, int i) {
        parent.spawnParticle(particle, v, v1, v2, i);
    }

    @Override
    public <T> void spawnParticle(Particle particle, Location location, int i, T t) {
        parent.spawnParticle(particle, location, i, t);
    }

    @Override
    public <T> void spawnParticle(Particle particle, double v, double v1, double v2, int i, T t) {
        parent.spawnParticle(particle, v, v1, v2, i, t);
    }

    @Override
    public void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2) {
        parent.spawnParticle(particle, location, i, v, v1, v2);
    }

    @Override
    public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5) {
        parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5);
    }

    @Override
    public <T> void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, T t) {
        parent.spawnParticle(particle, location, i, v, v1, v2, t);
    }

    @Override
    public <T> void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, T t) {
        parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, t);
    }

    @Override
    public void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, double v3) {
        parent.spawnParticle(particle, location, i, v, v1, v2, v3);
    }

    @Override
    public void spawnParticle(Particle particle, double v, double v1, double v2, int i, double v3, double v4, double v5, double v6) {
        parent.spawnParticle(particle, v, v1, v2, i, v3, v4, v5, v6);
    }

    @Override
    public <T> void spawnParticle(Particle particle, Location location, int i, double v, double v1, double v2, double v3, T t) {
        parent.spawnParticle(particle, location, i, v, v1, v2, v3, t);
    }

    @Override
    public int getEntityCount() {
        return TaskManager.IMP.sync(new RunnableVal<Integer>() {
            @Override
            public void run(Integer value) {
                this.value = parent.getEntityCount();
            }
        });
    }

    @Override
    public int getTileEntityCount() {
        return TaskManager.IMP.sync(new RunnableVal<Integer>() {
            @Override
            public void run(Integer value) {
                this.value = parent.getTileEntityCount();
            }
        });
    }

    @Override
    public int getTickableTileEntityCount() {
        return TaskManager.IMP.sync(new RunnableVal<Integer>() {
            @Override
            public void run(Integer value) {
                this.value = parent.getTickableTileEntityCount();
            }
        });
    }

    @Override
    public int getChunkCount() {
        return TaskManager.IMP.sync(new RunnableVal<Integer>() {
            @Override
            public void run(Integer value) {
                this.value = parent.getChunkCount();
            }
        });
    }

    @Override
    public int getPlayerCount() {
        return TaskManager.IMP.sync(new RunnableVal<Integer>() {
            @Override
            public void run(Integer value) {
                this.value = parent.getPlayerCount();
            }
        });
    }

    @Override
    public Block getBlockAt(final int x, final int y, final int z) {
        return new AsyncBlock(this, queue, x, y, z);
    }

    @Override
    public Block getBlockAt(Location loc) {
        return getBlockAt(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
    }

    @Override
    @Deprecated
    public int getBlockTypeIdAt(int x, int y, int z) {
        return queue.getCachedCombinedId4Data(x, y & 0xFF, z, 0) >> 4;
    }

    @Override
    @Deprecated
    public int getBlockTypeIdAt(Location loc) {
        return getBlockTypeIdAt(loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
    }

    @Override
    public int getHighestBlockYAt(int x, int z) {
        for (int y = getMaxHeight() - 1; y >= 0; y--) {
            if (queue.getCachedCombinedId4Data(x, y, z, 0) != 0) {
                return y;
            }
        }
        return 0;
    }

    @Override
    public int getHighestBlockYAt(Location loc) {
        return getHighestBlockYAt(loc.getBlockX(), loc.getBlockZ());
    }

    @Override
    public Block getHighestBlockAt(int x, int z) {
        int y = getHighestBlockYAt(x, z);
        return getBlockAt(x, y, z);
    }

    @Override
    public Block getHighestBlockAt(Location loc) {
        return getHighestBlockAt(loc.getBlockX(), loc.getBlockZ());
    }

    @Override
    public Chunk getChunkAt(int x, int z) {
        return new AsyncChunk(this, queue, x, z);
    }

    @Override
    public Chunk getChunkAt(Location location) {
        return getChunkAt(location.getBlockX(), location.getBlockZ());
    }

    @Override
    public Chunk getChunkAt(Block block) {
        return getChunkAt(block.getX(), block.getZ());
    }

    @Override
    public void getChunkAtAsync(int x, int z, ChunkLoadCallback cb) {
        parent.getChunkAtAsync(x, z, cb);
    }

    @Override
    public void getChunkAtAsync(Location location, ChunkLoadCallback cb) {
        parent.getChunkAtAsync(location, cb);
    }

    @Override
    public void getChunkAtAsync(Block block, ChunkLoadCallback cb) {
        parent.getChunkAtAsync(block, cb);
    }

    @Override
    public boolean isChunkLoaded(Chunk chunk) {
        return chunk.isLoaded();
    }

    @Override
    public Chunk[] getLoadedChunks() {
        return parent.getLoadedChunks();
    }

    @Override
    public void loadChunk(final Chunk chunk) {
        if (!chunk.isLoaded()) {
            TaskManager.IMP.sync(new RunnableVal<Object>() {
                @Override
                public void run(Object value) {
                    parent.loadChunk(chunk);
                }
            });
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null || !(obj instanceof World)) {
            return false;
        }
        World other = (World) obj;
        return StringMan.isEqual(other.getName(), getName());
    }

    @Override
    public int hashCode() {
        return this.getUID().hashCode();
    }

    @Override
    public boolean isChunkLoaded(int x, int z) {
        return parent.isChunkLoaded(x, z);
    }

    @Override
    public boolean isChunkInUse(int x, int z) {
        return parent.isChunkInUse(x, z);
    }

    @Override
    public void loadChunk(final int x, final int z) {
        if (!isChunkLoaded(x, z)) {
            TaskManager.IMP.sync(new RunnableVal<Object>() {
                @Override
                public void run(Object value) {
                    parent.loadChunk(x, z);
                }
            });
        }
    }

    @Override
    public boolean loadChunk(final int x, final int z, final boolean generate) {
        if (!isChunkLoaded(x, z)) {
            return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
                @Override
                public void run(Boolean value) {
                    this.value = parent.loadChunk(x, z, generate);
                }
            });
        }
        return true;
    }

    @Override
    public boolean unloadChunk(final Chunk chunk) {
        if (chunk.isLoaded()) {
            return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
                @Override
                public void run(Boolean value) {
                    this.value = parent.unloadChunk(chunk);
                }
            });
        }
        return true;
    }

    @Override
    public boolean unloadChunk(int x, int z) {
        return unloadChunk(x, z, true);
    }

    @Override
    public boolean unloadChunk(int x, int z, boolean save) {
        return unloadChunk(x, z, save, false);
    }

    @Deprecated
    @Override
    public boolean unloadChunk(final int x, final int z, final boolean save, final boolean safe) {
        if (isChunkLoaded(x, z)) {
            return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
                @Override
                public void run(Boolean value) {
                    this.value = parent.unloadChunk(x, z, save, safe);
                }
            });
        }
        return true;
    }

    @Override
    public boolean unloadChunkRequest(int x, int z) {
        return unloadChunk(x, z);
    }

    @Override
    public boolean unloadChunkRequest(int x, int z, boolean safe) {
        return unloadChunk(x, z, safe);
    }

    @Override
    public boolean regenerateChunk(final int x, final int z) {
        return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
            @Override
            public void run(Boolean value) {
               this.value = parent.regenerateChunk(x, z);
            }
        });
    }

    @Override
    @Deprecated
    public boolean refreshChunk(int x, int z) {
        queue.sendChunk(queue.getFaweChunk(x, z));
        return true;
    }

    @Override
    public Item dropItem(final Location location, final ItemStack item) {
        return TaskManager.IMP.sync(new RunnableVal<Item>() {
            @Override
            public void run(Item value) {
                this.value = parent.dropItem(location, item);
            }
        });
    }

    @Override
    public Item dropItemNaturally(final Location location, final ItemStack item) {
        return TaskManager.IMP.sync(new RunnableVal<Item>() {
            @Override
            public void run(Item value) {
                this.value = parent.dropItemNaturally(location, item);
            }
        });
    }

    @Override
    public Arrow spawnArrow(final Location location, final Vector direction, final float speed, final float spread) {
        return TaskManager.IMP.sync(new RunnableVal<Arrow>() {
            @Override
            public void run(Arrow value) {
                this.value = parent.spawnArrow(location, direction, speed, spread);
            }
        });
    }

    @Override
    public <T extends Arrow> T spawnArrow(Location location, Vector vector, float v, float v1, Class<T> aClass) {
        return parent.spawnArrow(location, vector, v, v1, aClass);
    }

    @Override
    public boolean generateTree(final Location location, final TreeType type) {
        return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
            @Override
            public void run(Boolean value) {
                this.value = parent.generateTree(location, type);
            }
        });
    }

    @Override
    public boolean generateTree(final Location loc, final TreeType type, final BlockChangeDelegate delegate) {
        return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
            @Override
            public void run(Boolean value) {
                this.value = parent.generateTree(loc, type, delegate);
            }
        });
    }

    @Override
    public Entity spawnEntity(Location loc, EntityType type) {
        return spawn(loc, type.getEntityClass());
    }

    @Override
    public LightningStrike strikeLightning(final Location loc) {
        return TaskManager.IMP.sync(new RunnableVal<LightningStrike>() {
            @Override
            public void run(LightningStrike value) {
                this.value = parent.strikeLightning(loc);
            }
        });
    }

    @Override
    public LightningStrike strikeLightningEffect(final Location loc) {
        return TaskManager.IMP.sync(new RunnableVal<LightningStrike>() {
            @Override
            public void run(LightningStrike value) {
                this.value = parent.strikeLightningEffect(loc);
            }
        });
    }

    @Override
    public List getEntities() {
        return TaskManager.IMP.sync(new RunnableVal<List<Entity>>() {
            @Override
            public void run(List<Entity> value) {
                this.value = parent.getEntities();
            }
        });
    }

    @Override
    public List<LivingEntity> getLivingEntities() {
        return TaskManager.IMP.sync(new RunnableVal<List<LivingEntity>>() {
            @Override
            public void run(List<LivingEntity> value) {
                this.value = parent.getLivingEntities();
            }
        });
    }

    @Override
    @Deprecated
    public <T extends Entity> Collection<T> getEntitiesByClass(final Class<T>... classes) {
        return TaskManager.IMP.sync(new RunnableVal<Collection<T>>() {
            @Override
            public void run(Collection<T> value) {
                this.value = (Collection<T>) parent.getEntitiesByClass(classes);
            }
        });
    }

    @Override
    public <T extends Entity> Collection<T> getEntitiesByClass(final Class<T> cls) {
        return TaskManager.IMP.sync(new RunnableVal<Collection<T>>() {
            @Override
            public void run(Collection<T> value) {
                this.value = (Collection<T>) parent.getEntitiesByClass(cls);
            }
        });
    }

    @Override
    public Collection<Entity> getEntitiesByClasses(final Class<?>... classes) {
        return TaskManager.IMP.sync(new RunnableVal<Collection<Entity>>() {
            @Override
            public void run(Collection<Entity> value) {
                this.value = parent.getEntitiesByClasses(classes);
            }
        });
    }

    @Override
    public List<Player> getPlayers() {
        return TaskManager.IMP.sync(new RunnableVal<List<Player>>() {
            @Override
            public void run(List<Player> value) {
                this.value = parent.getPlayers();
            }
        });
    }

    @Override
    public Collection<Entity> getNearbyEntities(final Location location, final double x, final double y, final double z) {
        return TaskManager.IMP.sync(new RunnableVal<Collection<Entity>>() {
            @Override
            public void run(Collection<Entity> value) {
                this.value = parent.getNearbyEntities(location, x, y, z);
            }
        });
    }

    @Override
    public String getName() {
        return parent.getName();
    }

    @Override
    public UUID getUID() {
        return parent.getUID();
    }

    @Override
    public Location getSpawnLocation() {
        return parent.getSpawnLocation();
    }

    @Override
    public boolean setSpawnLocation(final int x, final int y, final int z) {
        return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
            @Override
            public void run(Boolean value) {
                this.value = parent.setSpawnLocation(x, y, z);
            }
        });
    }

    @Override
    public long getTime() {
        return parent.getTime();
    }

    @Override
    public void setTime(long time) {
        parent.setTime(time);
    }

    @Override
    public long getFullTime() {
        return parent.getFullTime();
    }

    @Override
    public void setFullTime(long time) {
        parent.setFullTime(time);
    }

    @Override
    public boolean hasStorm() {
        return parent.hasStorm();
    }

    @Override
    public void setStorm(boolean hasStorm) {
        parent.setStorm(hasStorm);
    }

    @Override
    public int getWeatherDuration() {
        return parent.getWeatherDuration();
    }

    @Override
    public void setWeatherDuration(int duration) {
        parent.setWeatherDuration(duration);
    }

    @Override
    public boolean isThundering() {
        return parent.isThundering();
    }

    @Override
    public void setThundering(boolean thundering) {
        parent.setThundering(thundering);
    }

    @Override
    public int getThunderDuration() {
        return parent.getThunderDuration();
    }

    @Override
    public void setThunderDuration(int duration) {
        parent.setThunderDuration(duration);
    }

    public boolean createExplosion(double x, double y, double z, float power) {
        return this.createExplosion(x, y, z, power, false, true);
    }

    public boolean createExplosion(double x, double y, double z, float power, boolean setFire) {
        return this.createExplosion(x, y, z, power, setFire, true);
    }

    public boolean createExplosion(final double x, final double y, final double z, final float power, final boolean setFire, final boolean breakBlocks) {
        return TaskManager.IMP.sync(new RunnableVal<Boolean>() {
            @Override
            public void run(Boolean value) {
                this.value = parent.createExplosion(x, y, z, power, setFire, breakBlocks);
            }
        });
    }

    public boolean createExplosion(Location loc, float power) {
        return this.createExplosion(loc, power, false);
    }

    public boolean createExplosion(Location loc, float power, boolean setFire) {
        return this.createExplosion(loc.getX(), loc.getY(), loc.getZ(), power, setFire);
    }

    @Override
    public Environment getEnvironment() {
        return parent.getEnvironment();
    }

    @Override
    public long getSeed() {
        return parent.getSeed();
    }

    @Override
    public boolean getPVP() {
        return parent.getPVP();
    }

    @Override
    public void setPVP(boolean pvp) {
        parent.setPVP(pvp);
    }

    @Override
    public ChunkGenerator getGenerator() {
        return parent.getGenerator();
    }

    @Override
    public void save() {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.save();
            }
        });
    }

    @Override
    public List<BlockPopulator> getPopulators() {
        return parent.getPopulators();
    }

    @Override
    public <T extends Entity> T spawn(final Location location, final Class<T> clazz) throws IllegalArgumentException {
        return TaskManager.IMP.sync(new RunnableVal<T>() {
            @Override
            public void run(T value) {
                this.value = parent.spawn(location, clazz);
            }
        });
    }

    @Override
    public <T extends Entity> T spawn(Location location, Class<T> clazz, Consumer<T> function) throws IllegalArgumentException {
        return TaskManager.IMP.sync(new RunnableVal<T>() {
            @Override
            public void run(T value) {
                this.value = parent.spawn(location, clazz, function);
            }
        });
    }

    @Override
    public FallingBlock spawnFallingBlock(Location location, MaterialData data) throws IllegalArgumentException {
        return TaskManager.IMP.sync(new RunnableVal<FallingBlock>() {
            @Override
            public void run(FallingBlock value) {
                this.value = parent.spawnFallingBlock(location, data);
            }
        });
    }

    @Override
    @Deprecated
    public FallingBlock spawnFallingBlock(Location location, Material material, byte data) throws IllegalArgumentException {
        return this.spawnFallingBlock(location, material.getId(), data);
    }

    @Override
    @Deprecated
    public FallingBlock spawnFallingBlock(final Location location, final int blockId, final byte blockData) throws IllegalArgumentException {
        return TaskManager.IMP.sync(new RunnableVal<FallingBlock>() {
            @Override
            public void run(FallingBlock value) {
                this.value = parent.spawnFallingBlock(location, blockId, blockData);
            }
        });
    }

    @Override
    public void playEffect(Location location, Effect effect, int data) {
        this.playEffect(location, effect, data, 64);
    }

    @Override
    public void playEffect(final Location location, final Effect effect, final int data, final int radius) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.playEffect(location, effect, data, radius);
            }
        });
    }

    @Override
    public <T> void playEffect(Location loc, Effect effect, T data) {
        this.playEffect(loc, effect, data, 64);
    }

    @Override
    public <T> void playEffect(final Location location, final Effect effect, final T data, final int radius) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.playEffect(location, effect, data, radius);
            }
        });
    }

    @Override
    public ChunkSnapshot getEmptyChunkSnapshot(final int x, final int z, final boolean includeBiome, final boolean includeBiomeTempRain) {
        return TaskManager.IMP.sync(new RunnableVal<ChunkSnapshot>() {
            @Override
            public void run(ChunkSnapshot value) {
                this.value = parent.getEmptyChunkSnapshot(x, z, includeBiome, includeBiomeTempRain);
            }
        });
    }

    @Override
    public void setSpawnFlags(boolean allowMonsters, boolean allowAnimals) {
        parent.setSpawnFlags(allowMonsters, allowAnimals);
    }

    @Override
    public boolean getAllowAnimals() {
        return parent.getAllowAnimals();
    }

    @Override
    public boolean getAllowMonsters() {
        return parent.getAllowMonsters();
    }

    @Override
    public Biome getBiome(int x, int z) {
        return adapter.getBiome(queue.getBiomeId(x, z));
    }

    @Override
    public void setBiome(int x, int z, Biome bio) {
        int id = adapter.getBiomeId(bio);
        queue.setBiome(x, z, new BaseBiome(id));
    }

    @Override
    public double getTemperature(int x, int z) {
        return parent.getTemperature(x, z);
    }

    @Override
    public double getHumidity(int x, int z) {
        return parent.getHumidity(x, z);
    }

    @Override
    public int getMaxHeight() {
        return parent.getMaxHeight();
    }

    @Override
    public int getSeaLevel() {
        return parent.getSeaLevel();
    }

    @Override
    public boolean getKeepSpawnInMemory() {
        return parent.getKeepSpawnInMemory();
    }

    @Override
    public void setKeepSpawnInMemory(final boolean keepLoaded) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.setKeepSpawnInMemory(keepLoaded);
            }
        });
    }

    @Override
    public boolean isAutoSave() {
        return parent.isAutoSave();
    }

    @Override
    public void setAutoSave(boolean value) {
        parent.setAutoSave(value);
    }

    @Override
    public void setDifficulty(Difficulty difficulty) {
        parent.setDifficulty(difficulty);
    }

    @Override
    public Difficulty getDifficulty() {
        return parent.getDifficulty();
    }

    @Override
    public File getWorldFolder() {
        return parent.getWorldFolder();
    }

    @Override
    public WorldType getWorldType() {
        return parent.getWorldType();
    }

    @Override
    public boolean canGenerateStructures() {
        return parent.canGenerateStructures();
    }

    @Override
    public long getTicksPerAnimalSpawns() {
        return parent.getTicksPerAnimalSpawns();
    }

    @Override
    public void setTicksPerAnimalSpawns(int ticksPerAnimalSpawns) {
        parent.setTicksPerAnimalSpawns(ticksPerAnimalSpawns);
    }

    @Override
    public long getTicksPerMonsterSpawns() {
        return parent.getTicksPerMonsterSpawns();
    }

    @Override
    public void setTicksPerMonsterSpawns(int ticksPerMonsterSpawns) {
        parent.setTicksPerMonsterSpawns(ticksPerMonsterSpawns);
    }

    @Override
    public int getMonsterSpawnLimit() {
        return parent.getMonsterSpawnLimit();
    }

    @Override
    public void setMonsterSpawnLimit(int limit) {
        parent.setMonsterSpawnLimit(limit);
    }

    @Override
    public int getAnimalSpawnLimit() {
        return parent.getAnimalSpawnLimit();
    }

    @Override
    public void setAnimalSpawnLimit(int limit) {
        parent.setAnimalSpawnLimit(limit);
    }

    @Override
    public int getWaterAnimalSpawnLimit() {
        return parent.getWaterAnimalSpawnLimit();
    }

    @Override
    public void setWaterAnimalSpawnLimit(int limit) {
        parent.setWaterAnimalSpawnLimit(limit);
    }

    @Override
    public int getAmbientSpawnLimit() {
        return parent.getAmbientSpawnLimit();
    }

    @Override
    public void setAmbientSpawnLimit(int limit) {
        parent.setAmbientSpawnLimit(limit);
    }

    @Override
    public void playSound(final Location location, final Sound sound, final float volume, final float pitch) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.playSound(location, sound, volume, pitch);
            }
        });
    }

    @Override
    public void playSound(final Location location, final String sound, final float volume, final float pitch) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.playSound(location, sound, volume, pitch);
            }
        });
    }

    @Override
    public void playSound(Location location, Sound sound, SoundCategory category, float volume, float pitch) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.playSound(location, sound, category, volume, pitch);
            }
        });
    }

    @Override
    public void playSound(Location location, String sound, SoundCategory category, float volume, float pitch) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.playSound(location, sound, category, volume, pitch);
            }
        });
    }

    @Override
    public String[] getGameRules() {
        return parent.getGameRules();
    }

    @Override
    public String getGameRuleValue(String rule) {
        return parent.getGameRuleValue(rule);
    }

    @Override
    public boolean setGameRuleValue(String rule, String value) {
        return parent.setGameRuleValue(rule, value);
    }

    @Override
    public boolean isGameRule(String rule) {
        return parent.isGameRule(rule);
    }

    @Override
    public Spigot spigot() {
        return parent.spigot();
    }

    @Override
    public void setMetadata(final String key, final MetadataValue meta) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.setMetadata(key, meta);
            }
        });
    }

    @Override
    public List<MetadataValue> getMetadata(String key) {
        return parent.getMetadata(key);
    }

    @Override
    public boolean hasMetadata(String key) {
        return parent.hasMetadata(key);
    }

    @Override
    public void removeMetadata(final String key, final Plugin plugin) {
        TaskManager.IMP.sync(new RunnableVal<Object>() {
            @Override
            public void run(Object value) {
                parent.removeMetadata(key, plugin);
            }
        });
    }

    @Override
    public void sendPluginMessage(Plugin source, String channel, byte[] message) {
        parent.sendPluginMessage(source, channel, message);
    }

    @Override
    public Set<String> getListeningPluginChannels() {
        return parent.getListeningPluginChannels();
    }

    public BukkitImplAdapter getAdapter() {
        return adapter;
    }
}
