package gregtech.api.util;

import net.minecraft.entity.Entity;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.util.Vec3;
import net.minecraft.world.World;

import java.util.Objects;
import java.util.function.BiConsumer;
import java.util.function.Consumer;

@SuppressWarnings("unused")
public abstract class WorldSpawnedEventBuilder implements Runnable {
    private static final String ILLEGAL_STATE_STR1 = "Position, identifier and world must be set";
    /* Variables */

    private World world;

    /* Getters, Setters */

    public World getWorld() {
        return world;
    }

    public WorldSpawnedEventBuilder setWorld(World world) {
        this.world = world;
        return this;
    }

    /* Methodes */

    @SuppressWarnings("unchecked")
    public <U extends WorldSpawnedEventBuilder> void times(int times, Consumer<U> action) {
        Objects.requireNonNull(action);
        for (int i = 0; i < times; i++) {
            action.accept((U) this);
        }
    }

    @SuppressWarnings("unchecked")
    public <U extends WorldSpawnedEventBuilder> void times(int times, BiConsumer<U, Integer> action) {
        Objects.requireNonNull(action);
        for (int i = 0; i < times; i++) {
            action.accept((U) this, i);
        }
    }

    /* Interfaces */

    private interface IPositionedWorldSpawnedEvent {
        Vec3 getPosition();
        IPositionedWorldSpawnedEvent setPosition(Vec3 position);
        IPositionedWorldSpawnedEvent setPosition(double x, double y, double z);
    }

    private interface IEntityWorldSpawnedEvent {
        Entity getEntity();
        IEntityWorldSpawnedEvent setEntity(Entity entity);
    }

    private interface IEntityPlayerWorldSpawnedEvent {
        EntityPlayer getEntityPlayer();
        IEntityPlayerWorldSpawnedEvent setEntityPlayer(EntityPlayer entity);
    }

    private interface IStringIdentifierWorldSpawnedEvent {
         String getIdentifier();
         IStringIdentifierWorldSpawnedEvent setIdentifier(String identifier);
    }

    private interface ISoundWorldSpawnedEvent {
        float getPitch();
        float getVolume();
        ISoundWorldSpawnedEvent setPitch(float pitch);
        ISoundWorldSpawnedEvent setVolume(float volume);
    }

    /* Abstract Classes */

    private abstract static class EntityWorldSpawnedEventBuilder extends WorldSpawnedEventBuilder implements IEntityWorldSpawnedEvent {

        private Entity entity;

        @Override
        public Entity getEntity() {
            return entity;
        }

        @Override
        public EntityWorldSpawnedEventBuilder setEntity(Entity entity) {
            this.entity = entity;
            return this;
        }
    }

    private abstract static class PositionedEntityWorldSpawnedEventBuilder extends EntityWorldSpawnedEventBuilder implements IPositionedWorldSpawnedEvent {

        private Vec3 position;

        @Override
        public Vec3 getPosition() {
            return position;
        }

        @Override
        public PositionedEntityWorldSpawnedEventBuilder setPosition(Vec3 position) {
            this.position = position;
            return this;
        }

        @Override
        public PositionedEntityWorldSpawnedEventBuilder setPosition(double x, double y, double z) {
            this.position = Vec3.createVectorHelper(x, y, z);
            return this;
        }

    }

    private abstract static class PositionedWorldSpawnedEventBuilder extends WorldSpawnedEventBuilder implements IPositionedWorldSpawnedEvent {
        private Vec3 position;

        public Vec3 getPosition() {
            return position;
        }

        public PositionedWorldSpawnedEventBuilder setPosition(Vec3 position) {
            this.position = position;
            return this;
        }

        public PositionedWorldSpawnedEventBuilder setPosition(double x, double y, double z) {
            this.position = Vec3.createVectorHelper(x, y, z);
            return this;
        }
    }

    private abstract static class StringIdentifierPositionedWorldSpawnedEventBuilder extends PositionedWorldSpawnedEventBuilder implements IStringIdentifierWorldSpawnedEvent {
        private String identifier;

        @Override
        public String getIdentifier() {
            return identifier;
        }

        @Override
        public StringIdentifierPositionedWorldSpawnedEventBuilder setIdentifier(String identifier) {
            this.identifier = identifier;
            return this;
        }
    }

