// 
// Decompiled by Procyon v0.6.0
// 

package com.google.common.flogger.backend;

import java.util.ArrayList;
import java.util.List;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Iterator;
import java.util.AbstractSet;
import com.google.common.flogger.util.Checks;
import java.util.Collections;
import java.util.Set;
import com.google.common.flogger.MetadataKey;

public abstract class MetadataProcessor
{
    private static final MetadataProcessor EMPTY_PROCESSOR;
    
    public static MetadataProcessor forScopeAndLogSite(final Metadata scopeMetadata, final Metadata logMetadata) {
        final int totalSize = scopeMetadata.size() + logMetadata.size();
        if (totalSize == 0) {
            return MetadataProcessor.EMPTY_PROCESSOR;
        }
        if (totalSize <= 28) {
            return getLightweightProcessor(scopeMetadata, logMetadata);
        }
        return getSimpleProcessor(scopeMetadata, logMetadata);
    }
    
    static MetadataProcessor getLightweightProcessor(final Metadata scope, final Metadata logged) {
        return new LightweightProcessor(scope, logged);
    }
    
    static MetadataProcessor getSimpleProcessor(final Metadata scope, final Metadata logged) {
        return new SimpleProcessor(scope, logged);
    }
    
    private MetadataProcessor() {
    }
    
    public abstract <C> void process(final MetadataHandler<C> p0, final C p1);
    
    public abstract <C> void handle(final MetadataKey<?> p0, final MetadataHandler<C> p1, final C p2);
    
    public abstract <T> T getSingleValue(final MetadataKey<T> p0);
    
    public abstract int keyCount();
    
    public abstract Set<MetadataKey<?>> keySet();
    
    static {
        EMPTY_PROCESSOR = new MetadataProcessor() {
            @Override
            public <C> void process(final MetadataHandler<C> handler, final C context) {
            }
            
            @Override
            public <C> void handle(final MetadataKey<?> key, final MetadataHandler<C> handler, final C context) {
            }
            
            @Override
            public <T> T getSingleValue(final MetadataKey<T> key) {
                return null;
            }
            
            @Override
            public int keyCount() {
                return 0;
            }
            
            @Override
            public Set<MetadataKey<?>> keySet() {
                return Collections.emptySet();
            }
        };
    }
    
    private static final class LightweightProcessor extends MetadataProcessor
    {
        private static final int MAX_LIGHTWEIGHT_ELEMENTS = 28;
        private final Metadata scope;
        private final Metadata logged;
        private final int[] keyMap;
        private final int keyCount;
        
        private LightweightProcessor(final Metadata scope, final Metadata logged) {
            super(null);
            this.scope = Checks.checkNotNull(scope, "scope metadata");
            this.logged = Checks.checkNotNull(logged, "logged metadata");
            final int maxKeyCount = scope.size() + logged.size();
            Checks.checkArgument(maxKeyCount <= 28, "metadata size too large");
            this.keyMap = new int[maxKeyCount];
            this.keyCount = this.prepareKeyMap(this.keyMap);
        }
        
        @Override
        public <C> void process(final MetadataHandler<C> handler, final C context) {
            for (int i = 0; i < this.keyCount; ++i) {
                final int n = this.keyMap[i];
                this.dispatch(this.getKey(n & 0x1F), n, handler, context);
            }
        }
        
        @Override
        public <C> void handle(final MetadataKey<?> key, final MetadataHandler<C> handler, final C context) {
            final int index = this.indexOf(key, this.keyMap, this.keyCount);
            if (index >= 0) {
                this.dispatch(key, this.keyMap[index], handler, context);
            }
        }
        
        @Override
        public <T> T getSingleValue(final MetadataKey<T> key) {
            Checks.checkArgument(!key.canRepeat(), "key must be single valued");
            final int index = this.indexOf(key, this.keyMap, this.keyCount);
            return (index >= 0) ? key.cast(this.getValue(this.keyMap[index])) : null;
        }
        
        @Override
        public int keyCount() {
            return this.keyCount;
        }
        
        @Override
        public Set<MetadataKey<?>> keySet() {
            return new AbstractSet<MetadataKey<?>>() {
                @Override
                public int size() {
                    return LightweightProcessor.this.keyCount;
                }
                
                @Override
                public Iterator<MetadataKey<?>> iterator() {
                    return new Iterator<MetadataKey<?>>() {
                        private int i = 0;
                        
                        @Override
                        public boolean hasNext() {
                            return this.i < LightweightProcessor.this.keyCount;
                        }
                        
                        @Override
                        public MetadataKey<?> next() {
                            return LightweightProcessor.this.getKey(LightweightProcessor.this.keyMap[this.i++] & 0x1F);
                        }
                    };
                }
            };
        }
        
        private <T, C> void dispatch(final MetadataKey<T> key, final int n, final MetadataHandler<C> handler, final C context) {
            if (!key.canRepeat()) {
                handler.handle(key, key.cast(this.getValue(n)), context);
            }
            else {
                handler.handleRepeated(key, new ValueIterator<T>((MetadataKey)key, n), context);
            }
        }
        
