/*
 * COPYRIGHT (c) 2010 by Institute of Computer Science, 
 * Foundation for Research and Technology - Hellas
 * Contact: 
 *      POBox 1385, Heraklio Crete, GR-700 13 GREECE
 *      Tel:+30-2810-391632
 *      Fax: +30-2810-391638
 *      E-mail: isl@ics.forth.gr
 *      http://www.ics.forth.gr/isl/cci.html
 * 
 *  This work is licensed under a Creative Commons Attribution-ShareAlike 3.0 
 * Unported License (http://creativecommons.org/licenses/by-sa/3.0/)
*/

import org.w3c.dom.*;
import org.xml.sax.*;

import javax.xml.parsers.*;

import java.io.*;

import java.util.UUID;
import java.util.ArrayList;

public class Convertor {
	static Document document;
	static RdfsParser rdfsparser;
	static ArrayList<String[]> pinakasUUIDs = new ArrayList<String[]>();
	
	public Convertor(){
		rdfsparser = new RdfsParser();
	}
	
	public static boolean convert(String fileName,String filePath,String outString) throws ConversionCrashedException {
		boolean ParsingError = false;
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		try {
			DocumentBuilder builder = factory.newDocumentBuilder();
			document = builder.parse(new File(filePath+"\\"+fileName));
			
			OutputStreamWriter osw = new OutputStreamWriter(new FileOutputStream(outString+"\\"+fileName.replaceAll(".xml", ".rdf"),false),"UTF8");
	        PrintWriter pw = new java.io.PrintWriter( osw, true );
	        
			pw.println("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<rdf:RDF xml:lang=\"en\" xmlns:rdf=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:rdfs=\"http://www.w3.org/2000/01/rdf-schema#\"");
			for (int i=0;i<rdfsparser.uri.size();i++)
				pw.println("  xmlns:" + rdfsparser.namespace.get(i) + "= \"" +  rdfsparser.uri.get(i) +"\"");
			
			pw.println(">\n");
			traverse_dom(document.getChildNodes(),0,pw);
			pw.println("</rdf:RDF>");
			System.out.println(outString+"\\"+fileName.replaceAll(".xml", ".rdf")+"\n\n\n");
			if (ParsingError = rdfsparser.validate(outString+"\\"+fileName.replaceAll(".xml", ".rdf"))) 
					System.out.println("\n\nerrors in " + outString+"\\"+fileName.replaceAll(".xml", ".rdf")+"\n\n\n");
			

		} catch (SAXException sxe) {// Error generated during parsing)
			Exception x = sxe;
			if (sxe.getException() != null)
				x = sxe.getException();
			System.out.println("***Convert: Error generated during parsing --->"+x+"\n\n");
			//x.printStackTrace();

		} catch (ParserConfigurationException pce) {// Parser with specified options can't be built
			System.out.println("***Convert: Parser with specified options can't be built --->"+pce+"\n\n");
			//pce.printStackTrace();

		} catch (IOException ioe) {// I/O error
			System.out.println("***Convert: I/O error ---> "+ioe+"\n\n");
			//ioe.printStackTrace();
		}
		return ParsingError;
	}
	
