/*
 * This file is part of Mixin, licensed under the MIT License (MIT).
 *
 * Copyright (c) SpongePowered <https://www.spongepowered.org>
 * Copyright (c) contributors
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
package org.spongepowered.asm.util;

import java.util.ArrayList;
import java.util.Deque;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.objectweb.asm.signature.SignatureReader;
import org.objectweb.asm.signature.SignatureVisitor;
import org.objectweb.asm.signature.SignatureWriter;
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.util.asm.ASM;

/**
 * Represents an object-oriented view of a generic class signature. We use ASM's
 * {@link SignatureVisitor} to walk over an incoming signature in order to parse
 * out our internal tree. This is done so that incoming signatures from mixins
 * can be merged into the target class.
 */
public class ClassSignature {
    
    protected static final String OBJECT = "java/lang/Object";
    
    /**
     * Lazy evaluation support for class signatures
     */
    static class Lazy extends ClassSignature {
        
        private final String sig;
        
        private ClassSignature generated;

        Lazy(String sig) {
            this.sig = sig;
        }
        
        @Override
        public ClassSignature wake() {
            if (this.generated == null) {
                this.generated = ClassSignature.of(this.sig);
            }
            return this.generated;
        }

    }
    
    /**
     * A renamable type variable handle
     */
    static class TypeVar implements Comparable<TypeVar> {
    
        /**
         * Original type var name
         */
        private final String originalName;
        
        /**
         * Current name (conformed)
         */
        private String currentName;
        
        TypeVar(String name) {
            this.currentName = this.originalName = name;
        }

        @Override
        public int compareTo(TypeVar other) {
            return this.currentName.compareTo(other.currentName);
        }
        
        @Override
        public String toString() {
            return this.currentName;
        }
        
        String getOriginalName() {
            return this.originalName;
        }
        
        void rename(String name) {
            this.currentName = name;
        }
        
        public boolean matches(String originalName) {
            return this.originalName.equals(originalName);
        }
        
        @Override
        public boolean equals(Object obj) {
            return this.currentName.equals(obj);
        }
        
        @Override
        public int hashCode() {
            return this.currentName.hashCode();
        }
        
    }

    /**
     * Interface for signature tokens
     */
    static interface IToken {

        /**
         * Available wildcard modifiers
         */
        static final String WILDCARDS = "+-";
        
        /**
         * Return this token as a string representation of its type
         */
        public abstract String asType();
        
        /**
         * Return this token as a string representation of a bound
         */
        public abstract String asBound();

        /**
         * Return this token as a hard token object
         */
        public abstract Token asToken();
        
        /**
         * Set the arrayness status of this token, logical OR is applied between
         * the current internal arrayness flag and the incoming flag.
         * 
         * @param array arrayness flag
         * @return this token
         */
        public abstract IToken setArray(boolean array);
        
        /**
         * Set the wildcard on this token, only accepted if one of the valid
         * {@link #WILDCARDS}
         * 
         * @param wildcard wildcard to set
         * @return this token
         */
        public abstract IToken setWildcard(char wildcard);
        
    }
    
    /**
     * A token in the signature, can represent a type, a collection of bounds,
     * or a combination of the two. Includes internal arrayness and wildcard
     * flags as well as generic type arguments.
     */
    static class Token implements IToken {
        
        /**
         * Valid symbols, supports the {@link IToken#WILDCARDS} as well as
         * <tt>*</tt> for unbounded type args. 
         */
        static final String SYMBOLS = IToken.WILDCARDS + "*";
        
        /**
         * True if this token represents an inner type (output is not enclosed
         * in <tt>L;</tt>
         */
        private final boolean inner;
        
        /**
         * True if this token represents an array type
         */
        private boolean array;
        
        /**
         * Symbol (wildcard, unbound or base type)
         */
        private char symbol = 0;
        
        /**
         * Type descriptor, can be null if this type is only a symbol
         */
        private String type;
        
        /**
         * Class bounds on this token, null if no bounds have been set
         */
        private List<Token> classBound;
        
        /**
         * Interface bounds on this token, null if no bounds have been set
         */
        private List<Token> ifaceBound;
        
        /**
         * Generic signature components on this token, null if this is a raw
         * type token
         */
        private List<IToken> signature;
        
        /**
         * Suffix elements on this token, currently only used to append inner
         * class tokens to this token
         */
        private List<IToken> suffix;
        
