package org.monospark.spongematchers.matcher.sponge.block;

import java.util.Map;
import java.util.Objects;
import java.util.Map.Entry;
import java.util.Optional;

import org.monospark.spongematchers.matcher.BaseMatchers;
import org.monospark.spongematchers.matcher.SpongeMatcher;
import org.monospark.spongematchers.matcher.sponge.block.BlockTraitMatcher.Type;
import org.spongepowered.api.block.BlockState;
import org.spongepowered.api.block.BlockType;
import org.spongepowered.api.block.trait.BlockTrait;

import com.google.common.collect.Maps;

public final class BlockStateMatcher implements SpongeMatcher<BlockState> {

    private SpongeMatcher<BlockType> typeMatcher;
    
    private Map<String, BlockTraitMatcher<?>> traitMatchers;

    private BlockStateMatcher(SpongeMatcher<BlockType> typeMatcher, Map<String, BlockTraitMatcher<?>> traitMatchers) {
        this.typeMatcher = typeMatcher;
        this.traitMatchers = traitMatchers;
    }

    @Override
    public boolean matches(BlockState o) {
        if (!typeMatcher.matches(o.getType())) {
            return false;
        }

        for (Entry<String, BlockTraitMatcher<?>> traitMatcher : traitMatchers.entrySet()) {
            Optional<BlockTrait<?>> trait = o.getTrait(traitMatcher.getKey());
            if (!trait.isPresent()) {
                return false;
            }
            
            Optional<?> value = o.getTraitValue(trait.get());
            if (!matchesTrait(traitMatcher.getValue(), value.get())) {
                return false;
            }
        }
        return true;
    }
    
    @SuppressWarnings("unchecked")
    private <T> boolean matchesTrait(BlockTraitMatcher<T> matcher, Object value) {
        return matcher.getMatcher().matches((T) value);
    }
    
    public static Builder builder() {
        return new Builder();
    }
    
    public static final class Builder {
        
        private SpongeMatcher<BlockType> typeMatcher;
        
        private Map<String, BlockTraitMatcher<?>> traitMatchers;

        public Builder() {
            typeMatcher = BaseMatchers.wildcard();
            traitMatchers = Maps.newHashMap();
        }
        
        public Builder type(SpongeMatcher<BlockType> typeMatcher) {
            this.typeMatcher = Objects.requireNonNull(typeMatcher);
            return this;
        }
        
        public Builder addBooleanTrait(String name, SpongeMatcher<Boolean> traitMatcher) {
            traitMatchers.put(name, new BlockTraitMatcher<>(Type.BOOLEAN_TRAIT, traitMatcher));
            return this;
        }
        
        public Builder addIntegerTrait(String name, SpongeMatcher<Integer> traitMatcher) {
            traitMatchers.put(name, new BlockTraitMatcher<>(Type.INTEGER_TRAIT, traitMatcher));
            return this;
        }
        
        public Builder addEnumTrait(String name, SpongeMatcher<String> traitMatcher) {
            traitMatchers.put(name, new BlockTraitMatcher<>(Type.ENUM_TRAIT, traitMatcher));
            return this;
        }
        
        public SpongeMatcher<BlockState> build() {
            return new BlockStateMatcher(typeMatcher, traitMatchers);
        }
    }
}

@@ -92,23 +92,25 @@
 va.util.
-Objects
+Map.Entry
 ;%0Aimport
@@ -116,33 +116,31 @@
 t java.util.
-Map.Entry
+Objects
 ;%0Aimport jav
@@ -500,16 +500,105 @@
 ckTrait;
+%0Aimport org.spongepowered.api.data.DataQuery;%0Aimport org.spongepowered.api.data.DataView;
 %0A%0Aimport
@@ -819,24 +819,88 @@
 aitMatchers;
+%0A    %0A    private SpongeMatcher%3COptional%3CDataView%3E%3E dataMatcher;
 %0A%0A    privat
@@ -1004,16 +1004,75 @@
 Matchers
+,%0A            SpongeMatcher%3COptional%3CDataView%3E%3E dataMatcher
 ) %7B%0A    
@@ -1147,24 +1147,64 @@
 itMatchers;%0A
+        this.dataMatcher = dataMatcher;%0A
     %7D%0A%0A    @
@@ -1335,24 +1335,198 @@
 e;%0A        %7D
+%0A        %0A        Optional%3CDataView%3E data = o.toContainer().getView(DataQuery.of(%22UnsafeData%22));%0A        if (!dataMatcher.matches(data)) %7B%0A            return false;%0A        %7D
 %0A%0A        fo
@@ -2440,16 +2440,88 @@
 atchers;
+%0A        %0A        private SpongeMatcher%3COptional%3CDataView%3E%3E dataMatcher;
 %0A%0A      
@@ -2635,24 +2635,75 @@
 wHashMap();%0A
+            dataMatcher = BaseMatchers.wildcard();%0A
         %7D%0A  
@@ -3567,32 +3567,221 @@
      %7D%0A        %0A
+        public Builder data(SpongeMatcher%3COptional%3CDataView%3E%3E dataMatcher) %7B%0A            this.dataMatcher = Objects.requireNonNull(dataMatcher);%0A            return this;%0A        %7D%0A        %0A
         public S
@@ -3882,16 +3882,29 @@
 Matchers
+, dataMatcher
 );%0A     

Add a data matcher to the BlockState matcher