	public static void traverse_dom(NodeList nodelist,int indent,PrintWriter pw) throws ConversionCrashedException{
		for(int i=0;i<nodelist.getLength();i++){
			Node currNode = nodelist.item(i);
			
			if(currNode.getNodeName().compareTo("#text")!=0){
				
				/* check all the tags that start from P (CIDOC-CRM properties), L (CRMdigital properties), CLR, CLP and R (FRBR properties) and E (<E1.CRM_Entity>) */
				if(currNode.getNodeName().charAt(0)=='P'|| currNode.getNodeName().charAt(0)=='L'|| currNode.getNodeName().charAt(0)=='E' ||currNode.getNodeName().startsWith("CL")== true ||currNode.getNodeName().charAt(0)=='R'){
						if(currNode.getNodeName().charAt(0)!='E') { //exclude <E1.CRM_Entity>
						for(int j=0;j<indent;j++)
							pw.print("	");
						
						pw.print("<"+ find_namespace(currNode.getNodeName()) +":"+ format_string(currNode.getNodeName()) );
						//Copy in rdf code any existing attributes.
						if(currNode.hasAttributes())
						{
							for(int k=0;k<currNode.getAttributes().getLength();k++)
							{
								pw.print(" ");
								pw.print(currNode.getAttributes().item(k).getNodeName() + "=\"" + currNode.getAttributes().item(k).getNodeValue() + "\"");
							}
						}
						pw.print(">\n");
					}
					
					Node identifier = find_identifier(currNode);
					Node first_class = find_next_class(currNode.getFirstChild());
					
					// check if we have multiple instantiation (flag > 1)
					int flag = 0;
					NodeList multiNode = currNode.getChildNodes();
					for (int k=0; k<multiNode.getLength();k++) {
						if (multiNode.item(k).getNodeName().compareTo("in_class")==0)
							flag += 1; 
					}
					
					if(identifier!=null){
						found_identifier(currNode,identifier,first_class,indent,pw,flag);
					}else if(find_string(currNode)!=null){//has_note timespans etc <STRING>
						found_STRING_elem(currNode,indent,pw);
					}else if(find_nonempty_string(currNode.getFirstChild())!=null){
						found_only_text(currNode,indent,pw);
					}
					if(currNode.getNodeName().charAt(0)!='E'){ //exclude <E1.CRM_Entity>
						for(int j=0;j<indent;j++)
							pw.print("	");	
						pw.print("</"+ find_namespace(currNode.getNodeName()) +":"+ format_string(currNode.getNodeName()) +">\n");
					}
					pw.flush();
				}else
					traverse_dom(currNode.getChildNodes(),indent+1,pw);
			}
		}
	}
	
