/*******************************************************************************
 * This file is part of GECAMed.
 * 
 * GECAMed is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License (L-GPL) as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * GECAMed is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public License (L-GPL)
 * along with GECAMed.  If not, see <http://www.gnu.org/licenses/>.
 * 
 * GECAMed is Copyrighted by the Centre de Recherche Public Henri Tudor (http://www.tudor.lu)
 * (c) CRP Henri Tudor, Luxembourg, 2008
 *******************************************************************************/
package lu.tudor.santec.gecamed.importexport.gui.importer;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.transform.Source;
import javax.xml.transform.TransformerException;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import javax.xml.xpath.XPathExpressionException;

import lu.tudor.santec.gecamed.core.utils.AutoEncodingFileReader;
import lu.tudor.santec.gecamed.core.utils.FileUtils;
import lu.tudor.santec.gecamed.importexport.utils.XMLFileFilter;
import lu.tudor.santec.gecamed.importexport.utils.XPathAPI;

import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

/**
 * 
 * 
 * 
 * @author Johannes Hermen johannes.hermen(at)tudor.lu
 * 
 * @version
 * <br>
 *          $Log: XMLFileImporter.java,v $
 *          Revision 1.28  2013-12-27 18:09:25  donak
 *          Cleanup of imports
 *
 *          Revision 1.27  2013-07-15 06:18:36  ferring
 *          logging changed
 *
 *          Revision 1.26  2013-03-07 07:47:13  kutscheid
 *          some more fixes for the importer (it now checks if all added files are present)
 *
 *          Revision 1.25  2013-02-18 13:25:32  kutscheid
 *          add a choice dialog for payment methods
 * <br>
 *          Revision 1.24 2013-02-14 09:54:54 kutscheid <br>
 *          first commit of the new/remodelled importer implementation <br>
 *          fix some nullpointer exceptions <br>
 * <br>
 *          Revision 1.23 2012-03-22 10:22:46 ferring <br>
 *          if no encoding format given, the encoder tries the last used and if
 *          it fails, it checks the format. <br>
 * <br>
 *          Revision 1.22 2012-03-06 14:54:48 ferring <br>
 *          encoding is now detected to load files <br>
 * <br>
 *          Revision 1.21 2012-02-07 10:40:00 ferring <br>
 *          printStackTraces replaced by logging <br>
 * <br>
 *          Revision 1.20 2008-09-25 09:43:06 heinemann <br>
 *          fixed copyrights <br>
 * <br>
 *          Revision 1.19 2008-09-05 14:17:57 heinemann <br>
 *          bogus copy/paste errors removed <br>
 * <br>
 *          Revision 1.18 2008-09-03 15:12:34 hermen <br>
 *          changed xml validator to show line/column of error <br>
 * <br>
 *          Revision 1.17 2008-09-03 15:11:50 hermen <br>
 *          changed xml validator to show line/column of error <br>
 * <br>
 *          Revision 1.16 2008-09-03 14:59:06 hermen <br>
 *          JSR 173 Streaming API for XML <br>
 * <br>
 *          Revision 1.15 2008-09-03 14:04:43 hermen <br>
 *          changed xml validator to show line/column of error <br>
 * <br>
 *          Revision 1.14 2008-07-22 15:10:21 hermen <br>
 *          updated mappings <br>
 * <br>
 *          Revision 1.13 2008-07-18 13:19:51 hermen <br>
 *          updated mapping <br>
 * <br>
 *          Revision 1.12 2008-07-18 12:03:29 hermen <br>
 *          added move2existing <br>
 * <br>
 *          Revision 1.11 2008-07-18 09:20:59 hermen <br>
 *          added logging anch dublicate checking <br>
 * <br>
 *          Revision 1.10 2008-07-17 12:34:07 hermen <br>
 *          *** empty log message *** <br>
 * <br>
 *          Revision 1.9 2008-07-17 12:14:07 hermen <br>
 *          changed to single patient per file <br>
 * <br>
 *          Revision 1.8 2008-07-17 12:07:48 hermen <br>
 *          *** empty log message *** <br>
 * <br>
 *          Revision 1.7 2008-07-16 09:34:58 hermen <br>
 *          updated patient mappings <br>
 * <br>
 *          Revision 1.6 2008-07-16 09:19:45 hermen <br>
 *          added getPatientInfo <br>
 * <br>
 *          Revision 1.5 2008-07-16 09:09:04 hermen <br>
 *          updated patient mappings <br>
 * <br>
 *          Revision 1.4 2008-07-15 08:04:24 hermen <br>
 *          added physician address <br>
 * <br>
 *          Revision 1.3 2008-07-14 15:03:47 hermen <br>
 *          added file moving methods <br>
 * <br>
 *          Revision 1.2 2008-07-14 14:33:25 hermen <br>
 *          first checkin of the xml2physician mapper <br>
 * <br>
 *          Revision 1.1 2008-07-14 12:35:10 hermen <br>
 *          initial checkin of the xml file importer <br>
 * 
 */