        /**
         * The last suffix element added. When a suffix element is added all
         * calls to <tt>add...</tt> are delegated to the tail element.
         */
        private Token tail;
        
        Token() {
            this(false);
        }
        
        Token(String type) {
            this(type, false);
        }
        
        Token(char symbol) {
            this();
            this.symbol = symbol;
        }
        
        Token(boolean inner) {
            this(null, inner);
        }
        
        Token(String type, boolean inner) {
            this.inner = inner;
            this.type = type;
        }
        
        Token setSymbol(char symbol) {
            if (this.symbol == 0 && Token.SYMBOLS.indexOf(symbol) > -1) {
                this.symbol = symbol;
            }
            return this;
        }
        
        Token setType(String type) {
            if (this.type == null) {
                this.type = type;
            }
            return this;
        }
        
        boolean hasClassBound() {
            return this.classBound != null;
        }
        
        boolean hasInterfaceBound() {
            return this.ifaceBound != null;
        }
        
        @Override
        public IToken setArray(boolean array) {
            this.array |= array;
            return this;
        }
        
        @Override
        public IToken setWildcard(char wildcard) {
            if (IToken.WILDCARDS.indexOf(wildcard) == -1) {
                return this;
            }
            return this.setSymbol(wildcard);
        }
        
        private List<Token> getClassBound() {
            if (this.classBound == null) {
                this.classBound = new ArrayList<Token>();
            }
            return this.classBound;
        }
        
        private List<Token> getIfaceBound() {
            if (this.ifaceBound == null) {
                this.ifaceBound = new ArrayList<Token>();
            }
            return this.ifaceBound;
        }
        
        private List<IToken> getSignature() {
            if (this.signature == null) {
                this.signature = new ArrayList<IToken>();
            }
            return this.signature;
        }
        
        private List<IToken> getSuffix() {
            if (this.suffix == null) {
                this.suffix = new ArrayList<IToken>();
            }
            return this.suffix;
        }
        
        /**
         * Add a type argument to this symbol
         * 
         * @param symbol argument to add
         * @return new token
         */
        IToken addTypeArgument(char symbol) {
            if (this.tail != null) {
                return this.tail.addTypeArgument(symbol);
            }
            
            Token token = new Token(symbol);
            this.getSignature().add(token);
            return token;
        }

        /**
         * Add a type argument to this symbol
         * 
         * @param name argument to add
         * @return new token
         */
        IToken addTypeArgument(String name) {
            if (this.tail != null) {
                return this.tail.addTypeArgument(name);
            }
            
            Token token = new Token(name);
            this.getSignature().add(token);
            return token;
        }
        
        /**
         * Add a type argument to this symbol
         * 
         * @param token argument to add
         * @return new token
         */
        IToken addTypeArgument(Token token) {
            if (this.tail != null) {
                return this.tail.addTypeArgument(token);
            }
            
            this.getSignature().add(token);
            return token;
        }
        
        /**
         * Add a type argument to this symbol
         * 
         * @param token argument to add
         * @return new token
         */
        IToken addTypeArgument(TokenHandle token) {
            if (this.tail != null) {
                return this.tail.addTypeArgument(token);
            }
            
            TokenHandle handle = token.clone();
            this.getSignature().add(handle);
            return handle;
        }
        
        /**
         * Add a class or interface bound to this symbol
         * 
         * @param bound bound specifier
         * @param classBound true to add a class bound, false to add an
         *      interface bound
         * @return new token
         */
        Token addBound(String bound, boolean classBound) {
            if (classBound) {
                return this.addClassBound(bound);
            }
            
            return this.addInterfaceBound(bound);
        }

        /**
         * Add a class bound to this symbol
         * 
         * @param bound bound specifier
         * @return new token
         */
        Token addClassBound(String bound) {
            Token token = new Token(bound);
            this.getClassBound().add(token);
            return token;
        }

        /**
         * Add a class bound to this symbol
         * 
         * @param bound bound specifier
         * @return new token
         */
        Token addInterfaceBound(String bound) {
            Token token = new Token(bound);
            this.getIfaceBound().add(token);
            return token;
        }

        /**
         * Add an inner class suffix to this symbol
         * 
         * @param name inner class name
         * @return new tail token
         */
        Token addInnerClass(String name) {
            this.tail = new Token(name, true);
            this.getSuffix().add(this.tail);
            return this.tail;
        }
        
        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return this.asType();
        }
        
