package smart_gs.diff;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import javax.swing.JOptionPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeModel;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.apache.commons.lang.StringEscapeUtils;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import smart_gs.logical.IDAT;
import smart_gs.logical.Preference;
import smart_gs.logical.URISolver;
import smart_gs.swingui.WorkspaceWindow;
import difflib.DiffRow;
import difflib.DiffRowGenerator;


public class GsxComparator {
	
	private Document targetDoc;
	private Document revised;
	private File targetGsx;
	private TreeModel diffTree;

	public GsxComparator(String pathOriginal, String pathRevised) {
		this.targetDoc = getDocument(pathOriginal);
		this.revised = getDocument(pathRevised);
	}
	
	public GsxComparator (File targetGsx) {	
		try {
			DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
			this.targetGsx = targetGsx;
			ZipFile gsxFile= new ZipFile(this.targetGsx);
			ZipEntry gsxSpread = gsxFile.getEntry("spread.xml");
			if (gsxSpread==null){
				JOptionPane.showMessageDialog(WorkspaceWindow.getInstance(), targetGsx.getName() + ": Invalid gsx file.");			
				gsxFile.close();
				return;
			}
			this.targetDoc = builder.parse(gsxFile.getInputStream(gsxSpread));
			gsxFile.close();
			
		} catch (ZipException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}
	
	public TreeModel compare() {
		return this.buildDiffTree();
	}
	
	private TreeModel buildDiffTree(){		
		DefaultMutableTreeNode rootNode = new DefaultMutableTreeNode("Root");		
		Element originalRoot = getSpreadTreeElement(this.targetDoc);
		this.traverseOriginalXml(originalRoot, rootNode);
		this.setDiffTree(new DefaultTreeModel(rootNode));
		return this.diffTree;
	}
	
	private void traverseOriginalXml(Element docParent, DefaultMutableTreeNode parent){
		NodeList children = docParent.getChildNodes();
		for (int i = 0; i < children.getLength(); i++) {
			Node node = children.item(i);
			String name = node.getNodeName();
			if (name.equals("directory")){
				Element elem = (Element) node;
				String directoryName = elem.getAttribute("name");
				ResourceDiff diff = new ResourceDiff(directoryName,ResourceDiff.Type.DIRECTORY);
				DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(diff, true);
				this.traverseOriginalXml(elem, newNode);				
				parent.add(newNode);
				System.out.println(directoryName);
			} else if (name.equals("spread")){
				Element elem = (Element) node;
				String spreadName = elem.getAttribute("name");
				ResourceDiff diff = new ResourceDiff(spreadName,ResourceDiff.Type.SPREAD);
				DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(diff, true);
				this.traverseOriginalXml(elem, newNode);				
				parent.add(newNode);
				System.out.println(spreadName);
			} else if (name.equals("spreadDocument")){
				Element targetElement = (Element) node;
				String idatType = targetElement.getAttribute("type");
				ResourceDiff diff = new ResourceDiff(idatType,ResourceDiff.Type.SPREAD_DOCUMENT);
				// get the src of the idat in the target gsx file
				String targetSource = StringEscapeUtils.unescapeHtml(targetElement.getAttribute("source"));
			
				// get the src of the corresponding idat in the current project
				String uri = targetElement.getAttribute("uri");
				IDAT idat = (IDAT) URISolver.getResource(uri);
				if (idat == null) {continue;}
				// set idat names
				Preference p = Preference.getInstance();
				diff.setName(p.getIDATName(Integer.parseInt(idatType)));				
				String originalSource = idat.getSource();
				// generate the diff HTML
				String diffHtml = this.compareSources(originalSource, targetSource);
				if (diffHtml != null){
					diff.setDiffHtml(diffHtml);
					diff.setChanged(true);
					((ResourceDiff) parent.getUserObject()).setChanged(true);
				} else {
					diff.setDiffHtml("<h1>No changes found</h1>");
					diff.setChanged(false);
				}
				// create a new entry in the diff tree
				DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(diff, false);								
				parent.add(newNode);
				System.out.println(idatType + ": " + diffHtml);
			} 
		}
	}
	
	private String compareSources(String targetSource, String originalSource) {
		String diff = "";
		originalSource = originalSource.replaceAll("<br/?>", "\n").replaceAll("\\<.*?\\>", "");
		targetSource = targetSource.replaceAll("<br/?>", "\n").replaceAll("\\<.*?\\>", "");		
		List<String> originalList = Arrays.asList(originalSource.split("\n"));		
		List<String> targetList = Arrays.asList(targetSource.split("\n"));
		System.out.println("original: " + originalList);
		System.out.println("target: " + targetList);
		DiffRowGenerator.Builder builder = new DiffRowGenerator.Builder();
		builder.showInlineDiffs(false); 
		DiffRowGenerator dfg = builder.build();
		List<DiffRow> rows = dfg.generateDiffRows(originalList, targetList);
		boolean changed = false;
		diff += "<table>";
		for (DiffRow row : rows) {			
			diff += "<tr>";
			String oldLine = StringEscapeUtils.unescapeHtml(row.getOldLine());
			String newLine = StringEscapeUtils.unescapeHtml(row.getNewLine());
			switch(row.getTag()) {
			case EQUAL: 
				diff += "<td>"+oldLine+"</td>";
				diff += "<td>"+newLine+"</td>";
				break;
			case CHANGE:
				String[] compared = this.compareLines(oldLine, newLine); 
				diff += "<td class='change'>"+ compared[0] +"</td>";
				diff += "<td class='change'>"+ compared[1] +"</td>";
				changed = true;
				break;
			case DELETE:
				diff += "<td class='delete'><del>"+oldLine+"</del></td>";
				diff += "<td></td>";	
				changed = true;
				break;
			case INSERT:
				diff += "<td></td>";
				diff += "<td class='insert'>"+newLine+"</td>";
				changed = true;
			default:				
				break;	
			}
			diff += "</tr>";
		}
		diff += "</table>";
		if (!changed) {return null;}
		return diff;
	}
	
	private String[] compareLines (String line1, String line2) {
		DiffRowGenerator.Builder builder = new DiffRowGenerator.Builder();
		builder.showInlineDiffs(true); 
		DiffRowGenerator dfg = builder.build();
		List<String> chars1 = Arrays.asList(line1.split(""));
		List<String> chars2 = Arrays.asList(line2.split(""));		
		List<DiffRow> rows = dfg.generateDiffRows(chars1, chars2);
		String oldLine = "";
		String newLine = "";
		for (DiffRow row : rows) {
			switch(row.getTag()) {
			case EQUAL: 
				oldLine += row.getOldLine();
				newLine += row.getNewLine();
				break;
			case CHANGE:
				oldLine += "<em>" + row.getOldLine()  + "</em>";
				newLine += "<em>" + row.getNewLine() + "</em>";
				break;
			case DELETE:
				oldLine += "<em>" +row.getOldLine() + "</em>";
				newLine += row.getNewLine();
				break;
			case INSERT:
				oldLine += row.getOldLine();
				newLine += "<em>" + row.getNewLine() + "</em>";
				break;
			default:				
				break;	
			}
		}		
		return new String[]{oldLine, newLine};
	}
	
	/**
	 * 与えられたURIからspreadDocumentを取得する。存在しない場合はnullを返す
	 * @param uri
	 * @param document
	 * @return
	 */
	private Element getSpreadDocumentByUri(String uri, Document document) {
		Element elem = null;
		try {
			XPathFactory xPathfactory = XPathFactory.newInstance();
			XPath xpath = xPathfactory.newXPath();
			XPathExpression expr;
			expr = xpath.compile("//spreadDocument[@uri=\""+ uri +"\"]");
			NodeList nl = (NodeList) expr.evaluate(document, XPathConstants.NODESET);
			elem = (Element) nl.item(0);
		} catch (XPathExpressionException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return elem;
	}
	private Element getSpreadTreeElement(Document document) {
		return (Element) document.getElementsByTagName("spreadTree").item(0);
	}
	
	
	/**
	 * パスからXMLドキュメントを取得する
	 * @param path
	 * @return document 
	 */
	private Document getDocument (String path) {
		Document doc = null;
			try {
				File fXmlFile = new File(path);
				DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
				DocumentBuilder dBuilder;
				dBuilder = dbFactory.newDocumentBuilder();
				doc = dBuilder.parse(fXmlFile);		
			} catch (ParserConfigurationException e) {
				e.printStackTrace();
			} catch (SAXException e) {
				e.printStackTrace();
			} catch (IOException e) {
				e.printStackTrace();
			}	
		return doc;
	}

	public Document getRevised() {
		return revised;
	}

	public void setRevised(Document revised) {
		this.revised = revised;
	}

	public TreeModel getDiffTree() {
		return diffTree;
	}

	public void setDiffTree(TreeModel diffTree) {
		this.diffTree = diffTree;
	}
	
	public static void main(String[] args) {
		GsxComparator comp = new GsxComparator("/Users/yuta/tmp/spread.xml","/Users/yuta/tmp/spread2.xml");
		comp.compare();
	}
}