        private int prepareKeyMap(final int[] keyMap) {
            long bloomFilterMask = 0L;
            int count = 0;
            for (int n = 0; n < keyMap.length; ++n) {
                final MetadataKey<?> key = this.getKey(n);
                final long oldMask = bloomFilterMask;
                bloomFilterMask |= key.getBloomFilterMask();
                if (bloomFilterMask == oldMask) {
                    final int i = this.indexOf(key, keyMap, count);
                    if (i != -1) {
                        keyMap[i] = (key.canRepeat() ? (keyMap[i] | 1 << n + 4) : n);
                        continue;
                    }
                }
                keyMap[count++] = n;
            }
            return count;
        }
        
        private int indexOf(final MetadataKey<?> key, final int[] keyMap, final int count) {
            for (int i = 0; i < count; ++i) {
                if (key.equals(this.getKey(keyMap[i] & 0x1F))) {
                    return i;
                }
            }
            return -1;
        }
        
        private MetadataKey<?> getKey(final int n) {
            final int scopeSize = this.scope.size();
            return (n >= scopeSize) ? this.logged.getKey(n - scopeSize) : this.scope.getKey(n);
        }
        
        private Object getValue(final int n) {
            final int scopeSize = this.scope.size();
            return (n >= scopeSize) ? this.logged.getValue(n - scopeSize) : this.scope.getValue(n);
        }
        
        private final class ValueIterator<T> implements Iterator<T>
        {
            private final MetadataKey<T> key;
            private int nextIndex;
            private int mask;
            
            private ValueIterator(final MetadataKey<T> key, final int valueIndices) {
                this.key = key;
                this.nextIndex = (valueIndices & 0x1F);
                this.mask = valueIndices >>> 5 + this.nextIndex;
            }
            
            @Override
            public boolean hasNext() {
                return this.nextIndex >= 0;
            }
            
            @Override
            public T next() {
                final T next = this.key.cast(LightweightProcessor.this.getValue(this.nextIndex));
                if (this.mask != 0) {
                    final int skip = 1 + Integer.numberOfTrailingZeros(this.mask);
                    this.mask >>>= skip;
                    this.nextIndex += skip;
                }
                else {
                    this.nextIndex = -1;
                }
                return next;
            }
        }
    }
    
    private static final class SimpleProcessor extends MetadataProcessor
    {
        private final Map<MetadataKey<?>, Object> map;
        
        private SimpleProcessor(final Metadata scope, final Metadata logged) {
            super(null);
            final LinkedHashMap<MetadataKey<?>, Object> map = new LinkedHashMap<MetadataKey<?>, Object>();
            addTo(map, scope);
            addTo(map, logged);
            for (final Map.Entry<MetadataKey<?>, Object> e : map.entrySet()) {
                if (e.getKey().canRepeat()) {
                    e.setValue(Collections.unmodifiableList((List<?>)e.getValue()));
                }
            }
            this.map = Collections.unmodifiableMap((Map<? extends MetadataKey<?>, ?>)map);
        }
        
        private static void addTo(final Map<MetadataKey<?>, Object> map, final Metadata metadata) {
            for (int i = 0; i < metadata.size(); ++i) {
                final MetadataKey<?> key = metadata.getKey(i);
                final Object value = map.get(key);
                if (key.canRepeat()) {
                    List<Object> list = (List<Object>)value;
                    if (list == null) {
                        list = new ArrayList<Object>();
                        map.put(key, list);
                    }
                    list.add(key.cast(metadata.getValue(i)));
                }
                else {
                    map.put(key, key.cast(metadata.getValue(i)));
                }
            }
        }
        
        @Override
        public <C> void process(final MetadataHandler<C> handler, final C context) {
            for (final Map.Entry<MetadataKey<?>, Object> e : this.map.entrySet()) {
                dispatch(e.getKey(), e.getValue(), handler, context);
            }
        }
        
        @Override
        public <C> void handle(final MetadataKey<?> key, final MetadataHandler<C> handler, final C context) {
            final Object value = this.map.get(key);
            if (value != null) {
                dispatch(key, value, handler, context);
            }
        }
        
        @Override
        public <T> T getSingleValue(final MetadataKey<T> key) {
            Checks.checkArgument(!key.canRepeat(), "key must be single valued");
            final Object value = this.map.get(key);
            return (T)((value != null) ? value : null);
        }
        
        @Override
        public int keyCount() {
            return this.map.size();
        }
        
        @Override
        public Set<MetadataKey<?>> keySet() {
            return this.map.keySet();
        }
        
        private static <T, C> void dispatch(final MetadataKey<T> key, final Object value, final MetadataHandler<C> handler, final C context) {
            if (key.canRepeat()) {
                handler.handleRepeated(key, ((List)value).iterator(), context);
            }
            else {
                handler.handle((MetadataKey<Object>)key, value, context);
            }
        }
    }
}