public class XMLFileImporter {

	private static final String XSD_FILENAME = "gecameddata.xsd";
	private static final String TEMP_DIR = System.getProperty("java.io.tmpdir");

	private static final String IMPORTED_DIR = "IMPORTED";
	private static final String EXISTING_DIR = "NOT_IMPORTED_EXISTING";
	private static final String ERROR_DIR = "ERROR";
	
	private static final String ZIP_EXTENTION	= ".zip";

	private static final String XSD_PATH = "/lu/tudor/santec/gecamed/importexport/gui/resources/" + XSD_FILENAME;

	/**
	 * static logger for this class
	 */
	private static Logger logger = Logger.getLogger(XMLFileImporter.class.getName());

	private XMLFileFilter xmlFileFilter = new XMLFileFilter();
	private Validator validator;
//	private XMLInputFactory xmlif;

	/**
	 * creates a new Importer that is capable of validating and parsing
	 * gecameddata xml files.
	 */
	public XMLFileImporter() {

		// copy the xsd file to tmp
		File xsd = setupXSD();
		// create a validator for the xsd file
		setupValidator(xsd);

	}

	/**
	 * creates a validator for the specifies XSD File
	 */
	private void setupValidator(File f) {

//		xmlif = XMLInputFactory.newInstance();
//		xmlif.setProperty(XMLInputFactory.IS_SUPPORTING_EXTERNAL_ENTITIES, Boolean.FALSE);
		try {
			SchemaFactory schemaFactory = SchemaFactory.newInstance("http://www.w3.org/2001/XMLSchema");
			Schema schemaGrammar = schemaFactory.newSchema(f);
			validator = schemaGrammar.newValidator();
		} catch (SAXException e) {
			logger.log(Level.WARN, "Failed to create Validator for " + XSD_FILENAME, e);
		}
	}

	/**
	 * lists all xml files from the given file/dir
	 * 
	 * @param importFile
	 *            the given file/dir containing the xml files
	 * @return an Collection of all Files to import.
	 */
	public Collection<File> getImportFiles(File importFile) {
		Collection<File> foundFiles = new ArrayList<File>();
		if (importFile.isDirectory()) {
			File[] xmlFiles = importFile.listFiles(xmlFileFilter);
			for (int i = 0; i < xmlFiles.length; i++) {
				if (isGECAMedDataFile(xmlFiles[i])) {
					foundFiles.add(xmlFiles[i]);
				}
			}
		} else {
			if (xmlFileFilter.accept(importFile.getParentFile(), importFile.getName()) && isGECAMedDataFile(importFile)) {
				foundFiles.add(importFile);
			}
		}
		return foundFiles;
	}

	/**
	 * checks if the given file is a GECAMedData File
	 * 
	 * @param importFile
	 *            the file to check
	 * @return true if yes, else false
	 */
	private boolean isGECAMedDataFile(File importFile) {
		BufferedReader br = null;
		try {
			br = new BufferedReader(new FileReader(importFile));
			for (int i = 0; i < 4; i++) {
				String line = br.readLine();
				if (line.toLowerCase().startsWith("<gecameddata")) {
					br.close();
					return true;
				}
			}
		} catch (Exception e) {
			return false;
		} finally {
			try {
				if (br != null) {
					br.close();
				}
			} catch (IOException e) {
			}
		}
		return false;
	}

