/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 */
package org.apache.ivy.ant;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.text.ParseException;
import java.util.Properties;

import org.apache.ivy.Ivy;
import org.apache.ivy.core.settings.IvySettings;
import org.apache.ivy.core.settings.IvyVariableContainer;
import org.apache.ivy.util.Message;
import org.apache.ivy.util.url.CredentialsStore;
import org.apache.ivy.util.url.URLHandler;
import org.apache.ivy.util.url.URLHandlerDispatcher;
import org.apache.ivy.util.url.URLHandlerRegistry;
import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Project;
import org.apache.tools.ant.ProjectComponent;
import org.apache.tools.ant.taskdefs.Property;
import org.apache.tools.ant.types.DataType;

public class IvyAntSettings extends DataType {

    public static class Credentials {
        private String realm;

        private String host;

        private String username;

        private String passwd;

        public String getPasswd() {
            return this.passwd;
        }

        public void setPasswd(String passwd) {
            this.passwd = passwd;
        }

        public String getRealm() {
            return this.realm;
        }

        public void setRealm(String realm) {
            this.realm = format(realm);
        }

        public String getHost() {
            return this.host;
        }

        public void setHost(String host) {
            this.host = format(host);
        }

        public String getUsername() {
            return this.username;
        }

        public void setUsername(String userName) {
            this.username = format(userName);
        }
    }

    private Ivy ivyEngine = null;

    private File file = null;

    private URL url = null;

    private String realm = null;

    private String host = null;

    private String userName = null;

    private String passwd = null;
    
    private String id = "ivy.instance";
    
    private boolean autoRegistered = false;

    /**
     * Returns the default ivy settings of this classloader. If it doesn't exist yet, a new one is
     * created using the given project to back the VariableContainer.
     * 
     * @param  project  TODO add text.
     * @return  An IvySetting instance.
     */
    public static IvyAntSettings getDefaultInstance(ProjectComponent task) {
        Project project = task.getProject();
        Object defaultInstanceObj = project.getReference("ivy.instance");
        if (defaultInstanceObj != null
                && defaultInstanceObj.getClass().getClassLoader() != IvyAntSettings.class
                        .getClassLoader()) {
            task.log("ivy.instance reference an ivy:settings defined in an other classloader.  "
                     + "An new default one will be used in this project.", Project.MSG_WARN);
            defaultInstanceObj = null;
        }
        if (defaultInstanceObj != null && !(defaultInstanceObj instanceof IvyAntSettings)) {
            throw new BuildException("ivy.instance reference a "
                    + defaultInstanceObj.getClass().getName()
                    + " an not an IvyAntSettings.  Please don't use this reference id ()");
        }
        if (defaultInstanceObj == null) {
            task.log("No ivy:settings found for the default reference 'ivy.instance'.  " 
                    + "A default instance will be used", Project.MSG_VERBOSE);
            
            IvyAntSettings settings = new IvyAntSettings();
            settings.setProject(project);
            project.addReference("ivy.instance", settings);        
            settings.createIvyEngine(task);
            return settings;
        } else {
            return (IvyAntSettings) defaultInstanceObj;
        }
    }
    
    public File getFile() {
        return file;
    }

    public URL getUrl() {
        return url;
    }

    public String getPasswd() {
        return passwd;
    }

    public void setPasswd(String aPasswd) {
        passwd = aPasswd;
    }

    public String getRealm() {
        return realm;
    }

    public void setRealm(String aRealm) {
        realm = format(aRealm);
    }

    public String getHost() {
        return host;
    }

    public void setHost(String aHost) {
        host = format(aHost);
    }

    public String getUsername() {
        return userName;
    }

    public void setUsername(String aUserName) {
        userName = format(aUserName);
    }

    public void setProject(Project p) {
        super.setProject(p);
        
        if ("ivy.instance".equals(id) && !getProject().getReferences().containsKey(id)) {
            // register ourselfs as default settings, just in case the id attribute is not set
            getProject().addReference("ivy.instance", this);
            autoRegistered = true;
        }
    }
    
    private static String format(String str) {
        return str == null ? str : (str.trim().length() == 0 ? null : str.trim());
    }

    public void addConfiguredCredentials(Credentials c) {
        CredentialsStore.INSTANCE.addCredentials(c.getRealm(), c.getHost(), c.getUsername(), c
                .getPasswd());
    }

    public void setFile(File file) {
        this.file = file;
    }

    public void setUrl(String confUrl) throws MalformedURLException {
        this.url = new URL(confUrl);
    }
    
    public void setUrl(URL url) {
        this.url = url;
    }

    /*
     * This is usually not necessary to define a reference in Ant, but it's the only
     * way to know the id of the settings, which we use to set ant properties.
     */
    public void setId(String id) {
        if (autoRegistered && getProject().getReference(this.id) == this) {
            getProject().getReferences().remove(this.id);
            autoRegistered = false;
        }
        this.id = id;
        
        if (getProject() != null) {
            getProject().addReference(this.id, this);
        }
    }
    
