/*
 * Créé le 8 nov. 2006
 * 
 */

package fr.gouv.finances.cp.xemelios.importers.batch;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FilenameFilter;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.zip.ZipEntry;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.PosixParser;
import org.apache.log4j.Logger;
import org.apache.log4j.xml.DOMConfigurator;

import fr.gouv.finances.cp.utils.Pair;
import fr.gouv.finances.cp.xemelios.common.Constants;
import fr.gouv.finances.cp.xemelios.common.PJRef;
import fr.gouv.finances.cp.xemelios.common.config.DocumentModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentsModel;
import fr.gouv.finances.cp.xemelios.common.config.DocumentsParser;
import fr.gouv.finances.cp.xemelios.data.DataConfigurationException;
import fr.gouv.finances.cp.xemelios.data.DataLayerManager;
import fr.gouv.finances.cp.xemelios.importers.EtatImporteur;
/**
 * This class allow to import XML files with a batch import.
 * 
 * 
 * @since 3.1
 * 
 * @author jptessier,chm
 */
public class BatchRealImporter {
	private static Logger logger = null;
	private ArrayList<File> files;
	private Pair budg,coll;
	private DocumentsModel docModels;
	private String documentid;
	private Vector<String> cmopts;
	private Options opts = new Options();
	private String[] arguments;
	private PosixParser posixparser;
	private boolean budgIsInterractif=false,collIsInterractif=false;