    private abstract static class SoundStringIdentifierPositionedWorldSpawnedEventBuilder extends StringIdentifierPositionedWorldSpawnedEventBuilder implements ISoundWorldSpawnedEvent {

        private float pitch;
        private float volume;

        @Override
        public float getPitch() {
            return pitch;
        }

        @Override
        public float getVolume() {
            return volume;
        }

        @Override
        public SoundStringIdentifierPositionedWorldSpawnedEventBuilder setPitch(float pitch) {
            this.pitch = pitch;
            return this;
        }

        @Override
        public SoundStringIdentifierPositionedWorldSpawnedEventBuilder setVolume(float volume) {
            this.volume = volume;
            return this;
        }
    }

    /* Implementations */

    public static final class ParticleEventBuilder extends StringIdentifierPositionedWorldSpawnedEventBuilder {

        private Vec3 motion;

        public Vec3 getMotion() {
            return motion;
        }

        public ParticleEventBuilder setMotion(double x, double y, double z) {
            this.motion = Vec3.createVectorHelper(x, y, z);
            return this;
        }

        public ParticleEventBuilder setMotion(Vec3 motion) {
            this.motion = motion;
            return this;
        }

        @Override
        public ParticleEventBuilder setWorld(World world) {
            return (ParticleEventBuilder) super.setWorld(world);
        }

        @Override
        public ParticleEventBuilder setPosition(Vec3 position) {
            return (ParticleEventBuilder) super.setPosition(position);
        }

        @Override
        public ParticleEventBuilder setPosition(double x, double y, double z) {
            return (ParticleEventBuilder) super.setPosition(x, y, z);
        }

        @Override
        public ParticleEventBuilder setIdentifier(String identifier) {
            return (ParticleEventBuilder) super.setIdentifier(identifier);
        }

        @Override
        public void run() {
            if (getPosition() == null || getIdentifier() == null || getMotion() == null || getWorld() == null)
                throw new IllegalStateException("Position, identifier, motion and world must be set");

            getWorld().spawnParticle(
                    getIdentifier(),
                    getPosition().xCoord, getPosition().yCoord, getPosition().zCoord,
                    getMotion().xCoord, getMotion().yCoord, getMotion().zCoord
            );
        }
    }

    public static final class SoundEffectEventBuilder extends SoundStringIdentifierPositionedWorldSpawnedEventBuilder {

        @Override
        public SoundEffectEventBuilder setWorld(World world) {
            return (SoundEffectEventBuilder) super.setWorld(world);
        }

        @Override
        public SoundEffectEventBuilder setPosition(Vec3 position) {
            return (SoundEffectEventBuilder) super.setPosition(position);
        }

        @Override
        public SoundEffectEventBuilder setPosition(double x, double y, double z) {
            return (SoundEffectEventBuilder) super.setPosition(x, y, z);
        }

        @Override
        public SoundEffectEventBuilder setIdentifier(String identifier) {
            return (SoundEffectEventBuilder) super.setIdentifier(identifier);
        }

        @Override
        public SoundEffectEventBuilder setPitch(float pitch) {
            return (SoundEffectEventBuilder) super.setPitch(pitch);
        }

        @Override
        public SoundEffectEventBuilder setVolume(float volume) {
            return (SoundEffectEventBuilder) super.setVolume(volume);
        }

        @Override
        public void run() {
            if (getPosition() == null || getIdentifier() == null || getWorld() == null)
                throw new IllegalStateException(ILLEGAL_STATE_STR1);

            getWorld().playSoundEffect(
                    getPosition().xCoord, getPosition().yCoord, getPosition().zCoord,
                    getIdentifier(),
                    getPitch(), getVolume()
            );
        }
    }

    public static final class SoundEventBuilder extends SoundStringIdentifierPositionedWorldSpawnedEventBuilder {

        private boolean proximity;

        public boolean isProximity() {
            return proximity;
        }

        @Override
        public SoundEventBuilder setWorld(World world) {
            return (SoundEventBuilder) super.setWorld(world);
        }

        @Override
        public SoundEventBuilder setPosition(Vec3 position) {
            return (SoundEventBuilder) super.setPosition(position);
        }

