package tc.oc.pgm.blockdrops;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import javax.annotation.Nullable;

import com.google.common.collect.ImmutableList;
import org.bukkit.block.BlockState;
import org.bukkit.event.Event;
import org.bukkit.event.block.BlockBreakEvent;
import org.bukkit.inventory.ItemStack;
import org.bukkit.material.MaterialData;
import org.bukkit.util.BlockVector;
import tc.oc.commons.bukkit.event.BlockPunchEvent;
import tc.oc.commons.bukkit.event.BlockTrampleEvent;
import tc.oc.commons.bukkit.util.BlockStateUtils;
import tc.oc.commons.bukkit.util.NMSHacks;
import tc.oc.commons.core.util.Pair;
import tc.oc.pgm.filters.Filter;
import tc.oc.pgm.filters.query.IBlockQuery;
import tc.oc.pgm.filters.query.MaterialQuery;
import tc.oc.pgm.filters.query.PlayerBlockEventQuery;
import tc.oc.pgm.kits.Kit;
import tc.oc.pgm.kits.KitNode;
import tc.oc.pgm.match.ParticipantState;
import tc.oc.pgm.regions.FiniteBlockRegion;

public class BlockDropsRuleSet {
    private final ImmutableList<BlockDropsRule> rules;

    public BlockDropsRuleSet() {
        this(ImmutableList.of());
    }

    public BlockDropsRuleSet(List<BlockDropsRule> rules) {
        this.rules = ImmutableList.copyOf(rules);
    }

    public boolean isEmpty() {
        return this.rules.isEmpty();
    }

    public ImmutableList<BlockDropsRule> getRules() {
        return this.rules;
    }

    /**
     * Return the subset of rules that may act on the given region
     */
    public BlockDropsRuleSet subsetAffecting(FiniteBlockRegion region) {
        ImmutableList.Builder<BlockDropsRule> subset = ImmutableList.builder();
        for(BlockDropsRule rule : this.rules) {
            for(BlockVector block : region.getBlockVectors()) {
                if(rule.region == null || rule.region.contains(block)) {
                    subset.add(rule);
                    break;
                }
            }
        }

        return new BlockDropsRuleSet(subset.build());
    }

    /**
     * Return the subset of rules that may act on any of the given materials
     */
    public BlockDropsRuleSet subsetAffecting(Set<MaterialData> materials) {
        ImmutableList.Builder<BlockDropsRule> subset = ImmutableList.builder();
        for(BlockDropsRule rule : this.rules) {
            for(MaterialData material : materials) {
                if(rule.filter == null || rule.filter.query(MaterialQuery.of(material)) != Filter.QueryResponse.DENY) {
                    subset.add(rule);
                    break;
                }
            }
        }

        return new BlockDropsRuleSet(subset.build());
    }

    public BlockDrops getDrops(BlockState block, MaterialData material) {
        return this.getDrops(null, block, material, null);
    }

    public BlockDrops getDrops(@Nullable Event event, BlockState block, ParticipantState player) {
        return this.getDrops(event, block, block.getData(), player);
    }

    public BlockDrops getDrops(@Nullable Event event, BlockState block, MaterialData material, @Nullable ParticipantState playerState) {
        List<Pair<Double, ItemStack>> items = new ArrayList<>();
        List<Kit> kits = new ArrayList<>();
        MaterialData replacement = null;
        Float fallChance = null;
        Float landChance = null;
        double fallSpeed = 1;
        int experience = 0;
        boolean custom = false;
        block = BlockStateUtils.cloneWithMaterial(block.getBlock(), material);

        boolean rightToolUsed;
        if(event instanceof BlockBreakEvent) {
            rightToolUsed = NMSHacks.canMineBlock(material, ((BlockBreakEvent) event).getPlayer().getItemInHand());;
        } else {
            rightToolUsed = true;
        }

        for (BlockDropsRule rule : this.rules) {
            if(event instanceof BlockPunchEvent && !rule.punch) continue;
            if(event instanceof BlockTrampleEvent && !rule.trample) continue;
            if(rule.region != null && !rule.region.contains(block)) continue;

            if(rule.filter != null) {
                final IBlockQuery query = PlayerBlockEventQuery.of(block, Optional.ofNullable(event), Optional.ofNullable(playerState));
                if(!rule.filter.query(query).isAllowed()) continue;
            }

            custom = true;

            if(rule.drops.kit != null) {
                kits.add(rule.drops.kit);
            }

            if(rule.drops.replacement != null) {
                replacement = rule.drops.replacement;
            }

            if(rule.drops.fallChance != null) {
                fallChance = rule.drops.fallChance;
            }

            if(rule.drops.landChance != null) {
                landChance = rule.drops.landChance;
            }

            if(rule.drops.fallSpeed != null) {
                fallSpeed = rule.drops.fallSpeed;
            }

            if(rule.dropOnWrongTool || rightToolUsed) {
                items.addAll(rule.drops.items);
                experience += rule.drops.experience;
            }
        }

        return custom ? new BlockDrops(items, KitNode.of(kits), experience, replacement, fallChance, landChance, fallSpeed) : null;
    }
}
