/*
 * 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.net.URL;
import java.net.URLEncoder;
import java.util.StringTokenizer;
import java.io.IOException;
import java.util.Collections;
import java.util.MissingResourceException;
import java.util.ResourceBundle;

import org.openide.ErrorManager;
import org.openide.util.NbBundle;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileStateInvalidException;
import org.openide.filesystems.FileSystem;
import org.openide.util.HelpCtx;

/** Options autoupdate module
*
* @author Ales Kemr
*/
public class XMLAutoupdateType extends AutoupdateType {

    final static String PROP_URL = "URL";  // NOI18N
    
    /** Holds value of property URL. */
    private URL url;
    
    /** Holds value of property defaultURL. */
    private  String defaultURL;
    
    /** System property holding autoupdate version */
    private static final String UPDATE_VERSION_PROP = "netbeans.autoupdate.version"; // NOI18N
    
    /** System property holding ide identity */
    private static final String IDE_HASH_CODE = "netbeans.hash.code"; // NOI18N
    
    /** Current internal version of autoupdate */
    public static final String UPDATE_VERSION = "1.21"; // NOI18N

    /** Important system properies */
    private static final String SYSPROP_COUNTRY = "netbeans.autoupdate.country"; // NOI18N
    private static final String SYSPROP_LANGUAGE = "netbeans.autoupdate.language"; // NOI18N
    private static final String SYSPROP_VARIANT = "netbeans.autoupdate.variant"; // NOI18N

    /** serialVersionUID */
    static final long serialVersionUID = 362844553432169452L;
    
    /** Holds value of property displayName */
    private String displayName = null;
    
    private FileObject typeFileObject = null;
    
    /** Holds value of property url_key. */
    private String url_key;
    
    /** Holds value of property localizing bundle in update center declaration. */
    private String localizingBundleName;

    private boolean valid = true;
    
    /** Creates new XMLAutoupdateType */
    public XMLAutoupdateType() {
        try {
            url = new URL( getDefaultURL() );
        }
        catch (java.net.MalformedURLException e) {
            url = null;
        }
    }
    
    public XMLAutoupdateType(URL url) {
        this.url = url;
    }
    
    public XMLAutoupdateType(URL url, String displayName, String url_key, Boolean enabled) {
        this( url, displayName, null, url_key, enabled );
    }
    
    public XMLAutoupdateType(URL url, String displayName, FileObject fo, String url_key, Boolean enabled) {
        this (url, displayName, fo, url_key, enabled, null);
    }
    
    public XMLAutoupdateType(URL url, String displayName, FileObject fo, String url_key, Boolean enabled, String localizingBundleName) {
        this.typeFileObject = fo;
        this.url = url;
        this.displayName = displayName;
        this.url_key = url_key;
        this.localizingBundleName = localizingBundleName;

        if ( enabled != null )
            setEnabled( enabled.booleanValue() );
    }
    
    public static XMLAutoupdateType createXMLAutoupdateType(FileObject fo) throws IOException {
        URL url;
        String sKey = (String)fo.getAttribute("url_key"); // NOI18N
        String remoteBundleName = (String)fo.getAttribute ("SystemFileSystem.localizingBundle"); // NOI18N
        ResourceBundle bundle = getBundleFromName (remoteBundleName);
        if (sKey != null) {
            String localizedValue;
            try {
                localizedValue = bundle.getString (sKey);
            } catch (MissingResourceException mre) {
                localizedValue = "http://"; // XXX What set if the key is not in any bundle
            }
            //System.out.println (remoteBundleName + " for <" + sKey + "> returns " + localizedValue);
            url = new URL (localizedValue);
        } else {
            Object o = fo.getAttribute("url"); // NOI18N
            if (o instanceof String) {
                url = new URL((String)o);
            } else {
                url = (URL)o;
            }
        }
        
        Boolean en = (Boolean)fo.getAttribute("enabled");
        
        return new XMLAutoupdateType( url, null, fo, sKey, en, remoteBundleName );
    }
    