	/**
	 * copies the xsd File to a tmp dir and returns its location.
	 * 
	 * @return
	 */
	private File setupXSD() {
		File xsdFile = null;
		try {
			xsdFile = new File(TEMP_DIR, XSD_FILENAME);
			xsdFile.deleteOnExit();
			FileUtils.writeStreamToFile(this.getClass().getResourceAsStream(XSD_PATH), xsdFile);
		} catch (Exception e) {
			logger.log(Level.WARN, "Failed to copy into temp directory " + xsdFile, e);
		}
		return xsdFile;
	}

	/**
	 * parses the given XML file into a DOM Document
	 * 
	 * @param gecamedDataFile
	 * @return
	 */
	public Document parseFile(File gecamedDataFile) throws Exception {
		if (!gecamedDataFile.canRead())
			return null;
		AutoEncodingFileReader encoder = null;
		try {
			encoder = new AutoEncodingFileReader(gecamedDataFile);
			return parseFile(encoder.read());
		} catch (Exception e) {
			logger.log(Level.WARN, e.getLocalizedMessage(), e);
			throw e;
		} finally {
			if (encoder != null)
				encoder.closeStream();
		}
	}
	
	/**
	 * parses the given XML file into a DOM Document
	 * 
	 * @param gecamedData
	 * @return
	 */
	public Document parseFile(String gecamedData) throws Exception {
		StringReader reader = null;
		try {
			reader = new StringReader(gecamedData);
			return parseFile(reader);
		} catch (Exception e) {
			logger.log(Level.WARN, e.getLocalizedMessage(), e);
			throw e;
		} finally {
			if (reader != null)
				reader.close();
		}
	}

	/**
	 * parses the given XML file into a DOM Document
	 * 
	 * @param gecamedDataFile
	 * @return
	 */
	public Document parseFile(Reader gecamedDataStream) throws Exception {
		Document resultDocument = null;
		InputSource is;
		DocumentBuilderFactory factory;
		DocumentBuilder builder;

		try {
			is = new InputSource(gecamedDataStream);
			factory = DocumentBuilderFactory.newInstance();
			factory.setNamespaceAware(true);
			builder = factory.newDocumentBuilder();
			resultDocument = (Document) builder.parse(is); // , TEMP_DIR +
															// File.separator);

		} catch (Exception e) {
			logger.log(Level.WARN, e.getLocalizedMessage(), e);
			throw e;
		}
		return resultDocument;
	}

	/**
	 * validates the given xml document against the xsd and returns
	 * true if validation is ok, else false
	 * 
	 * @param document
	 *            the document to validate
	 * @return true if validation is ok, else false
	 * @throws SAXException
	 *             , SAXParseException
	 * @throws XMLStreamException
	 * @throws Exception
	 */
	public boolean validateDocument(File f) throws IOException, SAXException, SAXParseException, XMLStreamException {
		if (f == null)
			return false;

		AutoEncodingFileReader encoder = null;
		InputStreamReader isr;
		Source source;

		try {
			encoder = new AutoEncodingFileReader(f);
			isr = encoder.read();
			source = new StreamSource(isr);
			validator.validate(source);
		} catch (SAXParseException e) {
			logger.log(Level.WARN, "Line: " + e.getLineNumber() + " Column:" + e.getColumnNumber() + "  " + e.getLocalizedMessage());
			throw e;
		} finally {
			if (encoder != null)
				encoder.closeStream();
		}
		return true;
	}

	/**
	 * returns the patients Name and Matr. for the patient in the specified
	 * Document
	 * 
	 * @param doc
	 * @return
	 */
	public String getPatientInfo(Document doc) {
		StringBuffer sb = new StringBuffer();
		try {

			Node patient = XPathAPI.selectSingleNode(doc, "/gecamedData/patient");

			if (patient != null) {
				Node matriculeNode = XPathAPI.selectSingleNode(patient, "matricule");
				Node lastNameNode = XPathAPI.selectSingleNode(patient, "lastName");
				Node firstNameNode = XPathAPI.selectSingleNode(patient, "firstName");
				if (lastNameNode != null) {
					sb.append(lastNameNode.getTextContent() + ", ");
				}
				if (firstNameNode != null) {
					sb.append(firstNameNode.getTextContent());
				}

				if (matriculeNode != null) {
					sb.append(" (").append(matriculeNode.getTextContent()).append(")");
				}

			} else {
				throw new TransformerException("Unable to find patient");
			}
		} catch (TransformerException e) {
			logger.log(Level.WARN, e.getLocalizedMessage(), e);
		} catch (XPathExpressionException e) {
			logger.log(Level.WARN, e.getLocalizedMessage(), e);
		}
		return sb.toString();
	}

