package com.minelittlepony.unicopia.spell;

import javax.annotation.Nullable;

import com.minelittlepony.unicopia.init.UMaterials;
import com.minelittlepony.util.MagicalDamageSource;
import com.minelittlepony.util.PosHelper;
import com.minelittlepony.util.collection.IStateMapping;
import com.minelittlepony.util.collection.StateMapList;
import com.minelittlepony.util.shape.IShape;
import com.minelittlepony.util.shape.Sphere;
import com.minelittlepony.util.vector.VecHelper;

import net.minecraft.block.Block;
import net.minecraft.block.BlockBush;
import net.minecraft.block.BlockLeaves;
import net.minecraft.block.BlockRedstoneWire;
import net.minecraft.block.BlockSnow;
import net.minecraft.block.material.Material;
import net.minecraft.block.state.IBlockState;
import net.minecraft.dispenser.IBlockSource;
import net.minecraft.entity.Entity;
import net.minecraft.entity.item.EntityTNTPrimed;
import net.minecraft.entity.player.EntityPlayer;
import net.minecraft.init.Blocks;
import net.minecraft.item.ItemStack;
import net.minecraft.util.math.BlockPos;
import net.minecraft.util.EnumFacing;
import net.minecraft.util.EnumParticleTypes;
import net.minecraft.world.World;

public class SpellIce extends AbstractSpell.RangedAreaSpell implements IUseAction, IDispenceable {

    public final StateMapList affected = new StateMapList();

    public SpellIce() {
        affected.add(IStateMapping.build(
                s -> s.getMaterial() == Material.WATER,
                s -> Blocks.ICE.getDefaultState()));
        affected.add(IStateMapping.build(
                s -> s.getMaterial() == Material.LAVA,
                s -> Blocks.OBSIDIAN.getDefaultState()));
        affected.add(IStateMapping.build(
                s -> s.getBlock() == Blocks.SNOW_LAYER,
                s -> {
                    s = s.cycleProperty(BlockSnow.LAYERS);
                    if (s.getValue(BlockSnow.LAYERS) >= 7) {
                        return Blocks.SNOW.getDefaultState();
                    }

                    return s;
                }));
        affected.replaceBlock(Blocks.FIRE, Blocks.AIR);
        affected.setProperty(Blocks.REDSTONE_WIRE, BlockRedstoneWire.POWER, 0);
    }

    private final int rad = 3;
    private final IShape effect_range = new Sphere(false, rad);

    @Override
    public String getName() {
        return "ice";
    }

    @Override
    public SpellAffinity getAffinity() {
        return SpellAffinity.GOOD;
    }

    @Override
    public int getTint() {
        return 0xBDBDF9;
    }

    @Override
    public boolean update(ICaster<?> source) {
        return false;
    }

    @Override
    public void render(ICaster<?> source) {
    }

    @Override
    public SpellCastResult onDispenced(BlockPos pos, EnumFacing facing, IBlockSource source, SpellAffinity affinity) {
        return applyBlocks(null, source.getWorld(), pos.offset(facing, rad)) ? SpellCastResult.NONE : SpellCastResult.DEFAULT;
    }

    @Override
    public SpellCastResult onUse(ItemStack stack, SpellAffinity affinity, EntityPlayer player, World world, BlockPos pos, EnumFacing side, float hitX, float hitY, float hitZ) {
        if (player != null && player.isSneaking()) {
            applyBlockSingle(world, pos);
        } else {
            applyBlocks(player, world, pos);
        }

        return SpellCastResult.DEFAULT;
    }

    @Override
    public SpellCastResult onUse(ItemStack stack, SpellAffinity affinity, EntityPlayer player, World world, @Nullable Entity hitEntity) {
        if (hitEntity != null && applyEntitySingle(player, hitEntity)) {
            return SpellCastResult.DEFAULT;
        }

        return SpellCastResult.NONE;
    }

    private boolean applyBlocks(EntityPlayer owner, World world, BlockPos pos) {

        for (BlockPos i : PosHelper.getAllInRegionMutable(pos, effect_range)) {
            applyBlockSingle(world, i);
        }

        return applyEntities(owner, world, pos);
    }

    protected boolean applyEntities(EntityPlayer owner, World world, BlockPos pos) {
        return VecHelper.findAllEntitiesInRange(owner, world, pos, 3).filter(i ->
            applyEntitySingle(owner, i)
        ).count() > 0;
    }

    protected boolean applyEntitySingle(EntityPlayer owner, Entity e) {
        if (e instanceof EntityTNTPrimed) {
            e.setDead();
            e.getEntityWorld().setBlockState(e.getPosition(), Blocks.TNT.getDefaultState());
        } else if (e.isBurning()) {
            e.extinguish();
        } else {
            e.attackEntityFrom(MagicalDamageSource.causePlayerDamage("cold", owner), 2);
        }

        return true;
    }

    private void applyBlockSingle(World world, BlockPos pos) {
        IBlockState state = world.getBlockState(pos);
        Block id = state.getBlock();

        IBlockState converted = affected.getConverted(state);

        if (!state.equals(converted)) {
            world.setBlockState(pos, converted, 3);
        } else if (state.getMaterial() != UMaterials.cloud && state.isSideSolid(world, pos, EnumFacing.UP)
                || (id == Blocks.SNOW)
                || (id instanceof BlockLeaves)) {
            incrementIce(world, pos.up());
        } else if (state.getMaterial() == Material.ICE && world.rand.nextInt(10) == 0) {
            if (isSurroundedByIce(world, pos)) {
                world.setBlockState(pos, Blocks.PACKED_ICE.getDefaultState());
            }
        }

        world.spawnParticle(EnumParticleTypes.WATER_SPLASH, pos.getX() + world.rand.nextFloat(), pos.getY() + 1, pos.getZ() + world.rand.nextFloat(), 0, 0, 0);
    }

    public static boolean isSurroundedByIce(World w, BlockPos pos) {
        return !PosHelper.adjacentNeighbours(pos).stream().anyMatch(i ->
            w.getBlockState(i).getMaterial() == Material.ICE
        );
    }

    private void incrementIce(World world, BlockPos pos) {
        IBlockState state = world.getBlockState(pos);
        Block id = state.getBlock();

        if (id == Blocks.AIR || (id instanceof BlockBush)) {
            world.setBlockState(pos, Blocks.SNOW_LAYER.getDefaultState(), 3);
        }
    }
}