        @Override
        public SoundEventBuilder setPosition(double x, double y, double z) {
            return (SoundEventBuilder) super.setPosition(x, y, z);
        }

        @Override
        public SoundEventBuilder setIdentifier(String identifier) {
            return (SoundEventBuilder) super.setIdentifier(identifier);
        }

        @Override
        public SoundEventBuilder setPitch(float pitch) {
            return (SoundEventBuilder) super.setPitch(pitch);
        }

        @Override
        public SoundEventBuilder setVolume(float volume) {
            return (SoundEventBuilder) super.setVolume(volume);
        }

        public SoundEventBuilder setProximity(boolean proximity) {
            this.proximity = proximity;
            return this;
        }

        @Override
        public void run() {
            if (getPosition() == null || getIdentifier() == null || getWorld() == null)
                throw new IllegalStateException(ILLEGAL_STATE_STR1);

            getWorld().playSound(
                    getPosition().xCoord, getPosition().yCoord, getPosition().zCoord,
                    getIdentifier(),
                    getPitch(), getVolume(), isProximity()
            );
        }
    }

    /**
     * Positional Data is rounded down due to this targeting a block.
     */
    public static final class RecordEffectEventBuilder extends StringIdentifierPositionedWorldSpawnedEventBuilder {

        @Override
        public RecordEffectEventBuilder setWorld(World world) {
            return (RecordEffectEventBuilder) super.setWorld(world);
        }

        @Override
        public RecordEffectEventBuilder setPosition(Vec3 position) {
            return (RecordEffectEventBuilder) super.setPosition(position);
        }

        @Override
        public RecordEffectEventBuilder setPosition(double x, double y, double z) {
            return (RecordEffectEventBuilder) super.setPosition(x, y, z);
        }

        @Override
        public RecordEffectEventBuilder setIdentifier(String identifier) {
            return (RecordEffectEventBuilder) super.setIdentifier(identifier);
        }

        @Override
        public void run() {
            if (getPosition() == null || getIdentifier() == null || getWorld() == null)
                throw new IllegalStateException(ILLEGAL_STATE_STR1);

            getWorld().playRecord(
                    getIdentifier(),
                    (int) getPosition().xCoord,(int)  getPosition().yCoord,(int)  getPosition().zCoord
            );
        }
    }

    public static final class ExplosionEffectEventBuilder extends PositionedEntityWorldSpawnedEventBuilder {
        private boolean isFlaming, isSmoking;
        private float strength;


        public float getStrength() {
            return strength;
        }

        public ExplosionEffectEventBuilder setStrength(float strength) {
            this.strength = strength;
            return this;
        }

        public boolean isFlaming() {
            return isFlaming;
        }

        public ExplosionEffectEventBuilder setFlaming(boolean flaming) {
            isFlaming = flaming;
            return this;
        }

        public boolean isSmoking() {
            return isSmoking;
        }

        public ExplosionEffectEventBuilder setSmoking(boolean smoking) {
            isSmoking = smoking;
            return this;
        }

        @Override
        public ExplosionEffectEventBuilder setWorld(World world) {
            return (ExplosionEffectEventBuilder) super.setWorld(world);
        }

        @Override
        public ExplosionEffectEventBuilder setEntity(Entity entity) {
            return (ExplosionEffectEventBuilder) super.setEntity(entity);
        }

        @Override
        public ExplosionEffectEventBuilder setPosition(double x, double y, double z) {
            return (ExplosionEffectEventBuilder) super.setPosition(x, y, z);
        }

        @Override
        public void run() {
            if (getPosition() == null || getWorld() == null)
                throw new IllegalStateException("Position and world must be set");

            getWorld().newExplosion(getEntity(), getPosition().xCoord, getPosition().yCoord, getPosition().zCoord, strength, isFlaming, isSmoking);
        }
    }

    /**
     * Positional Data is rounded down due to this targeting a block.
     */
    public static final class ExtinguishFireEffectEventBuilder extends PositionedWorldSpawnedEventBuilder implements IEntityPlayerWorldSpawnedEvent {

        private int side;
        private EntityPlayer entityPlayer;

        public int getSide() {
            return side;
        }

        public ExtinguishFireEffectEventBuilder setSide(int side) {
            this.side = side;
            return this;
        }

        @Override
        public EntityPlayer getEntityPlayer() {
            return entityPlayer;
        }

