/*
 * MetadataSystem.java
 *
 * Copyright (c) 2013 Mike Strobel
 *
 * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain;
 * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa.
 *
 * This source code is subject to terms and conditions of the Apache License, Version 2.0.
 * A copy of the license can be found in the License.html file at the root of this distribution.
 * By using this source code in any fashion, you are agreeing to be bound by the terms of the
 * Apache License, Version 2.0.
 *
 * You must not remove this notice, or any other, from this software.
 */

package com.strobel.assembler.metadata;

import com.strobel.compilerservices.RuntimeHelpers;
import com.strobel.core.Fences;
import com.strobel.core.VerifyArgument;

import java.util.concurrent.ConcurrentHashMap;

/**
 * @author Mike Strobel
 */
public class MetadataSystem extends MetadataResolver {
    private static MetadataSystem _instance;

    private final ConcurrentHashMap<String, TypeDefinition> _types;
    private final ITypeLoader _typeLoader;

    public static MetadataSystem instance() {
        if (_instance == null) {
            synchronized (MetadataSystem.class) {
                if (_instance == null) {
                    _instance = Fences.orderWrites(new MetadataSystem());
                }
            }
        }
        return _instance;
    }

    public MetadataSystem() {
        this(new ClasspathTypeLoader());
    }

    public MetadataSystem(final String classPath) {
        this(new ClasspathTypeLoader(VerifyArgument.notNull(classPath, "classPath")));
    }

    public MetadataSystem(final ITypeLoader typeLoader) {
        _typeLoader = VerifyArgument.notNull(typeLoader, "typeLoader");
        _types = new ConcurrentHashMap<>();
    }

    public void addTypeDefinition(final TypeDefinition type) {
        VerifyArgument.notNull(type, "type");
        _types.putIfAbsent(type.getInternalName(), type);
    }

    @Override
    protected TypeDefinition resolveCore(final TypeReference type) {
        VerifyArgument.notNull(type, "type");
        return resolveType(type.getInternalName());
    }

    @Override
    protected TypeReference lookupTypeCore(final String descriptor) {
        return resolveType(descriptor);
    }

    protected TypeDefinition resolveType(final String descriptor) {
        VerifyArgument.notNull(descriptor, "descriptor");

        if (descriptor.length() == 1) {
            final int primitiveHash = descriptor.charAt(0) - 'B';
            final TypeDefinition primitiveType = PRIMITIVE_TYPES_BY_DESCRIPTOR[primitiveHash];

            if (primitiveType != null) {
                return primitiveType;
            }
        }
        else {
            final int primitiveHash = hashPrimitiveName(descriptor);
            final TypeDefinition primitiveType = PRIMITIVE_TYPES_BY_NAME[primitiveHash];

            if (primitiveType != null && descriptor.equals(primitiveType.getName())) {
                return primitiveType;
            }
        }

        TypeDefinition cachedDefinition = _types.get(descriptor);

        if (cachedDefinition != null) {
            return cachedDefinition;
        }

        final Buffer buffer = new Buffer(0);

        if (!_typeLoader.tryLoadType(descriptor, buffer)) {
            return null;
        }

        final TypeDefinition typeDefinition = ClassFileReader.readClass(this, buffer);

        cachedDefinition = _types.putIfAbsent(descriptor, typeDefinition);
        typeDefinition.setTypeLoader(_typeLoader);

        if (cachedDefinition != null) {
            return cachedDefinition;
        }

        return typeDefinition;
    }

    // <editor-fold defaultstate="collapsed" desc="Primitive Lookup">

    private final static TypeDefinition[] PRIMITIVE_TYPES_BY_NAME = new TypeDefinition['Z' - 'B' + 1];
    private final static TypeDefinition[] PRIMITIVE_TYPES_BY_DESCRIPTOR = new TypeDefinition[16];

    static {
        RuntimeHelpers.ensureClassInitialized(BuiltinTypes.class);

        final TypeDefinition[] allPrimitives = {
            BuiltinTypes.Boolean,
            BuiltinTypes.Byte,
            BuiltinTypes.Character,
            BuiltinTypes.Short,
            BuiltinTypes.Integer,
            BuiltinTypes.Long,
            BuiltinTypes.Float,
            BuiltinTypes.Double,
            BuiltinTypes.Void
        };

        for (final TypeDefinition t : allPrimitives) {
            PRIMITIVE_TYPES_BY_DESCRIPTOR[hashPrimitiveName(t.getName())] = t;
            PRIMITIVE_TYPES_BY_NAME[t.getInternalName().charAt(0) - 'B'] = t;
        }
    }

    private static int hashPrimitiveName(final String name) {
        if (name.length() < 3) {
            return 0;
        }
        return (name.charAt(0) + name.charAt(2)) % 16;
    }

    // </editor-fold>
}

@@ -2599,16 +2599,117 @@
 - 'B';%0D%0A
+%0D%0A            if (primitiveHash %3E= 0 && primitiveHash %3C PRIMITIVE_TYPES_BY_DESCRIPTOR.length) %7B%0D%0A    
         
@@ -2802,32 +2802,36 @@
 %0D%0A%0D%0A            
+    
 if (primitiveTyp
@@ -2852,32 +2852,36 @@
                 
+    
 return primitive
@@ -2891,35 +2891,43 @@
 e;%0D%0A            
+ 
-%7D%0D%0A
+   %7D%0D%0A    
         %7D%0D%0A     
@@ -2924,21 +2924,32 @@
   %7D%0D%0A   
+ 
+    %7D%0D%0A
      
+   
 else %7B%0D%0A
@@ -3014,24 +3014,119 @@
 scriptor);%0D%0A
+%0D%0A            if (primitiveHash %3E= 0 && primitiveHash %3C PRIMITIVE_TYPES_BY_NAME.length) %7B%0D%0A    
             
@@ -3197,32 +3197,36 @@
 mitiveHash%5D;%0D%0A%0D%0A
+    
             if (
@@ -3305,32 +3305,36 @@
                 
+    
 return primitive
@@ -3332,32 +3332,51 @@
 primitiveType;%0D%0A
+                %7D%0D%0A
             %7D%0D%0A 

Fix for possible ArrayIndexOutOfRangeException when resolving types with single-character names. See issue #15.