package jp.ac.osaka_u.ist.sel.clonedetector.analyze;

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StreamTokenizer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import jp.ac.osaka_u.ist.sel.clonedetector.CloneDetector;
import jp.ac.osaka_u.ist.sel.clonedetector.data.Function;
import jp.ac.osaka_u.ist.sel.clonedetector.data.Word;

import org.eclipse.jdt.core.dom.AST;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.TypeDeclaration;

/**
 * <p>Javat@Cp̓NX</p>
 * @author y-yuuki
 */
public class JavaAnalyzer {

	private ASTParser parser = ASTParser.newParser(AST.JLS4);	
	private ArrayList<String> allWordList = new ArrayList<String>();
	
	/**
	 * <p>RXgN^</p>
	 */
	public JavaAnalyzer(){
		parser.setBindingsRecovery(true);
		parser.setStatementsRecovery(true);
		parser.setResolveBindings(true);
	}
	
	/**
	 * <p>PꃊXg̎擾</p>
	 * @return
	 */
	public ArrayList<String> getWordList(){
		return allWordList;
	}
		
	/**
	 * <p>fBNgT</p>
	 * @param file
	 * @throws IOException
	 */
	public void searchFile(File file){		
		if(file.isFile() && file.getName().endsWith(".java")){	
			parser.setSource(getCode(file));
			final CompilationUnit unit = (CompilationUnit) parser.createAST(null);		
	 		ASTNode node = unit.getRoot();
	 		extractFunction(node, file.getParent().replace("\\","."));			
		}else if(file.isDirectory()){
			File[] fileList = file.listFiles();
			for(File f: fileList)
				searchFile(f);
		}	
	}
	
	
	/**
	 * <p>\[XR[heLXg擾</p>
	 * @param file
	 * @return
	 * @throws IOException
	 */
	private char[] getCode(File file) {			
		String code = "";
		String line;
		try {
			BufferedReader reader = new BufferedReader(new FileReader(file));
			while((line=reader.readLine())!=null)
				code = code + "\n" +line;
			reader.close();
		} catch (IOException e){
			;
		}		
		return code.toCharArray();		
	}
	
	/**
	 * <p>\[XR[ht@NV̒o</p>
	 * @param node
	 * @param className
	 * @throws IOException
	 */
	@SuppressWarnings("unchecked")
	private void extractFunction(ASTNode node, String className){
	
		//NX錾̏ꍇ
		if(node.getNodeType()==ASTNode.TYPE_DECLARATION){
			TypeDeclaration n= ((TypeDeclaration )node);
			className = className+"."+n.getName().getIdentifier();
		}			
		
		//\bh錾̏ꍇ
		if(node.getNodeType()==ASTNode.METHOD_DECLARATION ){
			Function method = new Function();
			MethodDeclaration n = (MethodDeclaration) node;
			method.setName(n.getName().getIdentifier());	
			method.setDirName(className);
			method.setCode(node.toString());			
			CloneDetector.functionList.add(method);
			
			ByteArrayInputStream is = new ByteArrayInputStream(node.toString().getBytes()); 
			StreamTokenizer tokenizer = new StreamTokenizer(new BufferedReader(new InputStreamReader(is)));
			tokenizer.wordChars('0', '9');
			tokenizer.wordChars('a', 'z');
			tokenizer.wordChars('A', 'Z');
			tokenizer.wordChars('_', '_');
			tokenizer.ordinaryChar('/');
			tokenizer.ordinaryChar('*');
			tokenizer.slashSlashComments(true);
			tokenizer.slashStarComments(true);
			extractWordList(tokenizer, method);
			return;
		}		

		//qm[h̎擾
		List<StructuralPropertyDescriptor> properties= node.structuralPropertiesForType();
		ArrayList<ASTNode> children = new ArrayList<ASTNode>();
		for (Iterator<?> i=properties.iterator(); i.hasNext();) {
			StructuralPropertyDescriptor descriptor=(StructuralPropertyDescriptor)i.next();
	        if (descriptor.isChildProperty()) {
	        	if((ASTNode)node.getStructuralProperty(descriptor)!=null)
	        		children.add((ASTNode)node.getStructuralProperty(descriptor));	        	
	        } else if (descriptor.isChildListProperty()) {
	        	List<?extends ASTNode> tmpChildren= (List<? extends ASTNode>) node.getStructuralProperty(descriptor);
	        	for (Iterator<? extends ASTNode> j= tmpChildren.iterator();j.hasNext();) {
	                children.add(j.next());                
                }
	        }
		}		
		//qm[h
		for(ASTNode n:children){		
			extractFunction(n,className);
		}

	}
	
	/**
	 * <p>֐烏[h𒊏o</p>
	 * @param tokenizer
	 * @param method
	 * @throws IOException 
	 */
	private void extractWordList(StreamTokenizer tokenizer, Function method) {
		int token;
		try {
			while ((token = tokenizer.nextToken()) != StreamTokenizer.TT_EOF) {				
				switch (token){
					case StreamTokenizer.TT_WORD:	
						method.incSize();
						separateIdentifier(tokenizer.sval,method.getWordList());
						break;
				}
			}
		} catch (IOException e) {
			System.err.println("Error: can't analyze source code.");
			System.exit(1);
		}		
	}

	/**
	 * <p>PꃊXg</p>
	 * @param identifier
	 * @param wordList
	 */
	private void separateIdentifier(String identifier, ArrayList<Word> wordList){
		String word = new String("");
		ArrayList<String> identifierWord = new ArrayList<String>();
		if(identifier.length()<=2){
			identifierWord.add("word_2");	
		}else{
			for(char c: identifier.toCharArray()){
				if( 'a'<=c && c<='z'){
					word = word+c; 					
				}else if('A'<=c && c<='Z'){
					if(!word.isEmpty()){
						if('A'<=word.charAt(word.length()-1) && word.charAt(word.length()-1)<='Z'){
							word = word+c; 		
						}else{
							if(!word.isEmpty())
								identifierWord.add(word.toLowerCase());
							word = ""+c; 		 					
						}
					}else{
						word=word+c; 
					}
				}else{
					if(!word.isEmpty())
						identifierWord.add(word.toLowerCase());
					word="";
				} 
			}
			if(!word.isEmpty())
				identifierWord.add(word);
			addWord(wordList,identifierWord);	
		}
		
	}
	 
	/**
	 * <p>[hXgւ̒ǉ</p>
	 * @param wordList
	 * @param identifierWord 
	 * @param word
	 */
	private void addWord(ArrayList<Word> wordList, ArrayList<String> identifierWord){
		for(String newWord:identifierWord){
			newWord = newWord.toLowerCase();
			if(!newWord.isEmpty()){
				boolean addFlg = true;
				for(Word word: wordList){
					if(word.getName().equals(newWord)){
						addFlg = false;
						word.addCount(1.0);
						break;
					}					
				}			
				if(addFlg){
					wordList.add(new Word(newWord,Word.WORD,1.0));
				}
			}
		}
	}
}
