/**
 * Copyright © CENRA (2016)
 *
 * remi.clement@espaces-naturels.fr
 * 
 * This software is a multi-platform and portable application based on 
 * the scientifical toolbox to monitor wetlands. With this toolbox it’s 
 * possible to calculate indicators «RhoMéO» to monitor quality, 
 * fonctionalities and urban and agricultural pressures of wetlands. 
 * Indicators are calculated using naturalist data entered and/or imported,
 * and geographic data downloaded from a geographic server.
 * 
 * This software is governed by the CeCILL license under French law and
 * abiding by the rules of distribution of free software.  You can  use, 
 * modify and/ or redistribute the software under the terms of the CeCILL
 * license as circulated by CEA, CNRS and INRIA at the following URL
 * "http://www.cecill.info". 
 * 
 * As a counterpart to the access to the source code and  rights to copy,
 * modify and redistribute granted by the license, users are provided only
 * with a limited warranty  and the software's author,  the holder of the
 * economic rights,  and the successive licensors  have only  limited
 * liability. 
 * 
 * In this respect, the user's attention is drawn to the risks associated
 * with loading,  using,  modifying and/or developing or reproducing the
 * software by the user in light of its specific status of free software,
 * that may mean  that it is complicated to manipulate,  and  that  also
 * therefore means  that it is reserved for developers  and  experienced
 * professionals having in-depth computer knowledge. Users are therefore
 * encouraged to load and test the software's suitability as regards their
 * requirements in conditions enabling the security of their systems and/or 
 * data to be ensured and,  more generally, to use and operate it in the 
 * same conditions as regards security. 
 * 
 * The fact that you are presently reading this means that you have had
 * knowledge of the CeCILL license and that you accept its terms.
*/
package fr.cenra.rhomeo.core.preferences.ftp;

import fr.cenra.rhomeo.core.util.EncryptionResult;
import fr.cenra.rhomeo.core.util.SecretGenerator;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.prefs.Preferences;
import javax.validation.constraints.NotNull;
import org.apache.commons.net.ftp.FTPClient;

/**
 * Holds connection information over a single FTP service. The service is defined
 * by its host name, but also by a login, password and optionally a root directory.
 *
 * NOTE : If, for some reason, security context cannot initialize, passwords become
 * unavailable.
 *
 * @author Alexis Manin (Geomatys)
 */
public class FTPAccess {

    private static enum PROPERTIES {
        ADRESS,
        PORT,
        LOGIN,
        PASSWORD,
        WORKING_DIR
    }

    private final Preferences node;

    /**
     * Encryption manager. Can be null if an error occurred at initialisation. In
     * this case, no password will be read or written.
     */
    private final SecretGenerator cipher;

    protected FTPAccess(@NotNull final Preferences infoContainer) {
        this.node = infoContainer;
        cipher = SecretGenerator.getInstance().orElse(null);
    }

    private String getProperty(final PROPERTIES p) {
        return node.get(p.name(), null);
    }

    private void setProperty(final PROPERTIES p, final String value) {
        if (value == null || value.trim().isEmpty()) {
            node.remove(p.name());
        } else {
            node.put(p.name(), value);
        }
    }

    public String getAdress() {
        return getProperty(PROPERTIES.ADRESS);
    }

    public void setAdress(final String newAdress) {
        setProperty(PROPERTIES.ADRESS, newAdress);
    }

    public int getPort() {
        return node.getInt(PROPERTIES.PORT.name(), 21);
    }

    public void setPort(final int port) {
        node.putInt(PROPERTIES.PORT.name(), port);
    }

    public String getLogin() {
        return getProperty(PROPERTIES.LOGIN);
    }

    public void setLogin(final String newLogin) {
        setProperty(PROPERTIES.LOGIN, newLogin);
    }


    public String getWorkingDir() {
        return getProperty(PROPERTIES.WORKING_DIR);
    }

    public void setWorkingDir(final String newDir) {
        setProperty(PROPERTIES.WORKING_DIR, newDir);
    }

    public synchronized String getPassword() throws GeneralSecurityException {
        final String tmpValue = getProperty(PROPERTIES.PASSWORD);
        if (tmpValue == null || tmpValue.isEmpty()) { // No pw stored.
            return "";
        }

        if (cipher == null) {
            throw new GeneralSecurityException("no security manager available. Passwords cannot be read or written from preferences.");
        }

        byte[] sk = node.getByteArray("ftp_access_skey", null);
        byte[] sr = node.getByteArray("ftp_access_sran", null);
        if (sk == null || sr == null) {
            throw new GeneralSecurityException("no security manager available. Passwords cannot be read or written from preferences.");
        }

        return cipher.decrypt(tmpValue, sk, sr);
    }

    public synchronized void setPassword(final String newPassword) throws GeneralSecurityException {
        if (cipher == null) {
            throw new GeneralSecurityException("no security manager available. Passwords cannot be read or written from preferences.");
        }

        // Empty value, no encryption needed.
        if (newPassword == null || newPassword.isEmpty()) {
            setProperty(PROPERTIES.PASSWORD, "");
            return;
        }

        final EncryptionResult encrypted = cipher.encrypt(newPassword);
        setProperty(PROPERTIES.PASSWORD, encrypted.output);

        node.putByteArray("ftp_access_skey", encrypted.getKey());
        node.putByteArray("ftp_access_sran", encrypted.getSecureRandom());
    }

    /**
     * Create a new connection over the FTP service provided by holded information.
     * The provided {@link FTPClient} must be closed using {@link FTPClient#disconnect() }
     * once you've finished your work.
     *
     * @return A client to exchange data with FTP service which contains
     * reference files.
     *
     * @throws IOException If we cannot connect to distant service.
     * @throws java.security.GeneralSecurityException If an authentication is
     * required, but we cannot access to password.
     */
    public FTPClient createClient() throws IOException, GeneralSecurityException {
        return createClient(getAdress(), getPort(), getWorkingDir(), getLogin(), getPassword());
    }

    public static FTPClient createClient(final String adress, final int port, final String workDir, final String login, final String password) throws IOException, GeneralSecurityException {
        if (adress == null) {
            throw new IllegalStateException("No adress configured for FTP access !");
        }

        final FTPClient client = new FTPClient();
        client.enterLocalActiveMode();
        if (port < 1) {
            client.connect(adress);
        } else {
            client.connect(adress, port);
        }

        if (login != null) {
            if (!client.login(login, password)) {
                throw new GeneralSecurityException("Unable to authenticate with the login "+ login);
            }
        }

        if (workDir != null) {
            if (!client.changeWorkingDirectory(workDir)) {
                throw new IOException("Unable to change of working directory to:"+ workDir);
            }
        }

        return client;
    }
}