        /* (non-Javadoc)
         * @see org.spongepowered.asm.util.ClassSignature.IToken#asBound()
         */
        @Override
        public String asBound() {
            StringBuilder sb = new StringBuilder();
            
            if (this.type != null) {
                sb.append(this.type);
            }
            
            if (this.classBound != null) {
                for (Token token : this.classBound) {
                    sb.append(token.asType());
                }
            }
            
            if (this.ifaceBound != null) {
                for (Token token : this.ifaceBound) {
                    sb.append(':').append(token.asType());
                }
            }
            
            return sb.toString();
        }

        /* (non-Javadoc)
         * @see org.spongepowered.asm.util.ClassSignature.IToken#asType()
         */
        @Override
        public String asType() {
            return this.asType(false);
        }
        
        /**
         * Convert this token to a string representation of its type, optionally
         * generate only a raw representation (no generic signature)
         * 
         * @param raw if true, only emits the raw type
         * @return string representation of this token
         */
        public String asType(boolean raw) {
            StringBuilder sb = new StringBuilder();
            
            if (this.array) {
                sb.append('[');
            }
            
            if (this.symbol != 0) {
                sb.append(this.symbol);
            }
            
            if (this.type == null) {
                return sb.toString();
            }

            if (!this.inner) {
                sb.append('L');
            }
            
            sb.append(this.type);
            
            if (!raw) {
                if (this.signature != null) {
                    sb.append('<');
                    for (IToken token : this.signature) {
                        sb.append(token.asType());
                    }
                    sb.append('>');
                }
                
                if (this.suffix != null) {
                    for (IToken token : this.suffix) {
                        sb.append('.').append(token.asType());
                    }
                }
            }
            
            if (!this.inner) {
                sb.append(';');
            }
            
            return sb.toString();
        }
        
        boolean isRaw() {
            return this.signature == null;
        }
        
        String getClassType() {
            return this.type != null ? this.type : ClassSignature.OBJECT;
        }

