package cn.edu.seu.ise.dca.app.php.parser;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.util.Stack;
import java.util.regex.Pattern;

import org.apache.log4j.Logger;

import cn.edu.seu.ise.dca.app.php.model.PHPClass;
import cn.edu.seu.ise.dca.app.php.model.PHPFile;
import cn.edu.seu.ise.dca.app.php.model.PHPFunction;
import cn.edu.seu.ise.dca.global.Global;
import cn.edu.seu.ise.dca.util.FileUtil;
import cn.edu.seu.ise.dca.util.StringUtil;
import cn.edu.seu.ise.dca.util.URLUtil;


public class PHPParser {
	
	//Set up the log file
	static Logger logger = Logger.getLogger(PHPParser.class);
	
	/* Note: Before invoking the method getPHPFile, it is necessary to confirm that the file has been checked out! */
	public static PHPFile getPHPFile(String url, long revision) {
		PHPFile phpFile = new PHPFile(url, revision);
		BufferedReader reader = null;
		try {
			reader = new BufferedReader(new FileReader(new File(URLUtil.generateFilePath(Global.DOWNLOAD_DIR, url, revision))));
			//line is used to record the content of each line
			String line = null;
			//lineNo is used to record the number of source code
			long lineNo = 0;
			//actLineNo is used to record the number of actual source code line (without comments and blank lines)
			long actLineNo = 0;
			Stack<String> classSymbols = new Stack<String>();
			Stack<String> functionSymbols = new Stack<String>();
			PHPClass currentClass = null;
			PHPFunction currentFunction = null;
			
			//isComment is used to specify whether line belongs to comment or not
			boolean isComment = false;
			//classPattern is used to recognize the definition of class
			Pattern classPattern = Pattern.compile(PHPPattern.CLASS_DEF);
			//classSingleLinePattern is used to recognize the definition of class within a single line
			Pattern classSingleLinePattern = Pattern.compile(PHPPattern.CLASS_DEF_SINGLELINE);
			//functionPattern is used to recognize the definition of function
			Pattern functionPattern = Pattern.compile(PHPPattern.FUNCTION_DEF);
			//functionSingleLinePattern is used to recognize function of single line
			Pattern functionSingleLinePattern = Pattern.compile(PHPPattern.FUNCTION_DEF_SINGLELINE);
			//AbstractfunctionPattern is used to recognize abstract function
			Pattern AbstractfunctionPattern = Pattern.compile(PHPPattern.ABSTRACT_FUCNTION_DEF);
			
			while((line = reader.readLine()) != null) {
				lineNo ++;
				actLineNo ++;
				line = line.trim();
				if(line.length() < 2000)  {
					/* Handle of blank lines */
					if(line.equalsIgnoreCase("")) {
						actLineNo --;
					} else {
						/* Handle of comments */
						if(isComment) {
							if(line.startsWith("*/") || line.endsWith("*/")) {
								isComment = false;
								actLineNo --;
							}
						} else {
							if(line.contains("//")) {
								if(!(Pattern.compile(".*\".*\\/\\/.*\".*").matcher(line).find() || Pattern.compile(".*\'.*\\/\\/.*\'.*").matcher(line).find())) {
									//For single line comment , remove it.
									line = line.substring(0, line.indexOf("//"));
									//If the whole line is comment
									if(line.equalsIgnoreCase("")) {
										actLineNo --;
									}
								}
							}
							if(line.contains("#")){
								//If the # is not in "" or ''
								if(!(Pattern.compile(".*\".*\\#.*\".*").matcher(line).find() || Pattern.compile(".*\'.*\\#.*\'.*").matcher(line).find())) {
									//For single UNIX comment line, remove it.
									line = line.substring(0, line.indexOf("#"));
									//If the whole line is comment
									if(line.equalsIgnoreCase("")) {
										actLineNo --;
									}
								}
							} 
							if(line.contains("/*") && !line.contains("*/")){
								//For the start of multiple line comments
								isComment = true;
								line = line.substring(0, line.indexOf("/*"));
								//If the whole line is comment
								if(line.equalsIgnoreCase("")) {
									actLineNo --;
								}
							} else if(line.contains("/*") && line.contains("*/") && line.indexOf("/*") < line.indexOf("*/")){
								//For those contain multiple line comments in single line, remove it
								line = line.replaceAll("/\\*.*\\*/", "").trim();
								//If the whole line is comment
								if(line.equalsIgnoreCase("")) {
									actLineNo --;
								}
							} 
							
							/* Start to analysis the main content */
							//If it is the definition of class
							if(classPattern.matcher(line).find()) {
								/* Some work for error tolerant*/
								if(currentClass != null) {
									currentClass.setEndLine(lineNo - 1);
									currentClass = null;
								}
								while(!classSymbols.isEmpty()) {
									classSymbols.pop();
								}
								//create the PHP class
								PHPClass phpClass = new PHPClass(revision);
								//Set the start line of the class 
								phpClass.setStartLine(lineNo);
								//Check it contains a "{" 
								if(line.endsWith("{")) {
									classSymbols.push("{");
									line = line.substring(0, line.indexOf("{")).trim();
								}
								//Set the name of the class
								String[] classDefs = line.split("\\s+");
								int indexOfClass = StringUtil.indexOf(classDefs, PHPKeyword.PHP_CLASS);
								if(indexOfClass != -1) {
									String className = classDefs[indexOfClass + 1];
									//Set the name of the PHP class
									phpClass.setName(className);
									//Set the full name of the PHP class (like: FileName::ClassName)
									phpClass.setFullName(FileUtil.GetFullName(URLUtil.getFileNameWithoutSuffix(url), className));
								}
								//Set the current class to the newly created class
								currentClass = phpClass;
								//Add newly created class into the list stored in PHP file
								phpFile.addClass(phpClass);
							}
							
							//If it is the definition of class within single line
							else if(classSingleLinePattern.matcher(line).find()) {
								/* Some work for error tolerant*/
								if(currentClass != null) {
									currentClass.setEndLine(lineNo - 1);
									currentClass = null;
								}
								while(!classSymbols.isEmpty()) {
									classSymbols.pop();
								}
								//create the PHP class
								PHPClass phpClass = new PHPClass(revision);
								//Set the start line of the class 
								phpClass.setStartLine(lineNo);
								//Set the end line of the class
								phpClass.setEndLine(lineNo);
								//Set the name of the class
								String[] classDefs = line.split("\\s+");
								int indexOfClass = StringUtil.indexOf(classDefs, PHPKeyword.PHP_CLASS);
								if(indexOfClass != -1) {
									String className = classDefs[indexOfClass + 1];
									//Set the name of the PHP class
									phpClass.setName(className);
									//Set the full name of the PHP class (like: FileName::ClassName)
									phpClass.setFullName(FileUtil.GetFullName(URLUtil.getFileNameWithoutSuffix(url), className));
								}
								
								//Add newly created class into the list stored in PHP file
								phpFile.addClass(phpClass);	
							}
 							
							//If it is the definition of function
							else if(functionPattern.matcher(line).find()) {
								if(currentFunction != null) {
									currentFunction.setEndLine(lineNo - 1);
									currentFunction = null;
								}
								while(!functionSymbols.isEmpty()) {
									functionSymbols.pop();
								}
								
								PHPFunction phpFunction = new PHPFunction(revision);
								//Set the start line of the class
								phpFunction.setStartLine(lineNo);
								//Check it contains a "{" 
								if(line.endsWith("{")) {
									functionSymbols.add("{");
								}
								line = line.substring(0, line.indexOf("(")).trim();
								//Set the name of the function
								String[] functionDefs = line.split("\\s+");
								int indexOfFunction = StringUtil.indexOf(functionDefs, PHPKeyword.PHP_FUNCTION);
								String functionName = null;
								if(indexOfFunction != -1) {
									functionName = functionDefs[indexOfFunction + 1];
									phpFunction.setName(functionName);
								}
								//Check whether the function is global or local
								if(currentClass != null && classSymbols.size() == 1) {
									//If the function is local, add it into function list of current class
									phpFunction.setGlobal(false);
									//Set the full name of local function (e.g. FileName::ClassName::FunctionName)
									phpFunction.setFullName(FileUtil.GetFullName(currentClass.getFullName(), functionName));
									currentClass.addFunction(phpFunction);
								} else {
									//If the function is global, add it into function list of php file
									phpFunction.setGlobal(true);
									//Set the full name of global function (e.g. File::GLOBAL::FunctionName)
									phpFunction.setFullName(FileUtil.GetFullName(FileUtil.GetFullName(URLUtil.getFileNameWithoutSuffix(url), "GLOBAL"), functionName));
									phpFile.addGlobalFunction(phpFunction);
								}	
								//Set the current function to the newly created function
								currentFunction = phpFunction;
							}
							//If it is the definition of function with single line or abstract function
							else if (functionSingleLinePattern.matcher(line).find() || AbstractfunctionPattern.matcher(line).find()){
								if(currentFunction != null) {
									currentFunction.setEndLine(lineNo - 1);
									currentFunction = null;
								}
								while(!functionSymbols.isEmpty()) {
									functionSymbols.pop();
								}
								
								PHPFunction phpFunction = new PHPFunction(revision);
								//Set the start line of the class
								phpFunction.setStartLine(lineNo);
								//Set the end line of the class
								phpFunction.setEndLine(lineNo);
								//Extract the string that contains the function name
								line = line.substring(0, line.indexOf("(")).trim();
								//Set the name of the function
								String[] functionDefs = line.split("\\s+");
								int indexOfFunction = StringUtil.indexOf(functionDefs, PHPKeyword.PHP_FUNCTION);
								if(indexOfFunction != -1) {
									phpFunction.setName(functionDefs[indexOfFunction + 1]);
								}
								//Check whether the function is global or local
								if(currentClass != null && classSymbols.size() == 1) {
									//If the function is local, add it into function list of current class
									phpFunction.setGlobal(false);
									String currentClassFullName = FileUtil.GetFullName(URLUtil.getFileNameWithoutSuffix(url), currentClass.getName());
									phpFunction.setFullName(FileUtil.GetFullName(currentClassFullName, functionDefs[indexOfFunction + 1]));
									currentClass.addFunction(phpFunction);
								} else {
									//If the function is global, add it into function list of php file
									phpFunction.setGlobal(true);
									String currentFullName = FileUtil.GetFullName(URLUtil.getFileNameWithoutSuffix(url), "GLOBAL");
									phpFunction.setFullName(FileUtil.GetFullName(currentFullName, functionDefs[indexOfFunction + 1]));
									phpFile.addGlobalFunction(phpFunction);
								}	
								//Set the current function to null
								currentFunction = null;
							} 
							//Handle the line code that contains { or }
							else {
								int numberOfOCB = StringUtil.findAllNotInQuote(line, '{');
								int numberOfCCB = StringUtil.findAllNotInQuote(line, '}');
								//Only contains one {
								if(numberOfOCB == 1 && numberOfCCB == 0) {
									if(currentClass != null && classSymbols.isEmpty()) {
										//The start { of the class
										classSymbols.push("{");
									} else if(currentFunction != null) {
										//The { of is the start of the function or in the inner structure 
										functionSymbols.push("{");
									} else if(currentClass == null && currentFunction == null){
										//The { outside the class definition,do nothing
									}
								}
								//Only contains one }
								else if (numberOfOCB == 0 && numberOfCCB == 1) {
									if(currentClass != null && classSymbols.size() == 1 && functionSymbols.isEmpty()) {
										//The end } of the class
										classSymbols.pop();
										currentClass.setEndLine(lineNo);
										currentClass = null;
									} else if(currentFunction != null && functionSymbols.size() == 1){
										//The end } of the function
										functionSymbols.pop();
										currentFunction.setEndLine(lineNo);
										currentFunction = null;
									} else if (currentFunction != null && functionSymbols.size() > 1) {
										//The end } inside the function
										functionSymbols.pop();
									} else if(currentClass == null && currentFunction == null) {
										//The } outside the class definition, do nothing
									}
								}
								//contains both { and } and the numbers are not equal
								else if(numberOfOCB >= 1 && numberOfCCB >= 1) {
									if(currentFunction != null) {
										int diff = numberOfOCB - numberOfCCB;
										if(diff == 0) {
											//do nothing
										} else if(diff > 0){
											for(int i = 0; i < diff; i++) 
												functionSymbols.push("{");
										} else if(diff < 0) {
											for(int i = 0; i < -diff; i++) 
												functionSymbols.pop();
										}
									}
								}
							}
						}
					}
					
				}
			}
			phpFile.setLines(lineNo);
			phpFile.setActualLines(actLineNo);
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			if(reader != null) {
				try {
					reader.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
		return phpFile;
		
	}	
	
}