/*
 * The contents of this file are subject to the terms of the Common Development
 * and Distribution License (the License). You may not use this file except in
 * compliance with the License.
 * 
 * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
 * or http://www.netbeans.org/cddl.txt.
 * 
 * When distributing Covered Code, include this CDDL Header Notice in each file
 * and include the License file at http://www.netbeans.org/cddl.txt.
 * If applicable, add the following below the CDDL Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyrighted [year] [name of copyright owner]"
 * 
 * The Original Software is NetBeans. The Initial Developer of the Original
 * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
 * Microsystems, Inc. All Rights Reserved.
 */

package org.netbeans.modules.autoupdate;

import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.*;
import org.netbeans.updater.UpdateTracking;
import org.openide.ErrorManager;

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

import org.openide.xml.XMLUtil;
import org.openide.modules.SpecificationVersion;

/** This class represents one module update available on the web
 *
 * @author  akemr
 * @version 
 */
class PreparedModules extends Object {    

    private static final String ELEMENT_MODULES = "module_updates"; // NOI18N
    private static final String ELEMENT_MODULE = "module"; // NOI18N
    private static final String ATTR_CODENAMEBASE = "codenamebase"; // NOI18N
    private static final String ATTR_NAME = "name"; // NOI18N
    private static final String ATTR_SPEC_VERSION = "specification_version"; // NOI18N
    private static final String ATTR_SIZE = "size"; // NOI18N
    private static final String ATTR_NBM_NAME = "nbm_name"; // NOI18N
    
    /** Holds value of property modules. 
     * Each element of this List is instance of Module class.
     */
    private List modules = new ArrayList();

    private boolean pError = false;
    
    private static PreparedModules preparedModules;
    
    static PreparedModules getPrepared() {
        if ( preparedModules == null ) {
            readPrepared();            
        }
        return preparedModules;
    }
    
    static void readPrepared() {
        preparedModules = new PreparedModules();
        preparedModules.read();
    }
    
    /** Getter for property modules.
     * @return Value of property modules.
     */
    List getModules() {
        return modules;
    }
    
    void addModule(ModuleUpdate mu) {
        Module mod = new Module();
        mod.setCodenamebase( mu.getCodeNameBase() );
        mod.setName( mu.getName() );
        mod.setSize( mu.getDownloadSize() );
        SpecificationVersion sv = mu.getRemoteModule().getSpecificationVersion();
        mod.setSpec_version( sv == null ? null : sv.toString() );
        mod.setNbm_name( mu.getDistributionFilename() );
        
        // install to install or to user idr?
        if (mu.isToInstallDir ()) {
            // install dir
            mod.setCluster (mu.findInstallDirectory ());
        } else {
            // user dir
            mod.setCluster (new File (System.getProperty ("netbeans.user"))); // NOI18N
        }
        
        modules.add( mod );
    }
    
    void removeModule(String codenamebase) {
        for (int i=0; i< modules.size(); i++) {
            PreparedModules.Module mod = (PreparedModules.Module)modules.get(i);
            if (mod.getCodenamebase().equals(codenamebase)) {
                modules.remove(i);
                return;
            }
        }
    }
    
    String getPreparedVersion(String codenamebase) {
        for (int i=0; i< modules.size(); i++) {
            PreparedModules.Module mod = (PreparedModules.Module)modules.get(i);
            if (mod.getCodenamebase().equals(codenamebase)) {
                return mod.getSpec_version();
            }
        }
        return null;
    }
    
    boolean hasNBM(String nbm_name) {
        for (int i=0; i< modules.size(); i++) {
            PreparedModules.Module mod = (PreparedModules.Module)modules.get(i);
            if (mod.getNbm_name().equals(nbm_name)) {
                return true;
            }
        }
        return false;
    }
    
    void delete() {
        List/*<File>*/ clusters = UpdateTracking.clusters (true);
        assert clusters != null;
        Iterator it =  clusters.iterator ();
        while (it.hasNext ()) {
            getInstall_Later ((File)it.next ()).delete ();
        }
    }
    