        @Override
        public Token asToken() {
            return this;
        }

    }
    
    /**
     * TokenHandle is used to provide indirection to {@link Token}, primarily
     * for storing mappings of TypeVars to tokens but also to store Tokens as
     * type parameters on other Tokens where we may need to include a wildcard
     * or array marker without altering the underlying Token.
     */
    class TokenHandle implements IToken {
        
        /**
         * Inner token, never null
         */
        final Token token;
        
        /**
         * True if this handle is an array type
         */
        boolean array;
        
        /**
         * Wildcard for this handle
         */
        char wildcard;
        
        TokenHandle() {
            this(new Token());
        }

        TokenHandle(Token token) {
            this.token = token;
        }
        
        /* (non-Javadoc)
         * @see org.spongepowered.asm.util.ClassSignature.IToken
         *      #setArray(boolean)
         */
        @Override
        public IToken setArray(boolean array) {
            this.array |= array;
            return this;
        }

        /* (non-Javadoc)
         * @see org.spongepowered.asm.util.ClassSignature.IToken
         *      #setWildcard(char)
         */
        @Override
        public IToken setWildcard(char wildcard) {
            if (IToken.WILDCARDS.indexOf(wildcard) > -1) {
                this.wildcard = wildcard;
            }
            return this;
        }
        
        /* (non-Javadoc)
         * @see org.spongepowered.asm.util.ClassSignature.IToken#asBound()
         */
        @Override
        public String asBound() {
            return this.token.asBound();
        }

        /* (non-Javadoc)
         * @see org.spongepowered.asm.util.ClassSignature.IToken#asType()
         */
        @Override
        public String asType() {
            StringBuilder sb = new StringBuilder();
            
            if (this.wildcard > 0) {
                sb.append(this.wildcard);
            }
            
            if (this.array) {
                sb.append('[');
            }
            
            return sb.append(ClassSignature.this.getTypeVar(this)).toString();
        }
        
        /* (non-Javadoc)
         * @see org.spongepowered.asm.util.ClassSignature.IToken#asToken()
         */
        @Override
        public Token asToken() {
            return this.token;
        }
        
        /* (non-Javadoc)
         * @see java.lang.Object#toString()
         */
        @Override
        public String toString() {
            return this.token.toString();
        }
        
        /* (non-Javadoc)
         * @see java.lang.Object#clone()
         */
        @Override
        public TokenHandle clone() {
            return new TokenHandle(this.token);
        }
        
    }
    
    /**
     * Base visitor for parsing signatures using {@link SignatureReader}, a
     * reference to instances of this class are deliberately not maintained in
     * order to free the memory consumed by the visitors after parsing.
     */
    class SignatureParser extends SignatureVisitor {
        
        /**
         * Base for the various element visitors used
         */
        abstract class SignatureElement extends SignatureVisitor {
            
            public SignatureElement() {
                super(ASM.API_VERSION);
            }
            
        }
    
        /**
         * Base class for elements which propagate information through to an
         * underlying token
         */
        abstract class TokenElement extends SignatureElement {
            
            /**
             * The token for this element
             */
            protected Token token;
            
            /**
             * True if the next element to be appended is an array element
             */
            private boolean array;
    
            public Token getToken() {
                if (this.token == null) {
                    this.token = new Token();
                }
                return this.token;
            }
            
            protected void setArray() {
                this.array = true;
            }
    
            private boolean getArray() {
                boolean array = this.array;
                this.array = false;
                return array;
            }
            
            @Override
            public void visitClassType(String name) {
                this.getToken().setType(name);
            }
            
            @Override
            public SignatureVisitor visitClassBound() {
                this.getToken();
                return new BoundElement(this, true);
            }
            
            @Override
            public SignatureVisitor visitInterfaceBound() {
                this.getToken();
                return new BoundElement(this, false);
            }
    
            @Override
            public void visitInnerClassType(String name) {
                this.token.addInnerClass(name);
            }
            
            @Override
            public SignatureVisitor visitArrayType() {
                this.setArray();
                return this;
            }
    
            @Override
            public SignatureVisitor visitTypeArgument(char wildcard) {
                return new TypeArgElement(this, wildcard);
            }
    
            Token addTypeArgument() {
                return this.token.addTypeArgument('*').asToken();
            }
            
            IToken addTypeArgument(char symbol) {
                return this.token.addTypeArgument(symbol).setArray(this.getArray());
            }
            
            IToken addTypeArgument(String name) {
                return this.token.addTypeArgument(name).setArray(this.getArray());
            }
    
            IToken addTypeArgument(Token token) {
                return this.token.addTypeArgument(token).setArray(this.getArray());
            }
            
            IToken addTypeArgument(TokenHandle token) {
                return this.token.addTypeArgument(token).setArray(this.getArray());
            }
            
        }
        
        /**
         * A formal type parameter
         */
        class FormalParamElement extends TokenElement {
            
            /**
             * Handle to the underlying token for this type
             */
            private final TokenHandle handle;
            
            FormalParamElement(String param) {
                this.handle = ClassSignature.this.getType(param);
                this.token = this.handle.asToken();
            }
            
        }
        
        /**
         * A type argument
         */
        class TypeArgElement extends TokenElement {
            
            /**
             * Element that this is an argument for
             */
            private final TokenElement type;
            
            /**
             * True if the next token appended has a wildcard qualifier
             */
            private final char wildcard;
    
            TypeArgElement(TokenElement type, char wildcard) {
                this.type = type;
                this.wildcard = wildcard;
            }
            
            @Override
            public SignatureVisitor visitArrayType() {
                this.type.setArray();
                return this;
            }
            
            @Override
            public void visitBaseType(char descriptor) {
                this.token = this.type.addTypeArgument(descriptor).asToken();
            }
            
            @Override
            public void visitTypeVariable(String name) {
                TokenHandle token = ClassSignature.this.getType(name);
                this.token = this.type.addTypeArgument(token).setWildcard(this.wildcard).asToken();
            }
            
            @Override
            public void visitClassType(String name) {
                this.token = this.type.addTypeArgument(name).setWildcard(this.wildcard).asToken();
            }
            
            @Override
            public void visitTypeArgument() {
                this.token.addTypeArgument('*');
            }
            
            @Override
            public SignatureVisitor visitTypeArgument(char wildcard) {
                return new TypeArgElement(this, wildcard);
            }
            
            @Override
            public void visitEnd() {
            }
            
        }
    
        /**
         * A class (or interface) bound element
         */
        class BoundElement extends TokenElement {
            
            /**
             * The element which owns this bound
             */
            private final TokenElement type;
            
            /**
             * True if this element represents a class bound, false if interface
             */
            private final boolean classBound;
    
            BoundElement(TokenElement type, boolean classBound) {
                this.type = type;
                this.classBound = classBound;
            }
            
            @Override
            public void visitClassType(String name) {
                this.token = this.type.token.addBound(name, this.classBound);
            }
            
            @Override
            public void visitTypeArgument() {
                this.token.addTypeArgument('*');
            }
            
            @Override
            public SignatureVisitor visitTypeArgument(char wildcard) {
                return new TypeArgElement(this, wildcard);
            }
            
        }
        
        /**
         * Superclass element
         */
        class SuperClassElement extends TokenElement {
            
            @Override
            public void visitEnd() {
                ClassSignature.this.setSuperClass(this.token);
            }
            
        }
    
        /**
         * Interface element
         */
        class InterfaceElement extends TokenElement {
            
            @Override
            public void visitEnd() {
                ClassSignature.this.addInterface(this.token);
            }
            
        }
        
        /**
         * The params element. For some reason {@link #visitFormalTypeParameter}
         * doesn't return a visitor so we need to proxy the relevant calls
         * through to the current params element.
         */
        private FormalParamElement param;

        SignatureParser() {
            super(ASM.API_VERSION);
        }
        
        @Override
        public void visitFormalTypeParameter(String name) {
            this.param = new FormalParamElement(name);
        }

        @Override
        public SignatureVisitor visitClassBound() {
            return this.param.visitClassBound();
        }

        @Override
        public SignatureVisitor visitInterfaceBound() {
            return this.param.visitInterfaceBound();
        }

        @Override
        public SignatureVisitor visitSuperclass() {
            return new SuperClassElement();
        }

        @Override
        public SignatureVisitor visitInterface() {
            return new InterfaceElement();
        }

    }
    
    /**
     * Remapper
     */
    class SignatureRemapper extends SignatureWriter {
        
        private final Set<String> localTypeVars = new HashSet<String>();
        
        @Override
        public void visitFormalTypeParameter(String name) {
            this.localTypeVars.add(name);
            super.visitFormalTypeParameter(name);
        }
        
        @Override
        public void visitTypeVariable(String name) {
            if (!this.localTypeVars.contains(name)) {
                TypeVar typeVar = ClassSignature.this.getTypeVar(name);
                if (typeVar != null) {
                    super.visitTypeVariable(typeVar.toString());
                    return;
                }
            }
            super.visitTypeVariable(name);
        }
        
    }
    
    /**
     * Type vars defined in this signature, represents the list of formal
     * parameters, ordered
     */
    private final Map<TypeVar, TokenHandle> types = new LinkedHashMap<TypeVar, TokenHandle>();
    
    /**
     * The superclass defined in the signature
     */
    private Token superClass = new Token(ClassSignature.OBJECT);
    
    /**
     * Interfaces mentioned in the signature
     */
    private final List<Token> interfaces = new ArrayList<Token>();
    
    /**
     * Interfaces added manually
     */
    private final Deque<String> rawInterfaces = new LinkedList<String>();
    
    ClassSignature() {
    }
    
    /**
     * Use a {@link SignatureReader} to visit the supplied signature and build
     * the signature model
     * 
     * @param signature signature string to parse
     * @return fluent interface
     */
    private ClassSignature read(String signature) {
        if (signature != null) {
            try {
                new SignatureReader(signature).accept(new SignatureParser());
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
        return this;
    }
    
    /**
     * Get the type var for the specified var name
     * 
     * @param varName type var to lookup
     * @return type var for the supplied type var name
     */
    protected TypeVar getTypeVar(String varName) {
        for (TypeVar typeVar : this.types.keySet()) {
            if (typeVar.matches(varName)) {
                return typeVar;
            }
        }
        return null;
    }

    /**
     * Get the token for the specified type var name, creating it if necessary
     * 
     * @param varName type var to lookup
     * @return type token for the supplied type var name
     */
    protected TokenHandle getType(String varName) {
        for (TypeVar typeVar : this.types.keySet()) {
            if (typeVar.matches(varName)) {
                return this.types.get(typeVar);
            }
        }
        
        TokenHandle handle = new TokenHandle();
        this.types.put(new TypeVar(varName), handle);
        return handle;
    }

    /**
     * Get the type var matching the supplied type token, or the raw token type
     * if no mapping exists for the supplied token handle
     * 
     * @param handle type token handle to lookup
     * @return type var (with prefix and suffix) or raw type name
     */
    protected String getTypeVar(TokenHandle handle) {
        for (Entry<TypeVar, TokenHandle> type : this.types.entrySet()) {
            TypeVar typeVar = type.getKey();
            TokenHandle typeHandle = type.getValue();
            if (handle == typeHandle || handle.asToken() == typeHandle.asToken()) {
                return "T" + typeVar + ";";
            }
        }
        return handle.token.asType();
    }
    
    /**
     * Add a type var to this signature, the type var must not exist 
     * 
     * @param typeVar type var to add
     * @param handle type var's type token
     * @throws IllegalArgumentException if the specified type var already exists
     */
    protected void addTypeVar(TypeVar typeVar, TokenHandle handle) throws IllegalArgumentException {
        if (this.types.containsKey(typeVar)) {
            throw new IllegalArgumentException("TypeVar " + typeVar + " is already present on " + this);
        }
        
        this.types.put(typeVar, handle);
    }

    /**
     * Set the superclass for this signature
     * 
     * @param superClass super class to set
     */
    protected void setSuperClass(Token superClass) {
        this.superClass = superClass;
    }

    /**
     * Get the raw superclass type of this signature as a string
     * 
     * @return superclass type as a string
     */
    public String getSuperClass() {
        return this.superClass.asType(true);
    }

    /**
     * Add an interface to this signature
     * 
     * @param iface interface to add
     */
    protected void addInterface(Token iface) {
        if (!iface.isRaw()) {
            String raw = iface.asType(true);
            for (ListIterator<Token> iter = this.interfaces.listIterator(); iter.hasNext();) {
                Token intrface = iter.next();
                if (intrface.isRaw() && intrface.asType(true).equals(raw)) {
                    iter.set(iface);
                    return;
                }
            }
        }
        
        this.interfaces.add(iface);
    }
    
    /**
     * Add a raw interface declaration to this signature
     * 
     * @param iface interface name to add (bin format)
     */
    public void addInterface(String iface) {
        this.rawInterfaces.add(iface);
    }
    
    /**
     * Add a raw interface which was previously enqueued
     * 
     * @param iface interface to add
     */
    protected void addRawInterface(String iface) {
        Token token = new Token(iface);
        String raw = token.asType(true);
        for (Token intrface : this.interfaces) {
            if (intrface.asType(true).equals(raw)) {
                return;
            }
        }
        this.interfaces.add(token);
    }
    
    /**
     * Merges another class signature into this one. The other signature is
     * first conformed so that no formal type parameters overlap with formal
     * type parameters defined on this signature. No attempt is made to combine
     * formal type parameters, this method merely ensures that parameters do
     * not overlap.
     * 
     * @param other Class signature to merge into this one
     */
    public void merge(ClassSignature other) {
        try {
            Set<String> typeVars = new HashSet<String>();
            for (TypeVar typeVar : this.types.keySet()) {
                typeVars.add(typeVar.toString());
            }
            
            other.conform(typeVars);
        } catch (IllegalStateException ex) {
            // Oh crap, this means we couldn't conform one or more type params!
            ex.printStackTrace();
            return;
        }

        for (Entry<TypeVar, TokenHandle> type : other.types.entrySet()) {
            this.addTypeVar(type.getKey(), type.getValue());
        }
        
        for (Token iface : other.interfaces) {
            this.addInterface(iface);
        }
    }

    private void conform(Set<String> typeVars) {
        for (TypeVar typeVar : this.types.keySet()) {
            String name = this.findUniqueName(typeVar.getOriginalName(), typeVars);
            typeVar.rename(name);
            typeVars.add(name);
        }
    }
    
    /**
     * Finds a unique name for <tt>typeVar</tt> which does not exist in the
     * <tt>typeVars</tt> which is as close as possible to the original name.
     * 
     * @param typeVar type var to conform
     * @param typeVars existing type vars
     * @return new name for the type var
     */
    private String findUniqueName(String typeVar, Set<String> typeVars) {
        if (!typeVars.contains(typeVar)) {
            return typeVar;
        }
        
        if (typeVar.length() == 1) {
            String name = this.findOffsetName(typeVar.charAt(0), typeVars);
            if (name != null) {
                return name;
            }
        }

        String name = this.findOffsetName('T', typeVars, "", typeVar);
        if (name != null) {
            return name;
        }
        
        name = this.findOffsetName('T', typeVars, typeVar, "");
        if (name != null) {
            return name;
        }
        
        name = this.findOffsetName('T', typeVars, "T", typeVar);
        if (name != null) {
            return name;
        }
        
        name = this.findOffsetName('T', typeVars, "", typeVar + "Type");
        if (name != null) {
            return name;
        }
        
        throw new IllegalStateException("Failed to conform type var: " + typeVar);
    }

    /**
     * Find an offset name for a single-char typevar.
     * 
     * @param c type var
     * @param typeVars existing type var
     * @return offset type var or null if var could not be conformed
     */
    private String findOffsetName(char c, Set<String> typeVars) {
        return this.findOffsetName(c, typeVars, "", "");
    }
    
    /**
     * Find an offset name for a type var
     * 
     * @param c Char to rotate
     * @param typeVars existing vars
     * @param prefix type var prefix
     * @param suffix type var suffix
     * @return conformed var name or null if none can be found
     */
    private String findOffsetName(char c, Set<String> typeVars, String prefix, String suffix) {
        String name = String.format("%s%s%s", prefix, c, suffix);
        if (!typeVars.contains(name)) {
            return name;
        }

        if (c > 0x40 && c < 0x5B) {
            for (int s = c - 0x40; s + 0x41 != c; s = ++s % 0x1A) {
                name = String.format("%s%s%s", prefix, (char)(s + 0x41), suffix);
                if (!typeVars.contains(name)) {
                    return name;
                }
            }
        }
        
        return null;
    }
    
    /**
     * Get a remapper for type vars in this signature
     * 
     * @return signature visitor
     */
    public SignatureVisitor getRemapper() {
        return new SignatureRemapper();
    }
    
    /**
     * Converts this signature into a string representation compatible with the
     * signature attribute of a Java class
     * 
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        while (this.rawInterfaces.size() > 0) {
            this.addRawInterface(this.rawInterfaces.remove());
        }
        
        StringBuilder sb = new StringBuilder();
        
        if (this.types.size() > 0) {
            boolean valid = false;
            StringBuilder types = new StringBuilder();
            for (Entry<TypeVar, TokenHandle> type : this.types.entrySet()) {
                String bound = type.getValue().asBound();
                if (!bound.isEmpty()) {
                    types.append(type.getKey()).append(':').append(bound);
                    valid = true;
                }
            }
            
            if (valid) {
                sb.append('<').append(types).append('>');
            }
        }
        
        sb.append(this.superClass.asType());
        
        for (Token iface : this.interfaces) {
            sb.append(iface.asType());
        }
        
        return sb.toString();
    }

    /**
     * Wake up this signature if it is lazy-loaded
     */
    public ClassSignature wake() {
        return this;
    }
    
    /**
     * Parse a generic class signature from the supplied string
     * 
     * @param signature signature string to parse
     * @return parsed signature object
     */
    public static ClassSignature of(String signature) {
        return new ClassSignature().read(signature);
    }
    
    /**
     * Parse a generic class signature from the supplied class node, uses the
     * declared signature if present, else falls back to generating a raw
     * signature from the class itself
     * 
     * @param classNode class node to parse
     * @return parsed signature
     */
    public static ClassSignature of(ClassNode classNode) {
        if (classNode.signature != null) {
            return ClassSignature.of(classNode.signature);
        }

        return ClassSignature.generate(classNode);
    }
    
    /**
     * Returns a lazy-evaluated signature object. For classes with a signature
     * present this saves having to do the parse until we actually need it. For
     * classes with no signature we just go ahead and generate it from the
     * supplied ClassNode while we have it 
     * 
     * @param classNode class node to parse
     * @return parsed signature or lazy-load handle
     */
    public static ClassSignature ofLazy(ClassNode classNode) {
        if (classNode.signature != null) {
            return new ClassSignature.Lazy(classNode.signature);
        }

        return ClassSignature.generate(classNode);
    }

    /**
     * Generate a rough (raw) signature from the supplied classnode
     * 
     * @param classNode class node to generate a signature for 
     * @return generated signature
     */
    private static ClassSignature generate(ClassNode classNode) {
        ClassSignature generated = new ClassSignature();
        generated.setSuperClass(new Token(classNode.superName != null ? classNode.superName : ClassSignature.OBJECT));
        for (String iface : classNode.interfaces) {
            generated.addInterface(new Token(iface));
        }
        return generated;
    }
    
}
