/*
 * Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies). 
 * All rights reserved.
 * This component and the accompanying materials are made available
 * under the terms of "Eclipse Public License v1.0"
 * which accompanies this distribution, and is available
 * at the URL "http://www.eclipse.org/legal/epl-v10.html".
 *
 * Initial Contributors:
 * Nokia Corporation - initial contribution.
 *
 * Contributors:
 *
 * Description:
 *
 */
package com.nokia.s60tools.compatibilityanalyser.model;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import com.nokia.s60tools.compatibilityanalyser.data.ReportData.FILE_TYPE;
import com.nokia.s60tools.compatibilityanalyser.model.CompatibilityIssueData.ISSUE_CATEGORY;

/**
 * This class contains utility methods, needed for parsing the report file,
 * generated by Compatibility Analyser.
 *
 */
public class ReportFileParser {

	/**
	 * The method parses the file and identifies whether it contains library 
	 * related issues or header related issues.
	 * @param reportPath specifies the path of the report file.
	 * @returns File type which can be either Header or Library type.
	 */
	public static FILE_TYPE fetchReportFileType(String reportPath)
	{
		try {
			Document xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(reportPath));
			
			Node parentNode = getChildNodeByName(xmlDocument, "bbcresults");
			Node headerNode = getChildNodeByName(parentNode, "header");
			Node haVersion = getChildNodeByName(headerNode, "haversion");
			Node laVersion = getChildNodeByName(headerNode, "laversion");
			
			if(haVersion != null && haVersion.getFirstChild() != null)
				return FILE_TYPE.HEADER;
			
			if(laVersion != null && laVersion.getFirstChild() != null)
				return FILE_TYPE.LIBRARY;
			
		}catch(SAXException e){
			
		}catch(ParserConfigurationException e){
			
		}catch(IOException e){
			
		}
		
