/*
 * Copyright
 *   2007 axYus - www.axyus.com
 *   2007 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.cp.xemelios.updater;

import fr.gouv.finances.cp.xemelios.updater.config.ComponentModel;
import fr.gouv.finances.cp.xemelios.updater.config.SiteModel;
import fr.gouv.finances.cp.xemelios.updater.config.UpdateParser;
import fr.gouv.finances.cp.xemelios.updater.config.XemeliosUpdateModel;
import fr.gouv.finances.dgfip.xemelios.common.NetAccess;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.util.Hashtable;
import java.util.List;
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.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

/**
 *
 * @author chm
 */
public class UpdaterUtil {
    private static Logger logger = Logger.getLogger(UpdaterUtil.class);
    
    private String updateUrl, componentsFileLocation;
    private XPathFactory xpf = null;
    private DocumentBuilderFactory domFactory;
    private Hashtable<String,String> installedComponents;
    private XemeliosUpdateModel downladableComponents;
    
    /** Creates a new instance of UpdaterUtil */
    public UpdaterUtil(String updateUrl, String componentsFileLocation) {
        super();
        this.updateUrl=updateUrl;
        this.componentsFileLocation=componentsFileLocation;
        try {
            xpf = XPathFactory.newInstance(XPathFactory.DEFAULT_OBJECT_MODEL_URI);
            domFactory = DocumentBuilderFactory.newInstance();
        } catch(Throwable t) {
            xpf = XPathFactory.newInstance();
            t.printStackTrace();
        }
    }
    private XemeliosUpdateModel getUpdatesAtUrl(String urlLocation) throws Exception {
        HttpClient client = NetAccess.getHttpClient();
        GetMethod meth = new GetMethod(urlLocation);
        int httpRet = client.executeMethod(meth);
        if(httpRet>300) throw new Exception(meth.getStatusText());
        InputStream is = meth.getResponseBodyAsStream();
        UpdateParser up = new UpdateParser();
        up.parse(is);
        is.close();
        XemeliosUpdateModel xum = (XemeliosUpdateModel) up.getMarshallable();
        return xum;
    }
    public boolean areThereUpdatesAvailable() {
        try {
            loadInstalledComponents();
            XemeliosUpdateModel xum = getUpdatesAtUrl(updateUrl);
            return checkUpdates(xum);
        } catch(Exception ex) {
            ex.printStackTrace();
        }
        return false;
    }
    private boolean checkUpdates(XemeliosUpdateModel xum) {
        logger.debug("checking for updates");
        boolean ret = false;
        if(xum.getConfigUpdates()!=null && xum.getConfigUpdates().getComponents().size()>0) {
            for(ComponentModel cm:xum.getConfigUpdates().getComponents()) {
                String existingVersion=installedComponents.get(cm.getId());
                if(existingVersion==null) return true;
                if(compareReleases(cm.getRelease().getV(),existingVersion)>0) return true;
            }
        }
        if(xum.getFeaturesUpdates()!=null && xum.getFeaturesUpdates().getComponents().size()>0) {
            for(ComponentModel cm:xum.getFeaturesUpdates().getComponents()) {
                String existingVersion=installedComponents.get(cm.getId());
                if(existingVersion==null) return true;
                if(compareReleases(cm.getRelease().getV(),existingVersion)>0) return true;
            }
        }
        if(xum.getXemeliosUpdates()!=null && xum.getXemeliosUpdates().getComponents().size()>0) {
            for(ComponentModel cm:xum.getXemeliosUpdates().getComponents()) {
                String existingVersion=installedComponents.get(cm.getId());
                if(existingVersion==null) return true;
                if(compareReleases(cm.getRelease().getV(),existingVersion)>0) return true;
            }
        }
        if(xum.getForeignSites()!=null) {
            for(SiteModel sm:xum.getForeignSites().getSites()) {
                try {
                    if(checkUpdates(getUpdatesAtUrl(sm.getText()))) return true;
                } catch(Exception ex) {}
            }
        }
        return false;
    }
    protected void loadInstalledComponents() throws FileNotFoundException, ParserConfigurationException, SAXException, IOException {
        installedComponents = new Hashtable<String,String>();
        InputSource is = new InputSource(new FileReader(new File(componentsFileLocation)));
        // on parse avant pour etre sur de l'implementation utilise (on veut du JAXP)
        DocumentBuilder domBuilder = domFactory.newDocumentBuilder();
        Document dom = domBuilder.parse(is);
        try {
            XPath xp = xpf.newXPath();
            Object o;
            o = xp.evaluate("//component", dom, XPathConstants.NODESET);
            if (o instanceof NodeList) {
                NodeList nl = (NodeList) o;
                for (int i = 0; i < nl.getLength(); i++) {
                    Element el = (Element) nl.item(i);
                    String compId;
                    try {
                        compId = (String) xp.evaluate("./@id", el, XPathConstants.STRING);
                        String compRelease = (String) xp.evaluate("./@version", el, XPathConstants.STRING);
                        installedComponents.put(compId,compRelease);
                    } catch (XPathExpressionException ex) {
                        ex.printStackTrace();
                    }
                }
            } else if (o instanceof List) {
                List l = (List) o;
                for (Object obj : l) {
                    if (obj instanceof Element) {
                            Element el = (Element) obj;
                            String compId;
                            try {
                                compId = (String) xp.evaluate("./@id", el, XPathConstants.STRING);
                                String compRelease = (String) xp.evaluate("./@version", el, XPathConstants.STRING);
                                installedComponents.put(compId,compRelease);
                            } catch (XPathExpressionException ex) {
                                ex.printStackTrace();
                            }
                    } else {
                        // erreur incomprehensible
                    }
                }
            } else {
                // erreur incomprehensible
            }
        } catch (XPathExpressionException ex) {
        }
    }
    public static int compareReleases(String r1, String r2) {
        String[] s1 = splitRelease(r1);
        String[] s2 = splitRelease(r2);
        int ret = 0;
        for(int i=0;i<Math.min(s1.length,s2.length);i++) {
            float a = -1f, b = -1f;
            try {
                a = Integer.parseInt(s1[i]);
            } catch(Throwable t) {
                if(s1[i].endsWith("+")) {
                    // if there is a "+" in the release number, it is a non-official release,
                    // based on the previous build number. So the "real" number is the previous + 0.5
                    try {
                        a = Integer.parseInt(s1[i].substring(0, s1[i].length()-1));
                        a += 0.5;
                    } catch(Throwable t2) {}
                }
            }
            try {
                b = Integer.parseInt(s2[i]);
            } catch(Throwable t) {
                if(s2[i].endsWith("+")) {
                    // if there is a "+" in the release number, it is a non-official release,
                    // based on the previous build number. So the "real" number is the previous + 0.5
                    try {
                        b = Integer.parseInt(s2[i].substring(0, s2[i].length()-1));
                        b += 0.5;
                    } catch(Throwable t2) {}
                }
            }
            if(a!=-1f && b!=-1f) {
                if(a<b) ret = -1;
                else if(a==b) ret = 0;
                else ret = 1;
            } else {
                ret = s1[i].compareTo(s2[i]);
            }
            if(ret!=0) {
                return ret;
            }
        }
        if(s1.length>s2.length) ret = 1;
        else if(s1.length<s2.length) ret = -1;
        return ret;
    }
    private static String[] splitRelease(String r) {
        return r.split("\\.");
    }
    
}