        @Override
        public ExtinguishFireEffectEventBuilder setEntityPlayer(EntityPlayer entity) {
            this.entityPlayer = entity;
            return this;
        }

        @Override
        public ExtinguishFireEffectEventBuilder setWorld(World world) {
            return (ExtinguishFireEffectEventBuilder) super.setWorld(world);
        }

        @Override
        public ExtinguishFireEffectEventBuilder setPosition(Vec3 position) {
            return (ExtinguishFireEffectEventBuilder) super.setPosition(position);
        }

        @Override
        public ExtinguishFireEffectEventBuilder setPosition(double x, double y, double z) {
            return (ExtinguishFireEffectEventBuilder) super.setPosition(x, y, z);
        }

        @Override
        public void run() {
            if (getEntityPlayer() == null || getPosition() == null || getWorld() == null)
                throw new IllegalStateException("EntityPlayer, position and world must be set");

            getWorld().extinguishFire(getEntityPlayer(), (int)  getPosition().xCoord, (int)  getPosition().yCoord, (int)  getPosition().zCoord, side);
        }
    }

    public static final class SoundAtEntityEventBuilder extends EntityWorldSpawnedEventBuilder implements ISoundWorldSpawnedEvent, IStringIdentifierWorldSpawnedEvent {

        private float pitch;
        private float volume;
        private String identifier;

        @Override
        public String getIdentifier() {
            return identifier;
        }

        @Override
        public SoundAtEntityEventBuilder setIdentifier(String identifier) {
            this.identifier = identifier;
            return this;
        }

        @Override
        public float getPitch() {
            return pitch;
        }

        @Override
        public float getVolume() {
            return volume;
        }

        @Override
        public SoundAtEntityEventBuilder setPitch(float pitch) {
            this.pitch = pitch;
            return this;
        }

        @Override
        public SoundAtEntityEventBuilder setVolume(float volume) {
            this.volume = volume;
            return this;
        }

        @Override
        public SoundAtEntityEventBuilder setWorld(World world) {
            return (SoundAtEntityEventBuilder) super.setWorld(world);
        }

        @Override
        public SoundAtEntityEventBuilder setEntity(Entity entity) {
            return (SoundAtEntityEventBuilder) super.setEntity(entity);
        }

        @Override
        public void run() {
            if (getWorld() == null || getIdentifier()  == null || getEntity() == null)
                throw new IllegalStateException("World, Identifier and entity must be set!");

            getWorld().playSoundAtEntity(getEntity(), getIdentifier(), volume, pitch);
        }
    }

    public static final class SoundToNearExceptEventBuilder extends WorldSpawnedEventBuilder implements ISoundWorldSpawnedEvent, IStringIdentifierWorldSpawnedEvent, IEntityPlayerWorldSpawnedEvent {

        private float pitch;
        private float volume;
        private String identifier;
        private EntityPlayer entityPlayer;

        @Override
        public String getIdentifier() {
            return identifier;
        }

        @Override
        public SoundToNearExceptEventBuilder setIdentifier(String identifier) {
            this.identifier = identifier;
            return this;
        }

        @Override
        public float getPitch() {
            return pitch;
        }

        @Override
        public float getVolume() {
            return volume;
        }

        @Override
        public SoundToNearExceptEventBuilder setPitch(float pitch) {
            this.pitch = pitch;
            return this;
        }

        @Override
        public SoundToNearExceptEventBuilder setVolume(float volume) {
            this.volume = volume;
            return this;
        }

        @Override
        public SoundToNearExceptEventBuilder setWorld(World world) {
            return (SoundToNearExceptEventBuilder) super.setWorld(world);
        }

        @Override
        public void run() {
            if (getWorld() == null || getIdentifier()  == null || getEntityPlayer() == null)
                throw new IllegalStateException("World, Identifier and EntityPlayer must be set!");

            getWorld().playSoundAtEntity(getEntityPlayer(), getIdentifier(), volume, pitch);
        }

        @Override
        public EntityPlayer getEntityPlayer() {
            return entityPlayer;
        }

        @Override
        public SoundToNearExceptEventBuilder setEntityPlayer(EntityPlayer entity) {
            entityPlayer = entity;
            return this;
        }
    }
}