	public BatchRealImporter() throws Exception{
		this.files = new ArrayList<File>();
	}
	public static void main (String[] args) throws Exception{
		BatchRealImporter o = new BatchRealImporter();
		
		o.arguments=args;
		o.createLogger();
		//o.loadConfig();
		o.setCommandLineOptions();
		o.posixparser=new PosixParser();
		CommandLine cm = o.posixparser.parse(o.opts,o.arguments);
		Option[] s=cm.getOptions();
		o.cmopts = new Vector<String>();
		for(int i=0;i<s.length;i++){
			o.cmopts.add(s[i].getOpt());
		}
		if(!o.cmopts.contains("h")){
			// Chargement des fichiers de configuration
			o.loadConfig();
			// Type de document dans la ligne de commande?
			if(o.cmopts.contains("d")){
				// Recupération du type de document
				for(int i=0;i<args.length;i++){
					if(args[i].matches("-d")){
						o.documentid=args[i+1];
						System.out.println("docId ="+o.documentid);
					}
				}
				if(o.cmopts.contains("f")){
					Vector<String> fil= new Vector<String>();
					for(int i=0;i<args.length;i++){
						if(args[i].matches("-f")){
							for(int j=i+1;j<args.length;j++){
								fil.add(args[j]);
							}
						}
					}
					o.setFilesToImport(fil.toArray());
				}
				// Option fichier(s) présent(s) dans la ligne de commande?
				if(o.cmopts.contains("f")&&o.cmopts.contains("i")){
					Vector<String> fil= new Vector<String>();
					for(int i=0;i<args.length;i++){
						if(args[i].matches("-i")){
							if(args[i+1].matches("yes")){
								o.budgIsInterractif=true;
							}
							if(args[i+2].matches("yes")){
								o.collIsInterractif=true;
							}
						}
					}
					o.setFilesToImport(fil.toArray());
				}else{
					System.out.println("L'option fichier(s) est obligatoire dans la ligne de commande lorsque le mode interractif est declarer.");
				}
				o.importer(o.documentid);
			}else{
				System.out.println("L'option type de document est obligatoire dans la ligne de commande.");
			}
		}else{
			System.out.println("\n\nAide de l'importer batch.\n" +
					"_________________________\n\n" +
					"Syntaxe : importerBatch -d [typeDoc] -b [code][libelle] -c [code][libelle] -i [budget][collectivite] -f [fichier(s)]\n\n" +
					"-d : MODELE DU DOCUMENT à importer >> Option Obligatoire.\n\n" +
					"-b : BUDGET DU FICHIER  >> Option Facultative.\n\n" +
					"       - Si le budget est présent dans le fichier cette option est inutile.\n" +
					"       - Sinon précisez le libellé puis le code du budget.\n" +
					"		- Si pas d’argument pour le budget :\n" +
					"			- Si présence d’un budget par defaut dans fichier de conf, alors on celui-ci sera pris.\n" +
					"			- Sinon, abandon de l’import car le budget n'est pas définit.\n" +
					"		- Si option particulière –i y n alors mode interactif avec l’utilisateur dans la console." +
					"Pour chaque fichier sans budget, on pause la question à l’utilisateur.\n\n" +
					"-c : COLLECTIVITE DU FICHIER  >> Option Facultative.\n\n" +
					"		- Si la collectivité est présente dans le fichier, on l’utilise.\n" +
					"		- Sinon on prend celui de l’argument.\n" +
					"		- Si pas d’argument pour la collectivité, alors mode interactif avec l’utilisateur dans la console.\n" +
					"Pour chaque fichier sans collectivité, on pause la question à l’utilisateur.\n\n" +
					"-i : MODE INTERACTIF  >> Option Facultative.\n" +
					"		- y ou n permet d'activer l'option interactive pour l'element en question.\n\n" +
			"-f : NOM DU/DES FICHIER(S) à importer");
		}	
	}
	private void setFilesToImport(Object[] nomFichier){
		for(int i=0;i<nomFichier.length;i++){
			files.add(new File(nomFichier[i].toString()));
		}
	}
	private void setCommandLineOptions(){
		opts.addOption("d",true,"Modele de Document");
		opts.getOption("d").setArgs(1);
		opts.addOption("b",true,"Budget du document");
		opts.getOption("b").setArgs(2);
		opts.addOption("c",true,"Collectivite du document");
		opts.getOption("c").setArgs(2);
		opts.addOption("i",true,"Mode interractif pour la saisie du budget ou de la collectivite du document");
		opts.getOption("i").setArgs(2);
		opts.addOption("f",true,"Fichier(s) a importer");
		opts.addOption("h",false,"Aide");
		opts.addOption("help",false,"Aide");
	}
	private void importer(String docId) {
		DocumentModel dm = docModels.getDocumentById(docId);
		try {
			Class clazz = Class.forName(dm.getImportClass());
			Object obj = clazz.newInstance();
			if(!(obj instanceof EtatImporteur)) {
				System.out.println("Cette classe n'est pas un importeur.\n" +
						"Le fichier de configuration qui vous a été livré est certainement invalide.\n" +
				"Veuillez contacter votre fournisseur.");
				return;
			}
			EtatImporteur ei = (EtatImporteur)obj;
			if(!collIsInterractif){
				// Si collectivite présente dans la ligne de commande
				if(cmopts.contains("c") && coll==null){
					for(int i=0;i<arguments.length;i++){
						if(arguments[i].matches("-c")){
							coll = new Pair(arguments[i+1],arguments[i+2]);
						}
					}
				}
			}
			if(!budgIsInterractif){
				if(cmopts.contains("b") && budg==null){
					for(int i=0;i<arguments.length;i++){
						if(arguments[i].matches("-b")){
							budg = new Pair(arguments[i+1],arguments[i+2]);
						}
					}
				}else{
					// Recuperration du budget par defaut à partir du fichier de conf (default-budget)
					if(!dm.getDefaultBudgets().isEmpty()){
						budg = dm.getDefaultBudgets().firstElement();
					}else{
						System.out.println("Aucun budget définit pour ce fichier.\n" +
								"Le budget n'est definit ni dans le fichier,ni dans le fichier de configuration et ni dans la ligne de commande.\n" +
						"L'import a donc été annulé pour faute de budget.");				
					}
				}
			}
			ImportServiceBatchProvider isbp = new ImportServiceBatchProvider(dm,coll,budg);
			ei.setImpSvcProvider(isbp);
			ei.setDocument(dm);
			ImportContent fToImport = files(dm.getExtension(),dm.getBalise());
			ei.setFilesToImport(fToImport.filesToImport);
			ei.setPjs(fToImport.pjs);
			ei.execute();
		} catch(Exception ex) {
			logger.error("private void importer(String docId)");
			ex.printStackTrace();
		}
	}	
	public class ImportContent {
		ArrayList<File> filesToImport=new ArrayList<File>();
		ArrayList<PJRef> pjs;
		public ImportContent(ArrayList<File> f) {
			super();
			filesToImport = f;
			pjs = new ArrayList<PJRef>();
		}
	}	
	private void createLogger(){
		String log4jConfFile = System.getProperty("xemelios.log4j.xml");
		DOMConfigurator.configure(log4jConfFile);
		logger = Logger.getLogger(BatchRealImporter.class);
		logger.info("log4J initialise");
	}	
	protected void loadConfig() throws DataConfigurationException {
		docModels = getDocumentsInfos(System.getProperty(Constants.SYS_PROP_DOC_DEF_DIR));
		initDataLayer();
	}
	protected void initDataLayer() throws DataConfigurationException {
		String availableLayers = System.getProperty(Constants.SYS_PROP_AVAILABLE_LAYERS);
		StringTokenizer tokenizer = new StringTokenizer(availableLayers, ",");
		while(tokenizer.hasMoreTokens()) {
			String className = tokenizer.nextToken();
			try {
				Class.forName(className);
			} catch (ClassNotFoundException cnfEx) {
				logger.error("instanciating data layer: ", cnfEx);
			}
		}
		DataLayerManager.setDataImpl(System.getProperty(Constants.SYS_PROP_DATA_IMPL));
	}
	protected DocumentsModel getDocumentsInfos(String repertoires) {
		DocumentsModel ret = null;
		String[] dirs = repertoires.split(",");
		for(String repertoire:dirs) {
			try {
				File dir = new File(repertoire);
				logger.debug("Searching " + repertoire);
				if (!dir.exists()) {
					logger.info(repertoire + " does not exists.");
					continue;
				}
				// Fichiers de config
				File[] fichiers = dir.listFiles(new FilenameFilter() {
					public boolean accept(File dir, String name) {
						return name.toUpperCase().endsWith(".XML") && !"build.xml".equals(name);
					}
				});
				if (fichiers == null) {
					logger.info("No file found in " + repertoire);
					return null;
				}
				DocumentsParser dp = new DocumentsParser();
				for (int i = 0; i < fichiers.length; i++) {
					dp.parse(fichiers[i]);
					DocumentsModel dm = (DocumentsModel) dp.getMarshallable();
					if (ret == null) {
						ret = dm;
						for(DocumentModel docModel:dm.getDocuments()) {
							docModel.setBaseDirectory(repertoire);
						}
					} else {
						for(DocumentModel docModel : dm.getDocuments()) {
							docModel.setBaseDirectory(repertoire);
							ret.addChild(docModel, DocumentModel.QN);
						}
					}
				}
			} catch (Exception ex) {
				logger.error("getDocumentsInfos repertoire="+repertoire);
				logger.error( ex);
			}
		}
		return ret;
	}
	private ImportContent files(final String extension, final String titreEtat) {
		ImportContent ic = new ImportContent(files);
		Vector<File> ret=new Vector<File>();
		for(int i=0;i<ic.filesToImport.size();i++){
			ret.add(ic.filesToImport.get(i));
		}
		// on regarde si l'un des fichiers a importer est un zip
		File tmpDir = new File(System.getProperty("java.io.tmpdir"));
		for (int i = 0; i < ret.size(); i++) {
			if (ret.get(i).getName().toLowerCase().endsWith(".zip")) {
				if (ret.get(i).exists()) {
					ZipFile zf = null;
					try {
						zf = new ZipFile(ret.get(i));
						for (Enumeration<? extends ZipEntry> enumer = zf
								.entries(); enumer.hasMoreElements();) {
							ZipEntry ze = enumer.nextElement();
							if (!ze.isDirectory()) {
								String fileName = ze.getName();
								String entryName = fileName.toLowerCase();
								fileName = fileName.replace(
										File.pathSeparatorChar, '_').replace(
												File.separatorChar, '_').replace(':',
												'|').replace('\'', '_').replace('/',
												'_');
								logger.debug(entryName);
								if(PJRef.isPJ(ze)) {
									PJRef pj = new PJRef(ze);
									pj.writeTmpFile(tmpDir,zf);
									ic.pjs.add(pj);
								} else if ((entryName.endsWith(extension.toLowerCase()) || entryName.endsWith(".xml")) && !fileName.startsWith("_")) {
									// on decompresse le fichier dans le
									// repertoire temporaire, comme ca il sera
									// supprime en quittant
									InputStream is = zf.getInputStream(ze);
									BufferedInputStream bis = new BufferedInputStream(is);
									File output = new File(tmpDir, fileName);
									BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(output));
									byte[] buffer = new byte[1024];
									int read = bis.read(buffer);
									while (read > 0) {
										bos.write(buffer, 0, read);
										read = bis.read(buffer);
									}
									bos.flush();
									bos.close();
									bis.close();
									ic.filesToImport.add(output);
								}
							}
						}
						zf.close();
					} catch (ZipException zEx) {
						System.out.println("Le fichier "
								+ ret.get(i).getName()
								+ " n'est pas une archive ZIP valide.");
					} catch (IOException ioEx) {
						ioEx.printStackTrace();
					} finally {
						if(zf!=null) {
							try { zf.close(); } catch(Throwable t) {}
						}
					}
				}
			} else {
				ic.filesToImport.add(ret.get(i));
			}
		}
		return ic;
	}
}
