/*
 * 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.brooklyn.util.core.internal.ssh;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static org.apache.brooklyn.util.net.Networking.checkPortValid;

import java.io.File;
import java.util.Map;
import java.util.Set;

import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.os.Os;

import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;

public abstract class SshAbstractTool extends ShellAbstractTool implements SshTool {

    protected final String toString;

    protected final String host;
    protected final String user;
    protected final String password;
    protected final int port;
    protected String privateKeyPassphrase;
    protected String privateKeyData;
    protected File privateKeyFile;
    protected boolean strictHostKeyChecking;
    protected boolean allocatePTY;

    public static interface SshAction<T> {
        void clear() throws Exception;
        T create() throws Exception;
    }

    public static abstract class AbstractSshToolBuilder<T extends SshTool, B extends AbstractSshToolBuilder<T,B>> {
        protected String host;
        protected int port = 22;
        protected String user = System.getProperty("user.name");
        protected String password;
        protected String privateKeyData;
        protected String privateKeyPassphrase;
        protected Set<String> privateKeyFiles = Sets.newLinkedHashSet();
        protected boolean strictHostKeyChecking = false;
        protected boolean allocatePTY = false;
        protected File localTempDir = null;

        @SuppressWarnings("unchecked")
        protected B self() {
           return (B) this;
        }

        public B from(Map<String,?> props) {
            host = getMandatoryVal(props, PROP_HOST);
            port = getOptionalVal(props, PROP_PORT);
            user = getOptionalVal(props, PROP_USER);
            
            password = getOptionalVal(props, PROP_PASSWORD);
            
            warnOnDeprecated(props, "privateKey", "privateKeyData");
            privateKeyData = getOptionalVal(props, PROP_PRIVATE_KEY_DATA);
            privateKeyPassphrase = getOptionalVal(props, PROP_PRIVATE_KEY_PASSPHRASE);
            
            // for backwards compatibility accept keyFiles and privateKey
            // but sshj accepts only a single privateKeyFile; leave blank to use defaults (i.e. ~/.ssh/id_rsa and id_dsa)
            warnOnDeprecated(props, "keyFiles", null);
            String privateKeyFile = getOptionalVal(props, PROP_PRIVATE_KEY_FILE);
            if (privateKeyFile != null) privateKeyFiles.add(privateKeyFile);
            
            strictHostKeyChecking = getOptionalVal(props, PROP_STRICT_HOST_KEY_CHECKING);
            allocatePTY = getOptionalVal(props, PROP_ALLOCATE_PTY);
            
            String localTempDirPath = getOptionalVal(props, PROP_LOCAL_TEMP_DIR);
            localTempDir = (localTempDirPath == null) ? null : new File(Os.tidyPath(localTempDirPath));
            
            return self();
        }
        public B host(String val) {
            this.host = val; return self();
        }
        public B user(String val) {
            this.user = val; return self();
        }
        public B password(String val) {
            this.password = val; return self();
        }
        public B port(int val) {
            this.port = val; return self();
        }
        public B privateKeyPassphrase(String val) {
            this.privateKeyPassphrase = val; return self();
        }
        /** @deprecated 1.4.0, use privateKeyData */
        @Deprecated
        public B privateKey(String val) {
            this.privateKeyData = val; return self();
        }
        public B privateKeyData(String val) {
            this.privateKeyData = val; return self();
        }
        public B privateKeyFile(String val) {
            this.privateKeyFiles.add(val); return self();
        }
        public B localTempDir(File val) {
            this.localTempDir = val; return self();
        }
        public abstract T build();
    }

    protected SshAbstractTool(AbstractSshToolBuilder<?,?> builder) {
        super(builder.localTempDir);
        
        host = checkNotNull(builder.host, "host");
        port = builder.port;
        user = builder.user;
        password = builder.password;
        strictHostKeyChecking = builder.strictHostKeyChecking;
        allocatePTY = builder.allocatePTY;
        privateKeyPassphrase = builder.privateKeyPassphrase;
        privateKeyData = builder.privateKeyData;
        
        if (builder.privateKeyFiles.size() > 1) {
            throw new IllegalArgumentException("sshj supports only a single private key-file; " +
                    "for defaults of ~/.ssh/id_rsa and ~/.ssh/id_dsa leave blank");
        } else if (builder.privateKeyFiles.size() == 1) {
            String privateKeyFileStr = Iterables.get(builder.privateKeyFiles, 0);
            String amendedKeyFile = privateKeyFileStr.startsWith("~") ? (System.getProperty("user.home")+privateKeyFileStr.substring(1)) : privateKeyFileStr;
            privateKeyFile = new File(amendedKeyFile);
        } else {
            privateKeyFile = null;
        }
        
        checkArgument(host.length() > 0, "host value must not be an empty string");
        checkPortValid(port, "ssh port");
        
        toString = String.format("%s@%s:%d", user, host, port);
    }

    @Override
    public String toString() {
        return toString;
    }

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

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

    protected SshException propagate(Exception e, String message) throws SshException {
        Exceptions.propagateIfFatal(e);
        throw new SshException("(" + toString() + ") " + message + ": " + e.getMessage(), e);
    }
    
}