    private File getInstall_Later (File rootOfCluster) {
        return Autoupdater.Support.getInstall_Later (rootOfCluster);
    }
    
    void write () {
        // loop for all clusters and write if needed
        List/*<File>*/ clusters = UpdateTracking.clusters (true);
        assert clusters != null : "Clusters cannot be empty."; // NOI18N
        Iterator iter =  clusters.iterator ();
        while (iter.hasNext ()) {
            writeToCluster ((File)iter.next ());
        }
}
    
    private void writeToCluster (File cluster) {
        Document document = XMLUtil.createDocument(ELEMENT_MODULES, null, null, null);                
        
        Element root = document.getDocumentElement();
        Element module = null;
        Iterator it = modules.iterator();
        boolean empty = true;
        while ( it.hasNext() ) {
            Module mod = (Module)it.next();
            
            // pass this module to given cluster ?
            if (cluster.equals (mod.getCluster ())) {
                module = document.createElement(ELEMENT_MODULE);
                module.setAttribute(ATTR_CODENAMEBASE, mod.getCodenamebase());
                module.setAttribute(ATTR_NAME, mod.getName());
                module.setAttribute(ATTR_SPEC_VERSION, mod.getSpec_version());
                module.setAttribute(ATTR_SIZE, Long.toString(mod.getSize()));
                module.setAttribute(ATTR_NBM_NAME, mod.getNbm_name());

                root.appendChild( module );
                empty = false;
            }
        }
        
        if ( empty )
            return;

        document.getDocumentElement().normalize();
                
        File installLaterFile = Autoupdater.Support.getInstall_Later (cluster);
        installLaterFile.getParentFile ().mkdirs ();
        
        OutputStream os = null;
        try {
            os = new FileOutputStream (installLaterFile);
            XMLUtil.write(document, os, "UTF-8"); // NOI18N
        } catch (java.io.FileNotFoundException fnfe) {
            ErrorManager.getDefault ().notify (fnfe);
        } catch (java.io.IOException ioe) {
            ErrorManager.getDefault ().notify (ioe);
        } finally {
            if (os != null) {
                try {
                    os.close ();
                } catch (Exception x) {
                    ErrorManager.getDefault ().notify (x);
                }
            }
        }

    }
    
    /** Scan through org.w3c.dom.Document document. */
    private void read() {
        modules.clear();
        /** org.w3c.dom.Document document */
        org.w3c.dom.Document document = null;

        InputStream is;
        try {

            List/*<File>*/ clusters = UpdateTracking.clusters (true);
            assert clusters != null : "Clusters cannot be empty."; // NOI18N
            Iterator iter =  clusters.iterator ();
            while (iter.hasNext ()) {
                File cluster = (File)iter.next ();
                File installLaterFile = Autoupdater.Support.getInstall_Later (cluster);
                if (installLaterFile.exists ()) {
                    is = new FileInputStream( installLaterFile );

                    InputSource xmlInputSource = new InputSource( is );
                    document = XMLUtil.parse( xmlInputSource, false, false, new ErrorCatcher(), null );
                    if (is != null)
                        is.close();

                    assert document != null;

                    org.w3c.dom.Element element = document.getDocumentElement();
                    if ((element != null) && element.getTagName().equals(ELEMENT_MODULES)) {
                        scanElement_module_updates (element, cluster);
                    }            
                }
            }
        }
        catch ( org.xml.sax.SAXException e ) {
            System.out.println("Bad install_later" ); // NOI18N
            e.printStackTrace ();
            return;
        }
        catch ( java.io.IOException e ) {
            System.out.println("Missing install_later" ); // NOI18N
            e.printStackTrace ();
            return;
        }
    }

    /** Scan through org.w3c.dom.Element named module_updates. */
    private void scanElement_module_updates (org.w3c.dom.Element element, File cluster) { // <module_updates>
        org.w3c.dom.NodeList nodes = element.getChildNodes();
        for (int i = 0; i < nodes.getLength(); i++) {
            org.w3c.dom.Node node = nodes.item(i);
            if (node.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE) {
                org.w3c.dom.Element nodeElement = (org.w3c.dom.Element)node;
                if (nodeElement.getTagName().equals(ELEMENT_MODULE)) {
                    scanElement_module (nodeElement, cluster);
                }
            }
        }
    }