	/**
	 * moves the given file to the IMPORTED dir
	 * 
	 * @param f
	 *            the file to move
	 * @return true on success, else false
	 */
	public boolean move2Imported(File f) {
		return move(f, IMPORTED_DIR);
	}

	/**
	 * moves the given file to the EXISTING_DIR dir
	 * 
	 * @param f
	 *            the file to move
	 * @return true on success, else false
	 */
	public boolean move2Existing(File f) {
		return move(f, EXISTING_DIR);
	}

	/**
	 * moves the given file to the ERROR dir
	 * 
	 * @param f
	 *            the file to move
	 * @return true on success, else false
	 */
	public boolean move2Error(File f) {
		return move(f, ERROR_DIR);
	}
	
	
	private boolean move (File file2Import, String move2SubDir)
	{
		// get and create (if not yet created) the move2Dir
		File parentDir = file2Import.getParentFile();
		File movePath = new File(parentDir, move2SubDir);
		movePath.mkdirs();
		
		// find the ZIP-file, containing the patient files
		File zipFile = getZipFileForImportFile(file2Import);
		
		return file2Import.renameTo(new File(movePath, file2Import.getName()))
				// only move the ZIP file, if the XML file was moved (therefore &&) and the ZIP file exists
				&& (zipFile == null || zipFile.renameTo(new File(movePath, zipFile.getName())));
	}
	
	
	/**
	 * @param file2Import The import (XML-)file.
	 * @return The ZIP-file belonging to the file2Import or <code>null</code>, if no belonging ZIP-file was found.
	 */
	public static File getZipFileForImportFile (File file2Import)
	{
//		return new File(file2import.getParentFile(), file2import.getName()
//				.substring(0,file2import.getName().lastIndexOf('.')+1) + "zip");
		
		File parentDir = file2Import.getParentFile();
		String baseName = FilenameUtils.getBaseName(file2Import.getName());
		
		File zipFile = new File(parentDir, baseName + ZIP_EXTENTION);
		
		if (zipFile.exists())
			return zipFile;
		else
			return null;
	}

//	/**
//	 * Just for testing....
//	 * 
//	 * @param args
//	 */
//	public static void main(String[] args) {
//		try {
//
//			Properties properties = new Properties();
//			properties.load(new FileInputStream(new File("conf/GECAMed.properties")));
//			// copy properties to the system properties
//			for (Object element : properties.keySet()) {
//				System.setProperty((String) element, properties.getProperty((String) element));
//			}
//			JaasJbossConfiguration.activateConfiguration();
//			CallbackHandler ch = new LoginCallbackHandler("admin", "admin");
//			LoginContext lc = new LoginContext("gecam_login", ch);
//			lc.login();
//			new InitialContext();
//
//			XMLFileImporter importer = new XMLFileImporter();
////	    System.out.println("parsing document");
//
//			File f = new File("import/gecameddata.xml");
//
//			Document d = importer.parseFile(f);
//
////	    System.out.println(importer.getPatientInfo(d));
//
////	    System.out.println("validating document");
//			importer.validateDocument(f);
//
////	    PhysicianMapper pm = new PhysicianMapper();
////	    Collection<Physician> phys = pm.getPhysicians(importer.getMapper(d));
////	    for (Iterator iter = phys.iterator(); iter.hasNext();) {
////		Physician phy = (Physician) iter.next();
////		System.out.print(phy);
////		if (pm.checkPhysicianExists(phy)) {
////		    System.out.print(" exists in DB");
////		} else {
////		    System.out.print(" is new");
////		    pm.insertPhysician(phy);
////		    System.out.println(" ........inserted!");
////		}
////		System.out.println();
////	    }
//
//			PatientMapper patientMapper = new PatientMapper();
//			if(!patientMapper.importPatientFromDocument(f, d, false, false)) {
//				throw new PatientAllreadyExitsException("Patient in " + f.getAbsolutePath() + " allready exists in the Database");
//			}
//
//		} catch (Exception e) {
//			logger.log(Level.ERROR, e.getMessage(), e);
//		}
////	System.out.println("done");
//	}
}
