/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */
/*
 * Contributor(s): Thomas Ball
 */

package org.netbeans.modules.classfile;

import java.io.*;
import java.util.*;

/**
 * A representation of a parameter to a method declaration.  A parameter
 * will not have a name, unless the classfile is compiled with local
 * variable tables (if not, then the name is an empty string).
 * A final modifier on a parameter is never reported,
 * since that modifier is not stored in a classfile.
 *
 * @author  Thomas Ball
 */
public final class Parameter extends Field {

    static Parameter[] makeParams(Method method) {
	List<Parameter> paramList = new ArrayList<Parameter>();
        for (Iterator<Parameter> it = new ParamIterator(method); it.hasNext();)
            paramList.add(it.next());
        return paramList.toArray(new Parameter[paramList.size()]);
    }

    private static Parameter createParameter (String name, String type, ClassFile classFile,
            DataInputStream visibleAnnotations, DataInputStream invisibleAnnotations) {
        return new Parameter (name, type, classFile,
            visibleAnnotations, invisibleAnnotations);
    }
    
    /** Creates new Parameter */
    private Parameter(String name, String type, ClassFile classFile,
            DataInputStream visibleAnnotations, DataInputStream invisibleAnnotations) {
        super(name, type, classFile);
        loadParameterAnnotations(visibleAnnotations, invisibleAnnotations);
    }
    
    private void loadParameterAnnotations(DataInputStream visible, DataInputStream invisible) {
        super.loadAnnotations();
        if (annotations == null && (visible != null || invisible != null))
            annotations = new HashMap<ClassName,Annotation>(2);
        try {
            if (visible != null && visible.available() > 0)
                Annotation.load(visible, classFile.getConstantPool(), true, annotations);
        } catch (IOException e) {
            throw new InvalidClassFileAttributeException("invalid RuntimeVisibleParameterAnnotations attribute", e);
        }
        try {
            if (invisible != null && invisible.available() > 0)
                Annotation.load(invisible, classFile.getConstantPool(), false, annotations);
        } catch (IOException e) {
            throw new InvalidClassFileAttributeException("invalid RuntimeInvisibleParameterAnnotations attribute", e);
        }
    }

    /**
     * Return a string in the form "<type> <name>".  Class types
     * are shown in a "short" form; i.e. "Object" instead of
     * "java.lang.Object"j.
     *
     * @return string describing the variable and its type.
     */
    public final String getDeclaration() {
	StringBuffer sb = new StringBuffer();
	sb.append(CPFieldMethodInfo.getSignature(getDescriptor(), false));
	String name = getName();
	if (name != null) {
	    sb.append(' ');
	    sb.append(name);
	}
	return sb.toString();
    }
    
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer("name=");
	sb.append(getName());
        sb.append(" type="); //NOI18N
        sb.append(getDescriptor());
	if (getTypeSignature() != null) {
	    sb.append(", signature="); //NOI18N
	    sb.append(typeSignature);
	}
        loadAnnotations();
	if (annotations.size() > 0) {
	    Iterator<Annotation> iter = annotations.values().iterator();
	    sb.append(", annotations={ ");
	    while (iter.hasNext()) {
		sb.append(iter.next().toString());
		if (iter.hasNext())
		    sb.append(", ");
	    }
	    sb.append(" }");
	}
        return sb.toString();
    }

    private static class ParamIterator implements Iterator<Parameter> {
        ClassFile classFile;
        String signature;
        LocalVariableTableEntry[] localVars;

        /** the current local variable array position */
        int ivar;

        /** the current character in the type signature */
        int isig;
        
        /** annotation attributes */
        DataInputStream visibleAnnotations;
        DataInputStream invisibleAnnotations;
        
        /** 
         * @param method 
         */
        ParamIterator(Method method) {
            classFile = method.getClassFile();
            signature = method.getDescriptor();
            assert signature.charAt(0) == '(';
            isig = 1;  // skip '('
            ivar = method.isStatic() ? 0 : 1;
	    Code code = method.getCode();
            localVars = code != null ? 
		code.getLocalVariableTable() : 
		new LocalVariableTableEntry[0];
            AttributeMap attrs = method.getAttributes();
            try {
                visibleAnnotations = 
                    getParamAttr(attrs, "RuntimeVisibleParameterAnnotations"); //NOI18N
            } catch (IOException e) {
                throw new InvalidClassFileAttributeException("invalid RuntimeVisibleParameterAnnotations attribute", e);
            }
            try {
                invisibleAnnotations = 
                    getParamAttr(attrs, "RuntimeInvisibleParameterAnnotations"); //NOI18N
            } catch (IOException e) {
                throw new InvalidClassFileAttributeException("invalid RuntimeInvisibleParameterAnnotations attribute", e);
            }
        }
        
        private DataInputStream getParamAttr(AttributeMap attrs, String name) throws IOException {
            DataInputStream in = attrs.getStream(name);
            if (in != null)
                in.readByte(); // skip the redundant parameters number
            return in;
        }
        
        public boolean hasNext() {
            return signature.charAt(isig) != ')';
        }
        
        public Parameter next() {
            if (hasNext()) {
		String name = "";
		for (int i = 0; i < localVars.length; i++) {
		    LocalVariableTableEntry lvte = localVars[i];
		    // only parameters have a startPC of zero
		    if (lvte.index == ivar && lvte.startPC == 0) {
			name = localVars[i].getName();
			break;
		    }
		}
                ivar++;
                int sigStart = isig;
                while (isig < signature.length()) {
                    char ch = signature.charAt(isig);
                    switch (ch) {
                        case '[':
                            isig++;
                            break;
                        case 'B':
                        case 'C':
                        case 'F':
                        case 'I':
                        case 'S':
                        case 'Z':
                        case 'V': {
                            String type = signature.substring(sigStart, ++isig);
                            return Parameter.createParameter(name, type, classFile, 
                                    visibleAnnotations, invisibleAnnotations);
                        }
                        case 'D':
                        case 'J': {
                            ivar++;  // longs and doubles take two slots
                            String type = signature.substring(sigStart, ++isig);
                            return Parameter.createParameter(name, type, classFile, 
                                    visibleAnnotations, invisibleAnnotations);
                        }
                        case 'L': {
                            int end = signature.indexOf(';', isig) + 1;
                            String type = signature.substring(isig, end);
                            isig = end;
                            return Parameter.createParameter(name, type, classFile, 
                                    visibleAnnotations, invisibleAnnotations);
                        }

                    }
                }
            }
            throw new NoSuchElementException();
        }
        
        public void remove() {
            throw new UnsupportedOperationException();
        }
    }
}
