/*
 * Copyright
 *   2009 axYus - www.axyus.com
 *   2009 C.Marchand - christophe.marchand@axyus.com
 *
 * This file is part of XEMELIOS.
 *
 * XEMELIOS is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * XEMELIOS 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with XEMELIOS; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
package fr.gouv.finances.dgfip.xemelios.batch;

import java.io.BufferedReader;
import java.io.File;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.lang.reflect.Constructor;
import java.net.Socket;
import java.util.List;
import java.util.Properties;
import java.util.Timer;
import java.util.TimerTask;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;

/**
 * This class plans batches, according to configuration file
 * @author chm
 */
public class BatchRunner {

    public static final int DEFAULT_STOP_PORT = 8006;
    private File configFile;
    private Timer timer;

    public static void main(String args[]) {
        if (args.length == 0) {
            displaySyntax();
            throw new RuntimeException("invalid syntax");
        }
        File configFileName = new File(args[0]);
        if (!configFileName.exists()) {
            throw new RuntimeException("config file not found: " + configFileName.getAbsolutePath());
        }
        if (args.length == 2 && "-stop".equals(args[1])) {
            new BatchRunner(configFileName, true);
        } else {
            new BatchRunner(configFileName);
        }
    }

    public BatchRunner(File configFile) {
        super();
        this.configFile = configFile;
        try {
            initialize();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public BatchRunner(File configFile, boolean stop) {
        super();
        this.configFile = configFile;
        if (!stop) {
            try {
                initialize();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        } else {
            try {
                DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
                domFactory.setNamespaceAware(true);
                DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
                Document config = domBuilder.parse(configFile);
                XPathFactory xpf = XPathFactory.newInstance();
                XPath xp = xpf.newXPath();
                XPath xp2 = xpf.newXPath();
                String sStopPort = (String) xp.evaluate("/batches/@stop-listen-port", config, XPathConstants.STRING);
                int stopPort = DEFAULT_STOP_PORT;
                try {
                    stopPort = Integer.parseInt(sStopPort);
                } catch (NumberFormatException nfEx) {
                    throw new Exception("config file contains an invalid stop-port number");
                }
                stop(stopPort);
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    protected void initialize() throws Exception {
        DocumentBuilderFactory domFactory = DocumentBuilderFactory.newInstance();
        domFactory.setNamespaceAware(true);
        DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
        Document config = domBuilder.parse(configFile);
        XPathFactory xpf = XPathFactory.newInstance();
        XPath xp = xpf.newXPath();
        XPath xp2 = xpf.newXPath();
        timer = new Timer("batch-runner");
        String sStopPort = (String) xp.evaluate("/batches/@stop-listen-port", config, XPathConstants.STRING);
        int stopPort = DEFAULT_STOP_PORT;
        try {
            stopPort = Integer.parseInt(sStopPort);
        } catch (NumberFormatException nfEx) {
            throw new Exception("config file contains an invalid stop-port number");
        }
        startStopListener(stopPort);
        Object ret = xp.evaluate("/batches/batch", config, XPathConstants.NODESET);
        if (ret instanceof NodeList) {
            NodeList nl = (NodeList) ret;
            for (int i = 0; i < nl.getLength(); i++) {
                Element batch = (Element) nl.item(i);
                String className = batch.getAttribute("class");
                String periodicity = batch.getAttribute("periodicity");
                Properties props = new Properties();
                NodeList parameters = (NodeList) xp2.evaluate("./parameter", batch, XPathConstants.NODESET);
                for (int j = 0; j < parameters.getLength(); j++) {
                    Element parameter = (Element) parameters.item(j);
                    String pName = parameter.getAttribute("name");
                    String pValue = null;
                    if (parameter.hasAttribute("value")) {
                        pValue = parameter.getAttribute("value");
                    } else {
                        pValue = parameter.getTextContent();
                    }
                    if (pName != null && pValue != null) {
                        props.setProperty(pName, pValue);
                    }
                }
                startNewBatch(className, periodicity, props);
            }
        } else if (ret instanceof List) {
            for (Element batch : (List<Element>) ret) {
                String className = batch.getAttribute("class");
                String periodicity = batch.getAttribute("periodicity");
                Properties props = new Properties();
                List<Element> parameters = (List<Element>) xp2.evaluate("./parameter", batch, XPathConstants.NODESET);
                for (Element parameter : parameters) {
                    String pName = parameter.getAttribute("name");
                    String pValue = null;
                    if (parameter.hasAttribute("value")) {
                        pValue = parameter.getAttribute("value");
                    } else {
                        pValue = parameter.getTextContent();
                    }
                    if (pName != null && pValue != null) {
                        props.setProperty(pName, pValue);
                    }
                }
                startNewBatch(className, periodicity, props);
            }
        }

    }

    protected void startNewBatch(String className, String periodicity, Properties props) {
        try {
            Class clazz = Class.forName(className);
            TimerBatchTask task = new TimerBatchTask(clazz, props);
            char unity = periodicity.charAt(periodicity.length() - 1);
            int qty = Integer.parseInt(periodicity.substring(0, periodicity.length() - 1));
            long period = 0;
            switch (unity) {
                case 's': {
                    period = qty * 1000;
                    break;
                }
                case 'm': {
                    period = qty * 1000 * 60;
                    break;
                }
                case 'h': {
                    period = qty * 1000 * 60 * 60;
                    break;
                }
                case 'd': {
                    period = qty * 1000 * 60 * 60 * 24;
                    break;
                }
                default:
                    throw new RuntimeException(periodicity + " is not a valid periodicity. Periodicity must be a quantity followed by 's', 'm', 'h' or 'd'");
            }
            timer.scheduleAtFixedRate(task, 1000, period);
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new RuntimeException(ex);
        }
    }

    public static void displaySyntax() {
        StringBuilder sb = new StringBuilder();
        sb.append("java ").append(BatchRunner.class.getName()).append(" <config-file> [-stop]\n\twhere <config-file> is the fully-qualified location of config file.");
        System.err.println(sb.toString());
    }

    public void stop(int port) throws Exception {
        Socket so = new Socket("localhost", port);
        BufferedReader br = new BufferedReader(new InputStreamReader(so.getInputStream()));
        PrintWriter pw = new PrintWriter(so.getOutputStream(), true);
        String resp = br.readLine();
        if (resp.startsWith("+OK")) {
            pw.println("stop");
            resp = br.readLine();
        }
        so.close();
    }

    protected void startStopListener(int port) throws Exception {
        StopServer stopServer = new StopServer(port, timer);
        try {
            stopServer.start();
        } catch (Exception ex) {
            ex.printStackTrace();
            throw new Exception("an other instance is already running.");
        }
    }

    public class TimerBatchTask extends TimerTask {

        private Class batchClass;
        private Properties props;

        public TimerBatchTask(Class clazz, Properties props) {
            super();
            this.batchClass = clazz;
            this.props = props;
        }

        @Override
        public void run() {
            try {
                Constructor<Batch> cstr = batchClass.getConstructor(String[].class);
                Batch batch = cstr.newInstance(new Object[]{new String[]{}});
                batch.setProps(props);
                batch.launchBatch();
            } catch (Exception ex) {
                ex.printStackTrace();
                throw new RuntimeException(ex);
            }
        }
    }
}