    /** Scan through org.w3c.dom.Element named module. */
    private void scanElement_module (org.w3c.dom.Element element, File cluster) { // <module>
        Module module = new Module();
        org.w3c.dom.NamedNodeMap attrs = element.getAttributes();
        for (int i = 0; i < attrs.getLength(); i++) {
            org.w3c.dom.Attr attr = (org.w3c.dom.Attr)attrs.item(i);
            if (attr.getName().equals(ATTR_CODENAMEBASE)) { // <module codenamebase="???">
                module.setCodenamebase( attr.getValue() );
            }
            if (attr.getName().equals(ATTR_SPEC_VERSION)) { // <module specification_version="???">
                module.setSpec_version( attr.getValue() );
            }
            if (attr.getName().equals(ATTR_NAME)) { // <module name="???">
                module.setName( attr.getValue() );
            }
            if (attr.getName().equals(ATTR_SIZE)) { // <module size="???">
                try {                        
                    module.setSize( Long.parseLong( attr.getValue()));
                } catch (NumberFormatException nfe) {
                    module.setSize( 0 );
                }
            }
            if (attr.getName().equals(ATTR_NBM_NAME)) { // <module nbm_name="???">
                module.setNbm_name( attr.getValue() );
            }            
            module.setCluster (cluster);
        }
        modules.add( module );
    }
    
    class Module extends Object {
        /** Holds value of property name. */
        private String name;

        /** Holds value of property codenamebase. */
        private String codenamebase;

        /** Holds value of property spec_version. */
        private String spec_version;

        /** Holds value of property size. */
        private long size;

        /** Holds value of property nbm_name. */
        private String nbm_name;
        
        private File cluster;

        /** Getter for property name.
         * @return Value of property name.
         */
        public String getName() {
            return name;
        }

        /** Setter for property name.
         * @param name New value of property name.
         */
        public void setName(String name) {
            this.name = name;
        }

        /** Getter for property codenamebase.
         * @return Value of property codenamebase.
         */
        public String getCodenamebase() {
            return codenamebase;
        }

        /** Setter for property codenamebase.
         * @param codenamebase New value of property codenamebase.
         */
        public void setCodenamebase(String codenamebase) {
            this.codenamebase = codenamebase;
        }

        /** Getter for property spec_version.
         * @return Value of property spec_version.
         */
        public String getSpec_version() {
            return spec_version;
        }

        /** Setter for property spec_version.
         * @param spec_version New value of property spec_version.
         */
        public void setSpec_version(String spec_version) {
            this.spec_version = spec_version;
        }

        /** Getter for property size.
         * @return Value of property size.
         */
        public long getSize() {
            return size;
        }

        /** Setter for property size.
         * @param size New value of property size.
         */
        public void setSize(long size) {
            this.size = size;
        }

        /** Getter for property nbm_name.
         * @return Value of property nbm_name.
         */
        public String getNbm_name() {
            return nbm_name;
        }

        /** Setter for property nbm_name.
         * @param nbm_name New value of property nbm_name.
         */
        public void setNbm_name(String nbm_name) {
            this.nbm_name = nbm_name;
        }        
        
        public void setCluster (File cluster) {
            this.cluster = cluster;
        }
        
        public File getCluster () {
            return cluster;
        }
        
    }
    
    class ErrorCatcher implements org.xml.sax.ErrorHandler {
        private void message (String level, org.xml.sax.SAXParseException e) {
            pError = true;
        }

        public void error (org.xml.sax.SAXParseException e) {
            // normally a validity error
            pError = true;
        }

        public void warning (org.xml.sax.SAXParseException e) {
            //parseFailed = true;
        }

        public void fatalError (org.xml.sax.SAXParseException e) {
            pError = true;
        }
    }
}