    /** @return human presentable name */
    public String displayName() {
        if (displayName == null) {
            if (typeFileObject != null) {
                try {
                    FileSystem fs = typeFileObject.getFileSystem();
                    FileSystem.Status s = fs.getStatus();
                    String x = s.annotateName("", Collections.singleton(typeFileObject)); // NOI18N
                    if (!x.equals("")) { // NOI18N
                        displayName = x;
                    }
                } catch (FileStateInvalidException e) {
                    // OK, never mind.
                }
            }
            if (displayName == null) {
                displayName = NbBundle.getBundle( Settings.class).getString("CTL_XMLAutoupdateType_Name");
            }
        }
        return displayName;
    }
    
    /** Getter for property URL.
     * @return Value of property URL.
    */
    public URL getURL() {
        return url;
    }
    
    public HelpCtx getHelpCtx () {
        return new HelpCtx( org.netbeans.modules.autoupdate.XMLAutoupdateType.class );
    }
    
    /** Setter for property URL.
     * @param URL New value of property URL.
    */
    public void setURL(URL url) {
        URL old = this.url;
        this.url = url;
        firePropertyChange( PROP_URL, old, url );
    }
    
    public Updates connectForUpdates() {
        return new XMLUpdates( modifyURL( url ) );         
    }
    
    /** Gets the update URL */
    protected URL modifyURL (URL original) {
        
        URL updateURL = null;
        
        if ( System.getProperty( UPDATE_VERSION_PROP ) == null ) {
            System.setProperty( UPDATE_VERSION_PROP, UPDATE_VERSION );
        }
        
        if (System.getProperty (IDE_HASH_CODE) == null) {
            String id = ""; // NOI18N
            try {
                id = Settings.getShared ().getIdeIdentity ();
            } catch (NullPointerException npe) {
                // can ignore it, property used only for logging purposes
                ErrorManager.getDefault ().log (ErrorManager.WARNING, "Warning: Property PROP_IDE_IDENTITY hasn't been initialized yet."); // NOI18N
            }
            String prefix = NbBundle.getBundle (XMLAutoupdateType.class).getString ("URL_Prefix_Hash_Code"); // NOI18N
            System.setProperty (IDE_HASH_CODE, "".equals (id) ? prefix + "0" : prefix + id); // NOI18N
        }
        
        try {
            updateURL = new URL( encode( replace( original.toString() ) ) );
        }
        catch (java.net.MalformedURLException e) {
            ErrorManager.getDefault ().notify (e);
        };

        return updateURL;
        
    }
    
    /** Utility method for replacing {$xxx} with value of system property xxx,
     * or with another special value - see getReplacement
    */

    protected String replace( String string ) {

        // First of all set our system properties
        setSystemProperties();

        if ( string == null )
            return null;

        StringBuffer sb = new StringBuffer();

        int index, prevIndex;
        index = prevIndex = 0;
        while( ( index = string.indexOf( "{", index )) != -1 && index < string.length() - 1) { // NOI18N

            if ( string.charAt( index + 1 ) == '{' || string.charAt( index + 1 ) != '$'  ) {
                ++index;
                continue;
            }

            sb.append( string.substring( prevIndex, index ) );
            int endBracketIndex = string.indexOf( "}", index ); // NOI18N
            if ( endBracketIndex != -1 ) {
                String whatToReplace = string.substring( index + 2, endBracketIndex );
                sb.append( getReplacement(whatToReplace) );
            }
            prevIndex = endBracketIndex == -1 ? index + 2 : endBracketIndex + 1;
            ++index;
        }

        if ( prevIndex < string.length() - 1 )
            sb.append( string.substring( prevIndex ) );

        return sb.toString();
    }

    boolean isValid () {
        return valid;
    }

    /** What to replace by what in URL string         
    */
    protected String getReplacement(String whatToReplace) {        
        return System.getProperty( whatToReplace, ""  );
    }