		return FILE_TYPE.NONE;
	}
	
	/**
	 * The method parses given report file and fetches all files, which contain header issues.
	 * @param filePath specifies the path of the report file.
	 * @return list of Compatibility Issue Files.
	 */
	public static ArrayList<CompatibilityIssueFileData> parseHeaderAnalysisIssues(String filePath)
	{
		ArrayList<CompatibilityIssueFileData> hdrIssues = null;
		
		try {
			Document xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(filePath));
			
			Node parentNode = getChildNodeByName(xmlDocument, "bbcresults");
			Node issuesNode = getChildNodeByName(parentNode, "issuelist");
					
			if(issuesNode != null && issuesNode.hasChildNodes()){
				 hdrIssues = parseHeaderIssues(issuesNode);
			}
			
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
		
		return hdrIssues;
	}
	
	/**
	 * The method parses given report file and fetches all files, which contain library issues.
	 * @param filePath specifies the path of the report file.
	 * @return list of Compatibility Issue Files.
	 */
	public static ArrayList<CompatibilityIssueFileData> parseLibraryAnalysisIssues(String filePath)
	{
		ArrayList<CompatibilityIssueFileData> libraryIssues = null;
		
		try {
			Document xmlDocument = DocumentBuilderFactory.newInstance().newDocumentBuilder().parse(new File(filePath));
			
			Node parentNode = getChildNodeByName(xmlDocument, "bbcresults");
					
			Node issuesNode = getChildNodeByName(parentNode, "issuelist");
					
			if(issuesNode != null && issuesNode.hasChildNodes()){
				List <String> dllDirectoryPaths = parseAndFetchDllDirs(parentNode);
				libraryIssues = parseLibraryIssues(issuesNode, dllDirectoryPaths);
			}
			
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		}
		
		return libraryIssues;
	}
	
	
	/**
	 * This method scans the given node and returns the child node 
	 * of given name.
	 */
	private static Node getChildNodeByName(Node currentNode, String name) {
		if(currentNode == null)
			return null;
		
		// Get the child nodes
		NodeList nodes = currentNode.getChildNodes();
		
		// Go through the children, and if names match return the node
		for(int i = 0; i < nodes.getLength(); i++)
			if(nodes.item(i).getNodeName().equals(name))
				return nodes.item(i);

		// Not found, return null
		return null;
	}
	
	/**
	 * The method parses all Header issues and fetches the details of each issue.
	 * @param issuesNode parent node of all issues.
	 * @return list of Header issues.
	 */
	private static ArrayList<CompatibilityIssueFileData> parseHeaderIssues(Node issuesNode)
	{
		NodeList nodeList = issuesNode.getChildNodes();
		
		ArrayList<Node> headerNodes = new ArrayList<Node>();
		ArrayList<CompatibilityIssueFileData> headerIssues = new ArrayList<CompatibilityIssueFileData>();
		
		for(int i= 0; i< nodeList.getLength(); i++)
		{
			Node item = nodeList.item(i);
			if(item != null && item.getNodeName().equals("headerfile"))
			{
				headerNodes.add(item);
				CompatibilityIssueFileData issueFile = new CompatibilityIssueFileData();
				
				Node fileNode = getChildNodeByName(item, "comparefilename");
				Node shortFileNode = getChildNodeByName(item, "shortname");
				Node  baseFileNode = getChildNodeByName(item, "filename");
				
				if(fileNode != null && fileNode.getFirstChild() != null)
					issueFile.setCurrentIssueFile(fileNode.getFirstChild().getNodeValue());
				
				if(shortFileNode != null && shortFileNode.getFirstChild() != null)
					issueFile.setCurrentFileShortName(shortFileNode.getFirstChild().getNodeValue());
				
				if(baseFileNode != null && baseFileNode.getFirstChild() != null)
					issueFile.setBaselineIssueFile(baseFileNode.getFirstChild().getNodeValue());
								
				NodeList childList = item.getChildNodes();
			
				for(int j=0; j < childList.getLength(); j++)
				{
					Node child = childList.item(j);
					
					if(child != null && child.getNodeName().equals("issue"))
					{
						CompatibilityIssueData issue_details = new CompatibilityIssueData();
						issue_details.setCurrentIssueFile(issueFile.getCurrentIssueFile());
						issue_details.setBaselineIssueFile(issueFile.getBaselinIssueFile());
						issue_details.setCurrentFileName(issueFile.getCurrentFileShortName());
						
						readHeaderIssueData(child, issue_details);
												
						issueFile.addIssueData(issue_details);
					}
				}
				
				//Adding the file to the list only if the file contains any issues.
				if(issueFile.getIssues() != null && issueFile.getIssues().length > 0)
					headerIssues.add(issueFile);
			}
		}
		
		return headerIssues;
	}
	
	/**
	 * The method parses all Library issues and fetches the details of each issue.
	 * The given list of dll directory paths will be set to each issue.
	 * @param issuesNode parent node of all issues.
	 * @param dllDirectoryPaths list of current dll directory paths.
	 * @return list of Library issues.
	 */
	
	private static ArrayList<CompatibilityIssueFileData> parseLibraryIssues(Node issuesNode, List<String> dllDirectoryPaths)
	{
		NodeList nodeList = issuesNode.getChildNodes();
		
		ArrayList<Node> libraryNodes = new ArrayList<Node>();
		ArrayList<CompatibilityIssueFileData> libraryIssues = new ArrayList<CompatibilityIssueFileData>();
		
		for(int i= 0; i< nodeList.getLength(); i++)
		{
			Node item = nodeList.item(i);
			if(item != null && item.getNodeName().equals("library"))
			{
				libraryNodes.add(item);
				CompatibilityIssueFileData issueFile = new CompatibilityIssueFileData();
								
				Node fileNode = getChildNodeByName(item, "comparefilename");
				Node shortFileNode = getChildNodeByName(item, "shortname");
				Node  baseFileNode = getChildNodeByName(item, "name");
				
				if(fileNode != null && fileNode.getFirstChild() != null)
					issueFile.setCurrentIssueFile(fileNode.getFirstChild().getNodeValue());
				
				if(shortFileNode != null && shortFileNode.getFirstChild() != null)
					issueFile.setCurrentFileShortName(shortFileNode.getFirstChild().getNodeValue());
				
				if(baseFileNode != null && baseFileNode.getFirstChild() != null)
					issueFile.setBaselineIssueFile(baseFileNode.getFirstChild().getNodeValue());
				
				NodeList childList = item.getChildNodes();
				
				for(int j=0; j < childList.getLength(); j++)
				{
					Node child = childList.item(j);
					
					if(child != null && child.getNodeName().equals("issue"))
					{
						LibraryIssueData issue_details = new LibraryIssueData();
						issue_details.setCurrentIssueFile(issueFile.getCurrentIssueFile());
						issue_details.setBaselineIssueFile(issueFile.getBaselinIssueFile());
						issue_details.setCurrentFileName(issueFile.getCurrentFileShortName());
						issue_details.setDllDirPaths(dllDirectoryPaths);
						
						readLibraryIssueData(child, issue_details);
						
						//This is needed as library issues of some typeid, are ignored by the tool.
						if(issue_details.getIssueDescription() != null)
							issueFile.addIssueData(issue_details);
					}
				}
				
				//Adding the file to the list only if the file contains any issues.
				if(issueFile.getIssues() != null && issueFile.getIssues().length > 0)
					libraryIssues.add(issueFile);
			}
		}
		
		return libraryIssues;
	}
	
	/**
	 * This method reads the details of a header issue like line number, description etc.
	 * @param issueNode
	 * @param headerIssue
	 */
	private static void readHeaderIssueData(Node issueNode, CompatibilityIssueData headerIssue)
	{
		Node lineNumNode = getChildNodeByName(issueNode, "linenumber");
								
		if(lineNumNode != null)
		{
			//System.out.println("Line number of the issue is " + lineNumNode.getFirstChild().getNodeValue());
			headerIssue.setLineNumber(Integer.parseInt(lineNumNode.getFirstChild().getNodeValue()));
		}
				
		Node causeNode = getChildNodeByName(issueNode, "cause");
		Node descriptionNode = getChildNodeByName(issueNode, "identitydescription");
		Node typeStringNode = getChildNodeByName(issueNode, "typestring");
				
		StringBuffer error_desc  = new StringBuffer("");
				
		if(causeNode != null && causeNode.getFirstChild() != null)
		{
			String causeValue = causeNode.getFirstChild().getNodeValue();
			if(causeValue != null){
				if(causeValue.startsWith("::"))
					causeValue = causeValue.substring(causeValue.indexOf("::")+2);
					error_desc.append(causeValue + " ");
				}
			}
				
			if(descriptionNode != null && descriptionNode.getFirstChild() != null)
			{
				String descValue = descriptionNode.getFirstChild().getNodeValue();
				
				if(descValue != null)
					error_desc.append(descValue + " ");
			}
				
			if(typeStringNode != null && typeStringNode.getFirstChild() != null)
			{
				String typeString = typeStringNode.getFirstChild().getNodeValue();
				
				if(typeString != null)
					error_desc.append(typeString);
			}
				
			headerIssue.setIssueDescription(error_desc.toString());
				
			Node severityParentNode = getChildNodeByName(issueNode, "severity");
			Node severityTypeNode = getChildNodeByName(severityParentNode, "typestring");
				
			if(severityTypeNode != null && severityTypeNode.getFirstChild() != null)
				headerIssue.setBCSeverityText(severityTypeNode.getFirstChild().getNodeValue());
			
			Node scSeverityParentNode = getChildNodeByName(issueNode, "scseverity");
			Node scSeverityTypeNode = getChildNodeByName(scSeverityParentNode, "typestring");
				
			if(scSeverityTypeNode != null && scSeverityTypeNode.getFirstChild() != null)
				headerIssue.setSCSeverityText(scSeverityTypeNode.getFirstChild().getNodeValue());
				
			calculateSeverityCategory(headerIssue);
		
	}
	
	/**
	 * This method reads the details of a library issue and 
	 * frames the description of the issue from the available data.
	 * @param libraryNode
	 * @param issue_details
	 */
	private static void readLibraryIssueData(Node libraryNode, LibraryIssueData issue_details)
	{
		Node typeIdNode = getChildNodeByName(libraryNode, "typeid");
				
		if(typeIdNode != null)
		{
			int typeId = Integer.parseInt(typeIdNode.getFirstChild().getNodeValue());
					
			String errorDescription = null;
					
			if(typeId == 2 || typeId == 7 || typeId == 14)
				return;
					
			switch(typeId)
			{
				case 1:
					errorDescription = "Library is no longer available";
					break;
				case 3:{
					StringBuffer tmp = new StringBuffer("");
					Node funcNameNode = getChildNodeByName(libraryNode, "funcname");
					
					if(funcNameNode != null){
						tmp.append(funcNameNode.getFirstChild().getNodeValue());
						issue_details.setFunctionName(funcNameNode.getFirstChild().getNodeValue());
					}
					Node funcPositionNode = getChildNodeByName(libraryNode, "funcpos");
					Node newFuncPositionNode = getChildNodeByName(libraryNode, "newfuncpos");
							
					String origPosValue = funcPositionNode.getFirstChild().getNodeValue();
					String newPosValue = newFuncPositionNode.getFirstChild().getNodeValue();
							
					tmp.append(" is moved from Position " + origPosValue + " to " + newPosValue);
					errorDescription = tmp.toString();
					issue_details.setOrdinalPosition(newPosValue);
					
					break;
				}
				case 4:{
					StringBuffer tmp = new StringBuffer("Function ");
					Node funcNameNode = getChildNodeByName(libraryNode, "funcname");
						
					if(funcNameNode != null && funcNameNode.getFirstChild() != null)
						tmp.append(funcNameNode.getFirstChild().getNodeValue());
								
					Node funcPosNode = getChildNodeByName(libraryNode, "funcpos");
									
					tmp.append(" deleted");
								
					if(funcPosNode != null && funcPosNode.getFirstChild() != null)
						tmp.append(" from Position " + funcPosNode.getFirstChild().getNodeValue());
								
					errorDescription = tmp.toString();
					break;
				}
				case 5:{
					StringBuffer tmp = new StringBuffer("");
					Node newFuncNode = getChildNodeByName(libraryNode, "newfuncname");
							
					if(newFuncNode != null){
						String newFuncName = newFuncNode.getFirstChild().getNodeValue();
						tmp.append(newFuncName);
						issue_details.setFunctionName(newFuncName);
					}
					Node newFuncPosNode = getChildNodeByName(libraryNode, "newfuncpos");
					String newPositionValue = newFuncPosNode.getFirstChild().getNodeValue();
							
					tmp.append(" is inserted at Position " + newPositionValue);
					errorDescription = tmp.toString();
					issue_details.setOrdinalPosition(newPositionValue);
					
					break;
				}
				case 6:{
					StringBuffer tmp = new StringBuffer("Function name modified");
					Node oldFuncNode = getChildNodeByName(libraryNode, "funcname");
					Node newFuncNode = getChildNodeByName(libraryNode, "newfuncname");
					Node positionNode = getChildNodeByName(libraryNode, "funcpos");
							
					if(oldFuncNode != null && oldFuncNode.getFirstChild() != null)
						tmp.append(" from " + oldFuncNode.getFirstChild().getNodeValue());
							
					if(newFuncNode != null && newFuncNode.getFirstChild() != null){
						String newFuncName = newFuncNode.getFirstChild().getNodeValue();
						issue_details.setFunctionName(newFuncName);
						tmp.append(" to " + newFuncName);
					}			
					if(positionNode != null && positionNode.getFirstChild() != null){
						String positionValue = positionNode.getFirstChild().getNodeValue();
						issue_details.setOrdinalPosition(positionValue);	
						tmp.append(" at Position " + positionValue);
					}
					
					errorDescription = tmp.toString();
					break;
				}
				case 7:{
					StringBuffer tmp = new StringBuffer("New Function");
							
					Node funcNameNode = getChildNodeByName(libraryNode, "newfuncname");
					Node funcPosNode = getChildNodeByName(libraryNode, "newfuncpos");
							
					if(funcNameNode != null && funcNameNode.getFirstChild() != null){
						String funcName = funcNameNode.getFirstChild().getNodeValue();
						issue_details.setFunctionName(funcName);
						tmp.append(" " + funcName);
					}
					tmp.append(" added");
							
					if(funcPosNode != null && funcPosNode.getFirstChild() != null){
						String positionValue = funcPosNode.getFirstChild().getNodeValue();
						issue_details.setOrdinalPosition(positionValue);
						tmp.append(" at Position " + positionValue);
					}
					errorDescription = tmp.toString();
					break;
				}
				case 8:
				case 9:
				case 10:
				case 11:
				case 12:
				case 13:
				case 14:{
					Node typeInfoNode = getChildNodeByName(libraryNode, "typeinfo");
							
					if(typeInfoNode != null && typeInfoNode.getFirstChild() != null)
						errorDescription = typeInfoNode.getFirstChild().getNodeValue();
							
					break;
				}
			}
					issue_details.setIssueDescription(errorDescription);
					issue_details.setError_typeid(typeId);
					
					//issue.setLineNumber(Integer.parseInt(lineNumNode.getFirstChild().getNodeValue()));
		}
				
				
		Node bcSeverityNode = getChildNodeByName(libraryNode, "bc_severity");
				
		if(bcSeverityNode != null && bcSeverityNode.getFirstChild() != null)
			issue_details.setBCSeverityText(bcSeverityNode.getFirstChild().getNodeValue());
				
		Node scSeverityNode = getChildNodeByName(libraryNode, "sc_severity");
				
		if(scSeverityNode != null && scSeverityNode.getFirstChild() != null)
			issue_details.setSCSeverityText(scSeverityNode.getFirstChild().getNodeValue());
				
		calculateSeverityCategory(issue_details);
				
	}
	
	/**
	 * This method categorizes given issue as a BC break or SC break or Both.
	 * @param issue
	 */
	private static void calculateSeverityCategory(CompatibilityIssueData issue)
	{
		String bcSeverity = issue.getBCSeverityText();
		String scSeverity = issue.getSCSeverityText();
		
		try{
			if(!bcSeverity.equalsIgnoreCase("None") && !scSeverity.equalsIgnoreCase("None"))
				issue.setIssueCategory(ISSUE_CATEGORY.BOTH);
			else if(!bcSeverity.equalsIgnoreCase("None") && scSeverity.equalsIgnoreCase("None"))
				issue.setIssueCategory(ISSUE_CATEGORY.BC_BREAK);
			else if(bcSeverity.equalsIgnoreCase("None") && !scSeverity.equalsIgnoreCase("None"))
				issue.setIssueCategory(ISSUE_CATEGORY.SC_BREAK);
			else
				issue.setIssueCategory(ISSUE_CATEGORY.NONE);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * This method scans the report file to fetch all current dll directory paths.
	 * @param bbcResultsNode
	 * @return list of dll directory paths.
	 */
	private static List<String> parseAndFetchDllDirs(Node bbcResultsNode)
	{
		ArrayList<String> dllDirs = new ArrayList<String>();
		
		Node headerNode = getChildNodeByName(bbcResultsNode, "header");
		Node commandLineParams = getChildNodeByName(headerNode, "cmdlineparms");
		
		NodeList paramsList = commandLineParams.getChildNodes();
		
		for(int i= 0; i<paramsList.getLength(); i++)
		{
			Node paramNode = paramsList.item(i);
			
			Node pNameNode = getChildNodeByName(paramNode, "pname");
			
			if(pNameNode != null && pNameNode.getFirstChild() != null){
				String paramName = pNameNode.getFirstChild().getNodeValue();
				
				if(paramName == null || !paramName.equalsIgnoreCase("currentdlldir"))
					continue;
				else
				{
					NodeList paramChildList = paramNode.getChildNodes();
					for(int index =0; index < paramChildList.getLength(); index++)
					{
						Node childNode = paramChildList.item(index);
						if(!childNode.getNodeName().equalsIgnoreCase("pvalue"))
							continue;
						else{
							if(childNode.getFirstChild() != null)
								dllDirs.add(childNode.getFirstChild().getNodeValue());
						}
							
					}
					
					break;
				}
				
			}
			
		}
		
		return dllDirs;
	}
}
