/*
 * ModeShape (http://www.modeshape.org)
 * See the COPYRIGHT.txt file distributed with this work for information
 * regarding copyright ownership.  Some portions may be licensed
 * to Red Hat, Inc. under one or more contributor license agreements.
 * See the AUTHORS.txt file in the distribution for a full listing of 
 * individual contributors.
 *
 * ModeShape is free software. Unless otherwise indicated, all code in ModeShape
 * is licensed to you under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 * 
 * ModeShape 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
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.modeshape.jdbc.delegate;

import javax.jcr.Credentials;
import javax.jcr.SimpleCredentials;
import org.modeshape.common.text.TextDecoder;
import org.modeshape.common.text.UrlEncoder;
import org.modeshape.common.util.StringUtil;
import org.modeshape.jcr.api.Repositories;
import org.modeshape.jdbc.JdbcLocalI18n;
import org.modeshape.jdbc.LocalJcrDriver;
import java.sql.DriverPropertyInfo;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;

/**
 * The ConnectionInfo contains the information used to connect to the Jcr Repository.
 */
public abstract class ConnectionInfo {
    public static final TextDecoder URL_DECODER = new UrlEncoder();

    protected String url;
    protected String repositoryPath;
    protected Properties properties;
    private char propertyDelimiter = '?';

    protected ConnectionInfo( String url,
                              Properties properties ) {
        this.url = url;
        this.properties = properties;
    }


    protected void init() {
        Properties props = getProperties() != null ? (Properties)getProperties().clone() : new Properties();
        repositoryPath = getUrl().substring(this.getUrlPrefix().length());

        // Find any URL parameters ...
        int questionMarkIndex = repositoryPath.indexOf('?');
        if (questionMarkIndex != -1) {
            if (repositoryPath.length() > questionMarkIndex + 1) {
                String paramStr = repositoryPath.substring(questionMarkIndex + 1);
                for (String param : paramStr.split("&")) {
                    String[] pair = param.split("=");
                    if (pair.length > 1) {
                        String key = URL_DECODER.decode(pair[0] != null ? pair[0].trim() : null);
                        String value = URL_DECODER.decode(pair[1] != null ? pair[1].trim() : null);
                        if (!props.containsKey(key)) {
                            props.put(key, value);
                        }
                    }
                }
            }
            repositoryPath = repositoryPath.substring(0, questionMarkIndex).trim();
        }

        Properties newprops = new Properties();
        newprops.putAll(props);
        this.setProperties(newprops);
        String url = getUrl();
        this.setUrl(url != null ? url.trim() : null);
    }

    /**
     * Get the original URL of the connection.
     *
     * @return the URL; never null
     */
    public String getUrl() {
        return url;
    }

    /**
     * Get the part of the {@link #getUrl()} that indicates the path to connect to the Repository.
     * This value should be prefixed by the {@link #getUrlPrefix()} in the {@link #getUrl()}.
     *
     * @return String
     */
    public String getRepositoryPath() {
        return this.repositoryPath;
    }

    /**
     * Get the immutable properties for the connection.
     *
     * @return the properties; never null
     */
    public Properties getProperties() {
        return properties;
    }

    /**
     * Get the name of the repository. This is required only if the
     * {@link Repositories} instance is being used to obtain the Repository.
     *
     * @return the name of the repository, or null if no repository name was
     *         specified
     */
    public String getRepositoryName() {
        return properties.getProperty(LocalJcrDriver.REPOSITORY_PROPERTY_NAME);
    }

    /**
     * Call to set the repository name.  This is called when no repository name is set
     * on the URL, but there is only one repository in the list.
     *
     * @param repositoryName
     */
    void setRepositoryName( String repositoryName ) {
        this.properties.setProperty(LocalJcrDriver.REPOSITORY_PROPERTY_NAME, repositoryName);
    }

    /**
     * Get the name of the workspace. This is not required, and if abscent
     * implies obtaining the JCR Repository's default workspace.
     *
     * @return the name of the workspace, or null if no workspace name was
     *         specified
     */
    public String getWorkspaceName() {
        return properties.getProperty(LocalJcrDriver.WORKSPACE_PROPERTY_NAME);
    }

    /**
     * Call to set the workspace name. This is not required, and if abscent
     * implies obtaining the JCR Repository's default workspace.
     *
     * @param workSpaceName
     */
    public void setWorkspaceName( String workSpaceName ) {
        properties.setProperty(LocalJcrDriver.WORKSPACE_PROPERTY_NAME, workSpaceName);
    }

    /**
     * Get the JCR user name. This is not required, and if abscent implies that
     * no credentials should be used when obtaining a JCR Session.
     *
     * @return the JCR user name, or null if no user name was specified
     */
    public String getUsername() {
        return properties.getProperty(LocalJcrDriver.USERNAME_PROPERTY_NAME);
    }