    protected String encode(String stringURL) {
	String rval = stringURL;
            int q = stringURL.indexOf('?');
            if(q > 0) {
		StringBuffer buf = new StringBuffer(stringURL.substring(0, q+1));
		StringTokenizer st = new StringTokenizer(stringURL.substring(q + 1), "&");
		while(st.hasMoreTokens()) {
                    String a = st.nextToken();
                    int ei = a.indexOf("=");
                    if(ei < 0) {
                            buf.append(URLEncoder.encode(a));
                    } else {
                            buf.append(URLEncoder.encode(a.substring(0, ei)));
                            buf.append("=");
                            String tna = a.substring(ei+1);
                            int tni = tna.indexOf("%");
                            if( tni < 0) {
                                buf.append(URLEncoder.encode(tna));
                            } else {
                                buf.append(URLEncoder.encode(tna.substring(0, tni)));
                                buf.append("%");
                                buf.append(URLEncoder.encode(tna.substring(tni+1)));
                            }
                    }
                    if (st.hasMoreTokens())
                        buf.append("&");
		}
                rval = buf.toString();
            }

	return rval;
    }
    
    private static ResourceBundle getOldLocalizingBundle () {
        return NbBundle.getBundle (XMLAutoupdateType.class);
    }
    
    private static ResourceBundle getBundleFromName (String name) throws MissingResourceException {
        ResourceBundle bundle = null;
        if (name == null) {
            bundle = getOldLocalizingBundle ();
        } else {
            bundle = NbBundle.getBundle (name);
        }
        return bundle;
    }
    
    private static void setSystemProperties() {
            
        if ( System.getProperty( SYSPROP_COUNTRY, null ) == null ) {
            System.setProperty( SYSPROP_COUNTRY, java.util.Locale.getDefault().getCountry() );
        }
        if ( System.getProperty( SYSPROP_LANGUAGE, null ) == null ) {
            System.setProperty( SYSPROP_LANGUAGE, java.util.Locale.getDefault().getLanguage() );
        }
        if ( System.getProperty( SYSPROP_VARIANT, null ) == null ) {
            System.setProperty( SYSPROP_VARIANT, java.util.Locale.getDefault().getVariant() );
        }
    }
    
    protected String getDefaultURL() {
        if ( defaultURL == null ) {
            ResourceBundle remoteBundle = null;
            try {
                remoteBundle = getBundleFromName (localizingBundleName);
                defaultURL = url_key != null ? remoteBundle.getString( url_key ) : remoteBundle.getString( "URL_Default_N" );
            } catch (MissingResourceException mre) {
                // non-existent localizingBundleName
                // the module which did this setting declaration, was removed
                // return null and ignore this setting
                this.valid = false;
                return null;
            }
            //System.out.println(localizingBundleName + " for <" + url_key + "> returns " + defaultURL);
        }
        return defaultURL;        
    }
        
    private void writeObject(java.io.ObjectOutputStream out) throws java.io.IOException {
        if ( url.toString().equals( getDefaultURL()))
            out.writeObject( null );        
        else
            out.writeObject( url );
        out.writeObject( displayName );
        out.writeObject( url_key );
        out.writeObject( localizingBundleName );
    }

    private void readObject(java.io.ObjectInputStream in) throws java.io.IOException,
            ClassNotFoundException {
        URL u = null;
        try {
            // initialy is valid
            valid = true;

            u = (URL)in.readObject();
            String display = (String)in.readObject();
            if (display != null)
                displayName = display;        

            String key = (String)in.readObject();
            if (key != null)
                url_key = key;
            
            String remoteBundleName = (String)in.readObject ();
            if (remoteBundleName != null) {
                localizingBundleName = remoteBundleName;
            }

        } catch (java.io.OptionalDataException ode) {
            if ( ode.eof ) {
                // new version
                u = null;
                setEnabled( true );
            }
            else
                throw ode;
        }
        
        if ( u == null || u.toString().startsWith("http://www.netbeans.org/updates/31_" ))  { // NOI18N
            String urlSpec = getDefaultURL ();
            if (urlSpec != null) {
                setURL(new URL( urlSpec ));
            }
        } else {
            setURL(u);
        }

        if (! isValid ()) {
            setEnabled (false);
            displayName = NbBundle.getMessage (XMLAutoupdateType.class, "XMLAutoupdateType_InvalidSetting", displayName); // NOI18N
        }

    }
    
}