	public static void found_identifier(Node currNode,Node identifier,Node first_class,int indent,PrintWriter pw, int flag)
	throws ConversionCrashedException{
		Node curr_class = first_class;
		Node last_class = first_class;
		
		while(curr_class!=null){
				
			if(curr_class!=first_class){
				for(int j=0;j<indent+1;j++)
					pw.print("	");
				pw.print("</"+ find_namespace(get_value(last_class)) +":" + format_string(get_value(last_class))+">\n");//Close Class Entity
				for(int j=0;j<indent;j++)
					pw.print("	");	
				pw.print("</"+ find_namespace(currNode.getNodeName()) +":"+ format_string(currNode.getNodeName()) +">\n");//Close P
				for(int j=0;j<indent;j++)
					pw.print("	");
				pw.print("<"+ find_namespace(currNode.getNodeName()) +":"+ format_string(currNode.getNodeName()) +">\n");//Reopen P
			}
			
			
//System.out.println ("after while curr_class" + get_value(curr_class));
			//rdfsparser.getType(get_value(curr_class));
				
			for(int j=0;j<indent+1;j++)
				pw.print("	");
			
			// Apostolis - START
			// Σε αυτό το σημείο, το πρόγραμμα ελέγχει εάν το value του Identifier είναι μοναδικό. δηλαδή είναι URL, URN, UUID ή GUID. 
			// Στην συνέχεια, έαν δεν είναι μοναδικό, διαμορφώνει τον κώδικα του RDF βάζοντας ως rdf:about ένα UUID που έχει δημιουργήσει η function "giveRandomUUID()"
			// και βάζοντας ως rdfs:label το μη-μοναδικό value που υπήρχε αρχικά στο Identifier. Εαν το value είναι όντως μοναδικό, τότε δημιουργεί τον κώδικα βάζοντας ως rdf:about 
			// το μοναδικό value και χωρίς rdfs:label.
			
			String identifierValue = get_value(identifier);
			String idLow = identifierValue.toLowerCase();
			String rdfLabel = "";
			if ( (idLow.contains("http://")) || (idLow.contains("ftp://")) || (idLow.contains("file://")) || (idLow.contains("uuid:")) || (idLow.contains("urn:")) || (idLow.contains("guid:"))){
				// Εδώ το πρόγραμμα δημιουργεί τον κώδικα του RDF. Εχει αφαιρεθεί το "prefix" που υπήρχε πριν.
				pw.print("<"+ find_namespace(get_value(curr_class)) +":"+format_string(get_value(curr_class))+" rdf:about=\""+format_string(identifierValue)+"\">\n");
			}
			else {		
				// Σε αυτό το σημείο είναι σίγουρο ότι θα χρησιμοποιηθεί ένα UUID για τον Identifier, και το πρόγραμμα ελέγχει εάν
				// ο ίδιος Identifier με το ίδιο class έχει πάρει ήδη κάποιο UUID από προηγούμενο πέρασμα, στα πλαίσια του ίδιου αρχείου XML.
				// Εάν βρει τέτοια περίπτωση, το πρόγραμμα χρησιμοποιεί το ίδιο UUID με παλαιότερα, ενώ σε διαφορετική περίπτωση δημιουργείται
				// νέο UUID και ενημερώνεται ο πίνακας pinakasUUIDs με τις νέες αντιστoιχίες. Τα παραπάνω τα καταφέρνει με την χρήση της checkDuplicate()
				rdfLabel = identifierValue;
				String [] tempArray = new String [3];
				tempArray[0]=rdfLabel;
				tempArray[1]=get_value(curr_class);
				tempArray[2]="";
				String checkValue; //=checkDuplicate(tempArray);
				
				if (flag == 1) 
				    checkValue=checkDuplicate(tempArray, "");
				else {// multiple instantiation - all instances must have the same uuid
					checkValue=checkDuplicate(tempArray, get_value(last_class));
					tempArray[2]=checkValue;
					pinakasUUIDs.add(tempArray);
				}
				
				if (checkValue.equals("")){
					// Αν η checkDuplicate() έχει γυρίσει κενό String, αυτό σημαίνει ότι ο Identifier πρέπει να πάρει νέο UUID
					tempArray[2]=giveRandomUUID();
					identifierValue = "uuid:"+tempArray[2];
					pinakasUUIDs.add(tempArray);
				}
				else {
					// Αν η checkDuplicate() δεν έχει γυρίσει κενό, αυτό σημαίνει ότι βρήκε στον πίνακα pinakasUUIDs αυτόν τον Identifier (με το ίδιο class) και έχει γυρίσει
					// αντί κενό String, το UUID που πρέπει να χρησιμοποιηθεί.
					identifierValue = "uuid:"+checkValue;
					// Με όποιον τρόπο και αν γίνει από τους δύο παραπάνω, στο τέλος η μεταβλητή identifierValue θα έχει ένα UUID που το πρόγραμμα 
					// χρησιμοποιεί στην αμέσως επόμενη γραμμή κώδικα για να δημιουργήσει το RDF.
				}
				// Εδώ το πρόγραμμα δημιουργεί τον κώδικα του RDF. Σε αυτήν την περίπτωση το πρόγραμμα προσθέτει και μια rdfs label
				// στην οποία μεταφέρει την αρχική, μη μοναδική, τιμή του identifier.
				//ME _ ANTI GIA KENA STO LABEL pw.print("<crm:"+format_string(get_value(curr_class))+" rdf:about=\""+format_string(identifierValue)+"\" rdfs:label=\""+format_string(rdfLabel)+"\">\n");
				pw.print("<"+ find_namespace(get_value(curr_class)) +":"+format_string(get_value(curr_class))+" rdf:about=\""+format_string(identifierValue)+"\" rdfs:label=\""+rdfLabel+"\">\n");
				
			// Apostolis - END
			
			}
					
			last_class=curr_class;
			curr_class = find_next_class(last_class);
				
		}
		pw.flush();
		traverse_dom(currNode.getChildNodes(),indent+1,pw);
		for(int j=0;j<indent+1;j++)
			pw.print("	");	
		pw.print("</"+ find_namespace(get_value(last_class)) +":"+ format_string(get_value(last_class)) +">\n");
	}
	
	public static void found_STRING_elem(Node currNode,int indent,PrintWriter pw) throws ConversionCrashedException{
		Node string = find_string(currNode);
		for(int j=0;j<indent+1;j++)
			pw.print("	");		
		pw.print(" "+string.getNodeValue()+"\n");
		traverse_dom(currNode.getChildNodes(),indent+1,pw);	
	}
	
	public static void found_only_text(Node currNode,int indent,PrintWriter pw) throws ConversionCrashedException{
		Node text = find_nonempty_string(currNode.getFirstChild());
		for(int j=0;j<indent+1;j++)
			pw.print("	");		
		pw.print(" "+text.getNodeValue()+"\n");
		traverse_dom(currNode.getChildNodes(),indent+1,pw);	
	}
	