    /**
     * Get the JCR password. This is not required.
     *
     * @return the JCR password, or null if no password was specified
     */
    public char[] getPassword() {
        String result = properties.getProperty(LocalJcrDriver.PASSWORD_PROPERTY_NAME);
        return result != null ? result.toCharArray() : null;
    }

    /**
     * Return true of Teiid support is required for this connection.
     *
     * @return true if Teiid support is required.
     */
    public boolean isTeiidSupport() {
        String result = properties.getProperty(LocalJcrDriver.TEIID_SUPPORT_PROPERTY_NAME);
        if (result == null) {
            return false;
        }
        return result.equalsIgnoreCase(Boolean.TRUE.toString());
    }

    void setUrl( String url ) {
        this.url = url;
    }

    void setProperties( Properties properties ) {
        this.properties = properties;
    }

    /**
     * Get the effective URL of this connection, which places all properties
     * on the URL (with a '*' for each character in the password property)
     *
     * @return the effective URL; never null
     */
    public String getEffectiveUrl() {
        StringBuilder url = new StringBuilder(this.getUrlPrefix());
        url.append(this.getRepositoryPath());
        char propertyDelim = getPropertyDelimiter();
        for (String propertyName : getProperties().stringPropertyNames()) {
            String value = getProperties().getProperty(propertyName);
            if (value == null) {
                continue;
            }
            if (LocalJcrDriver.PASSWORD_PROPERTY_NAME.equals(propertyName)) {
                value = StringUtil.createString('*', value.length());
            }
            url.append(propertyDelim).append(propertyName).append('=')
               .append(value);
            propertyDelim = '&';
        }
        return url.toString();
    }

    /**
     * Return the starting property delimiter
     *
     * @return char property delimiter
     */
    protected char getPropertyDelimiter() {
        return propertyDelimiter;
    }

    protected void setPropertyDelimiter( char delimiter ) {
        this.propertyDelimiter = delimiter;
    }

    /**
     * Obtain the array of {@link DriverPropertyInfo} objects that describe the missing properties.
     *
     * @return DriverPropertyInfo the property infos; never null but possibly empty
     */
    public DriverPropertyInfo[] getPropertyInfos() {
        List<DriverPropertyInfo> results = new ArrayList<DriverPropertyInfo>();

        addUrlPropertyInfo(results);
        addUserNamePropertyInfo(results);
        addPasswordPropertyInfo(results);
        addWorkspacePropertyInfo(results);
        addRepositoryNamePropertyInfo(results);

        return results.toArray(new DriverPropertyInfo[results.size()]);
    }

    protected void addUrlPropertyInfo( List<DriverPropertyInfo> results ) {
        if (getUrl() == null) {
            DriverPropertyInfo info = new DriverPropertyInfo(
                    JdbcLocalI18n.urlPropertyName.text(), null);
            info.description = JdbcLocalI18n.urlPropertyDescription.text(this.getEffectiveUrl(), getUrlExample());
            info.required = true;
            info.choices = new String[] { getUrlExample() };
            results.add(info);
        }
    }

    protected void addUserNamePropertyInfo( List<DriverPropertyInfo> results ) {
        if (getUsername() == null) {
            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.usernamePropertyName.text(), null);
            info.description = JdbcLocalI18n.usernamePropertyDescription.text();
            info.required = false;
            info.choices = null;
            results.add(info);
        }
    }

    protected void addPasswordPropertyInfo( List<DriverPropertyInfo> results ) {
        if (getPassword() == null) {
            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.passwordPropertyName.text(), null);
            info.description = JdbcLocalI18n.passwordPropertyDescription.text();
            info.required = false;
            info.choices = null;
            results.add(info);
        }
    }

    protected void addWorkspacePropertyInfo( List<DriverPropertyInfo> results ) {
        if (getWorkspaceName() == null) {
            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.workspaceNamePropertyName.text(), null);
            info.description = JdbcLocalI18n.workspaceNamePropertyDescription.text();
            info.required = false;
            info.choices = null;
            results.add(info);
        }
    }

    protected void addRepositoryNamePropertyInfo( List<DriverPropertyInfo> results ) {
        if (getRepositoryName() == null) {
            DriverPropertyInfo info = new DriverPropertyInfo(JdbcLocalI18n.repositoryNamePropertyName.text(), null);
            info.description = JdbcLocalI18n.repositoryNamePropertyDescription.text();
            info.required = true;
            info.choices = null;
            results.add(info);
        }
    }

    /**
     * The delegate should provide an example of the URL to be used
     *
     * @return String url example
     */
    public abstract String getUrlExample();

    /**
     * The delegate should provide the prefix defined by the {@link LocalJcrDriver}
     *
     * @return String url prefix
     */
    public abstract String getUrlPrefix();

    /**
     * Return the credentials based on the user name and password.
     *
     * @return Credentials
     */
    public Credentials getCredentials() {
        String username = getUsername();
        char[] password = getPassword();
        if (username != null) {
            return new SimpleCredentials(username, password);
        }
        return null;
    }

}