/*
 *    Fernflower - The Analytical Java Decompiler
 *    http://www.reversed-java.com
 *
 *    (C) 2008 - 2010, Stiver
 *
 *    This software is NEITHER public domain NOR free software 
 *    as per GNU License. See license.txt for more details.
 *
 *    This software is distributed WITHOUT ANY WARRANTY; without 
 *    even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 *    A PARTICULAR PURPOSE. 
 */

package de.fernflower.struct.gen.generics;

import java.util.ArrayList;
import java.util.List;

import de.fernflower.code.CodeConstants;

public class GenericType {
	
	public static final int WILDCARD_EXTENDS = 1;
	public static final int WILDCARD_SUPER = 2;
	public static final int WILDCARD_UNBOUND = 3;
	public static final int WILDCARD_NO = 4;

	public int type;

	public int arraydim;
	
	public String value;

	
	private List<GenericType> enclosingClasses = new ArrayList<GenericType>();
	
	private List<GenericType> arguments = new ArrayList<GenericType>();
	
	private List<Integer> wildcards = new ArrayList<Integer>();

	
	public GenericType(int type, int arraydim, String value) {
		this.type = type;
		this.arraydim = arraydim;
		this.value = value;
	}

	
	public GenericType(String strtype) {
		
		parseSignature(strtype);
		
	}
	
	private void parseSignature(String sig) {
		
		int index = 0;
		while(index < sig.length()) {
			
			switch(sig.charAt(index)){
			case '[':
				arraydim++; 
				break;
			case 'T':
				type = CodeConstants.TYPE_GENVAR;
				value = sig.substring(index+1, sig.length()-1);
				return;
			case 'L':
				type = CodeConstants.TYPE_OBJECT;
				sig = sig.substring(index+1, sig.length()-1);
				
				for(;;) {
					String cl = getNextClassSignature(sig);
					
					String name = cl;
					String args = null;
					
					int argfrom = cl.indexOf("<"); 
					if(argfrom >= 0) {
						name = cl.substring(0, argfrom);
						args = cl.substring(argfrom+1, cl.length()-1);
					}
					
					if(cl.length() < sig.length()) {
						sig = sig.substring(cl.length()+1); // skip '.'
						GenericType type = new GenericType(CodeConstants.TYPE_OBJECT, 0, name);
						parseArgumentsList(args, type);
						enclosingClasses.add(type);
					} else {
						value = name;
						parseArgumentsList(args, this);
						break;
					}
				}
				
				return;
			default:
				value = sig.substring(index, index+1);
				type = getType(value.charAt(0));
			}
			
			index++;
		}
		
	}
	
	private String getNextClassSignature(String value) {
		
		int counter = 0;
		int index = 0;
		
		loop:
		while(index < value.length()) {
			switch(value.charAt(index)) {
			case '<':
				counter++;
				break;
			case '>':
				counter--;
				break;
			case '.':
				if(counter == 0) {
					break loop;
				}
			}
			
			index++;
		}
		
		return value.substring(0, index);
	}
	
	private void parseArgumentsList(String value, GenericType type) {
		
		if(value == null) {
			return;
		}
		
		while(value.length() > 0) {
			
			String tstr = getNextType(value);
			int len = tstr.length();
			int wildcard = WILDCARD_NO;
			
			switch(tstr.charAt(0)) {
			case '*':
				wildcard = WILDCARD_UNBOUND;
				break;
			case '+':
				wildcard = WILDCARD_EXTENDS;
				break;
			case '-':
				wildcard = WILDCARD_SUPER;
				break;
			}
			
			type.getWildcards().add(wildcard);

			if(wildcard != WILDCARD_NO) {
				tstr = tstr.substring(1);
			}
			
			type.getArguments().add(tstr.length() == 0?null:new GenericType(tstr));
			
			value = value.substring(len);
		}
		
	}
	
	public static String getNextType(String value) {
		
		int counter = 0;
		int index = 0;
		
		boolean contmode = false;
		
		loop:
		while(index < value.length()) {
			switch(value.charAt(index)) {
			case '*':
				if(!contmode) {
					break loop;
				}
				break;
			case 'L':	
			case 'T':
				if(!contmode) {
					contmode = true;
				}
			case '[':
			case '+':
			case '-':
				break;
			default:
				if(!contmode) {
					break loop;
				}
				break;
			case '<':
				counter++;
				break;
			case '>':
				counter--;
				break;
			case ';':
				if(counter == 0) {
					break loop;
				}
			}
			
			index++;
		}
		
		return value.substring(0, index+1);
	}
	
	private int getType(char c) {
		switch(c) {
		case 'B':
			return CodeConstants.TYPE_BYTE;  
		case 'C':
			return CodeConstants.TYPE_CHAR;  
		case 'D':
			return CodeConstants.TYPE_DOUBLE;  
		case 'F':
			return CodeConstants.TYPE_FLOAT;  
		case 'I':
			return CodeConstants.TYPE_INT;  
		case 'J':
			return CodeConstants.TYPE_LONG;  
		case 'S':
			return CodeConstants.TYPE_SHORT;  
		case 'Z':
			return CodeConstants.TYPE_BOOLEAN;
		case 'V':
			return CodeConstants.TYPE_VOID;
		case 'G':
			return CodeConstants.TYPE_GROUP2EMPTY;
		case 'N':
			return CodeConstants.TYPE_NOTINITIALIZED;
		case 'A':
			return CodeConstants.TYPE_ADDRESS;
		case 'X':
			return CodeConstants.TYPE_BYTECHAR;
		case 'Y':
			return CodeConstants.TYPE_SHORTCHAR;
		case 'U':
			return CodeConstants.TYPE_UNKNOWN;
		default:
			throw new RuntimeException("Invalid type"); 
		}
	}


	public List<GenericType> getArguments() {
		return arguments;
	}


	public List<GenericType> getEnclosingClasses() {
		return enclosingClasses;
	}


	public List<Integer> getWildcards() {
		return wildcards;
	}
	
}