	public static Node find_identifier(Node node){
		Node child = node.getFirstChild();
		while(child!=null){
			if(child.getNodeName().compareTo("Identifier")==0)
				return child;
			child=child.getNextSibling();
		}
		return null;
	}
	
	public static Node find_string(Node node){//psaxnei gia member String kai epistrefei to paidi tou pou exei non-empty #text
		Node child = node.getFirstChild();
		while(child!=null){
			if(child.getNodeName().compareTo("String")==0){
				Node eggoni=child.getFirstChild();
				return find_nonempty_string(eggoni);
			}
			child=child.getNextSibling();
		}
		return null;
	}
	
	public static Node find_nonempty_string(Node node){
		while(node!=null){
			if(node.getNodeName().compareTo("#text")!=0)
				return null;
			if(node.getNodeValue().trim().length()!=0)
				return node;
			node=node.getNextSibling();
		}		
		return null;
	}
	
	public static Node find_next_class(Node node){
	
		node = node.getNextSibling();
		while(node!=null){
			if(node.getNodeName().compareTo("in_class")==0) 
					return node;
			node = node.getNextSibling();
		}
		return null;
	}
	
	public static String[] is_multiple_instantiation(String multiclass){
	
	if((multiclass.indexOf(","))!=-1) {
				System.out.println ("multiclass" + multiclass);
				String [] multiNodes = new String [10];
				multiNodes = multiclass.split(",");
				System.out.println ("multinodes" + multiNodes[0].trim()+ multiNodes[1].trim());
			return multiNodes;}
	return null;
	}
	
	public static String format_string(String str){
		//return str.trim().replace('.','_').replace(' ','_');
		return str.trim().replace(' ','_').replace("<", "&lt;");
	}
	
	public static String get_value(Node node){
		if(node==null)
			return "node_null_error";
		if(node.getFirstChild()==null)
			return "child_node_null_error";
		return node.getFirstChild().getNodeValue();
	}
	
	// Apostolis - START
	
	// H function giveRandomUUID()
	// Αυτή η function είναι ουσιαστικά ένας generator ο οποίος παράγει UUIDs type 4.
	public static String giveRandomUUID(){
		UUID u = UUID.randomUUID();
		return u.toString();
	}
	
	// Η function checkDuplicate()
	// Αυτή η function ελέγχει εάν το Identifier και η Κλάση που της δίνεται, υπάρχει ήδη στον πίνακα pinakasUUIDs.
	// Εάν δεν υπάρχουν, σημαίνει ότι πρέπει να δοθεί νέο UUID και η function γυρίζει κενό String. Το ίδιο συμβαίνει
	// και όταν ο πίνακας με τα UUIDs είναι άδειος.
	// Όταν η function βρει μια εγγραφή στο πίνακα που ταιριάζει, δλδ έχει ίδιο Identifier και Κλάση με το δοθέν, τότε
	// γυρίζει το αντίστοιχο UUID για να χρησιμοποιηθεί παραπάνω από το πρόγραμμα.
	public static String checkDuplicate(String[] checkArray, String last_class){
		if (pinakasUUIDs.isEmpty()){
			return "";
		}
		else {
			for (int i=0; i<pinakasUUIDs.size(); i++){
				String[] temp=pinakasUUIDs.get(i);
				if (temp[0].equals(checkArray[0]) && (temp[1].equals(checkArray[1]) || temp[1].equals(last_class))){
					return temp[2].toString();
				}
			}
			return "";
		}	
	}
	//Apostolis - END

	// This function returns the appropriate namespace for CIDOC, FRBR and CRMdig
	public static String find_namespace(String currentTag){
		String namespace = "";
		if(currentTag.charAt(0)=='P'|| currentTag.charAt(0)=='E')
			namespace = "crm";
		else if (currentTag.charAt(0)=='D' || currentTag.charAt(0)=='L')
			namespace = "crmdig";
		else if (currentTag.charAt(0)=='F' || currentTag.startsWith("CL")== true ||currentTag.charAt(0)=='R')
			namespace = "frbr";
		return namespace;
		
	}
	
}