    public String getId() {
        return id;
    }

    /**
     * Return the configured Ivy instance.
     * @return Returns the configured Ivy instance.
     */
    public Ivy getConfiguredIvyInstance(ProjectComponent task) {
        if (ivyEngine == null) {
            createIvyEngine(task);
        }
        return ivyEngine;
    }

    void createIvyEngine(final ProjectComponent task) {
        Project project = task.getProject();
        Property prop = new Property() {
            public void execute() throws BuildException {
                addProperties(getDefaultProperties(task));
            }
        };
        prop.setProject(project);
        prop.init();
        prop.execute();

        IvyAntVariableContainer ivyAntVariableContainer = new IvyAntVariableContainer(project);
        IvySettings settings = new IvySettings(ivyAntVariableContainer);
        settings.setBaseDir(project.getBaseDir());
        
        if (file == null && url == null) {
            defineDefaultSettingFile(ivyAntVariableContainer, task);
        }

        Ivy ivy = Ivy.newInstance(settings);
        try {
            ivy.pushContext();
            AntMessageLogger.register(task, ivy);

            Message.showInfo();
            configureURLHandler();
            if (file != null) {
                if (!file.exists()) {
                    throw new BuildException("settings file does not exist: " + file);
                }
                ivy.configure(file);
            } else {
                if (url == null) {
                    throw new AssertionError(
                            "ivy setting should have either a file, either an url,"
                                    + " and if not defineDefaultSettingFile must set it.");
                }
                ivy.configure(url);
            }
            ivyAntVariableContainer.updateProject(id);
            ivyEngine = ivy;
        } catch (ParseException e) {
            throw new BuildException("impossible to configure ivy:settings with given "
                    + (file != null ? "file: " + file : "url: " + url) + " : " + e, e);
        } catch (IOException e) {
            throw new BuildException("impossible to configure ivy:settings with given "
                    + (file != null ? "file: " + file : "url: " + url) + " : " + e, e);
        } finally {
            ivy.popContext();
        }
    }

    protected Properties getDefaultProperties(ProjectComponent task) {
        URL url = IvySettings.getDefaultPropertiesURL();
        // this is copy of loadURL code from ant Property task (not available in 1.5.1)
        Properties props = new Properties();
        task.log("Loading " + url, Project.MSG_VERBOSE);
        try {
            InputStream is = url.openStream();
            try {
                props.load(is);
            } finally {
                if (is != null) {
                    is.close();
                }
            }
        } catch (IOException ex) {
            throw new BuildException(ex);
        }
        return props;
    }

    /**
     * Set file or url to its default value
     * 
     * @param variableContainer
     */
    private void defineDefaultSettingFile(IvyVariableContainer variableContainer, ProjectComponent task) {
        String settingsFileName = variableContainer.getVariable("ivy.conf.file");
        if (settingsFileName != null 
                && !settingsFileName.equals(variableContainer.getVariable("ivy.settings.file"))) {
            task.log("DEPRECATED: 'ivy.conf.file' is deprecated, use 'ivy.settings.file' instead", Project.MSG_INFO);
        } else {
            settingsFileName = variableContainer.getVariable("ivy.settings.file");
        }
        File[] settingsLocations = new File[] {
                new File(getProject().getBaseDir(), settingsFileName),
                new File(getProject().getBaseDir(), "ivyconf.xml"), 
                new File(settingsFileName),
                new File("ivyconf.xml") 
        };
        for (int i = 0; i < settingsLocations.length; i++) {
            file = settingsLocations[i];
            task.log("searching settings file: trying " + file, Project.MSG_VERBOSE);
            if (file.exists()) {
                break;
            }
        }
        if (!file.exists()) {
            file = null;
            if (Boolean.valueOf(getProject().getProperty("ivy.14.compatible")).booleanValue()) {
                task.log("no settings file found, using Ivy 1.4 default...", Project.MSG_VERBOSE);
                url = IvySettings.getDefault14SettingsURL();
            } else {
                String settingsFileUrl = variableContainer.getVariable("ivy.settings.url");
                if (settingsFileUrl != null) {
                    try {
                        url = new URL(settingsFileUrl);
                    } catch (MalformedURLException e) {
                        throw new BuildException("Impossible to configure ivy:settings with given url: " 
                            + settingsFileUrl + ": " + e.getMessage(), e);
                    }
                } else {
                    task.log("no settings file found, using default...", Project.MSG_VERBOSE);
                    url = IvySettings.getDefaultSettingsURL();
                }
            }
        }
    }

    private void configureURLHandler() {
        // TODO : the credentialStore should also be scoped
        CredentialsStore.INSTANCE.addCredentials(getRealm(), getHost(), getUsername(), getPasswd());

        URLHandlerDispatcher dispatcher = new URLHandlerDispatcher();
        URLHandler httpHandler = URLHandlerRegistry.getHttp();
        dispatcher.setDownloader("http", httpHandler);
        dispatcher.setDownloader("https", httpHandler);
        URLHandlerRegistry.setDefault(dispatcher);
    }

}
