package org.apache.maven.scm.provider.cvslib.cvsjava.util;

/*
 * 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.
 */

import ch.ethz.ssh2.Connection;
import ch.ethz.ssh2.Session;
import ch.ethz.ssh2.StreamGobbler;
import org.netbeans.lib.cvsclient.CVSRoot;
import org.netbeans.lib.cvsclient.command.CommandAbortedException;
import org.netbeans.lib.cvsclient.connection.AbstractConnection;
import org.netbeans.lib.cvsclient.connection.AuthenticationException;
import org.netbeans.lib.cvsclient.connection.ConnectionModifier;
import org.netbeans.lib.cvsclient.util.LoggedDataInputStream;
import org.netbeans.lib.cvsclient.util.LoggedDataOutputStream;

import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * @author <a href="mailto:evenisse@apache.org">Emmanuel Venisse</a>
 *
 */
public class ExtConnection
    extends AbstractConnection
{
    private String host;

    private int port;

    private String userName;

    private String password;

    private Connection connection;

    private Session session;

    private BufferedReader stderrReader;

    public ExtConnection( CVSRoot cvsRoot )
    {
        this( cvsRoot.getHostName(), cvsRoot.getPort(), cvsRoot.getUserName(), cvsRoot.getPassword(),
              cvsRoot.getRepository() );
    }

    public ExtConnection( String host, int port, String username, String password, String repository )
    {
        this.userName = username;

        if ( this.userName == null )
        {
            this.userName = System.getProperty( "user.name" );
        }

        this.password = password;

        this.host = host;

        setRepository( repository );

        this.port = port;

        if ( this.port == 0 )
        {
            this.port = 22;
        }
    }

    /** {@inheritDoc} */
    public void open()
        throws AuthenticationException, CommandAbortedException
    {
        connection = new Connection( host, port );

        /* TODO: add proxy support
        ProxyData proxy = new HTTPProxyData( proxyHost, proxyPort, proxyUserName, proxyPassword );

        connection.setProxyData( proxy );
        */

        try
        {
            // TODO: connection timeout?
            connection.connect();
        }
        catch ( IOException e )
        {
            String message = "Cannot connect. Reason: " + e.getMessage();
            throw new AuthenticationException( message, e, message );
        }

        File privateKey = getPrivateKey();

        try
        {
            boolean authenticated;
            if ( privateKey != null && privateKey.exists() )
            {
                authenticated = connection.authenticateWithPublicKey( userName, privateKey, getPassphrase() );
            }
            else
            {
                authenticated = connection.authenticateWithPassword( userName, password );
            }

            if ( !authenticated )
            {
                String message = "Authentication failed.";
                throw new AuthenticationException( message, message );
            }
        }
        catch ( IOException e )
        {
            closeConnection();
            String message = "Cannot authenticate. Reason: " + e.getMessage();
            throw new AuthenticationException( message, e, message );
        }

        try
        {
            session = connection.openSession();
        }
        catch ( IOException e )
        {
            String message = "Cannot open session. Reason: " + e.getMessage();
            throw new CommandAbortedException( message, message );
        }

        String command = "cvs server";
        try
        {
            session.execCommand( command );
        }
        catch ( IOException e )
        {
            String message = "Cannot execute remote command: " + command;
            throw new CommandAbortedException( message, message );
        }

        InputStream stdout = new StreamGobbler( session.getStdout() );
        InputStream stderr = new StreamGobbler( session.getStderr() );
        stderrReader = new BufferedReader( new InputStreamReader( stderr ) );
        setInputStream( new LoggedDataInputStream( stdout ) );
        setOutputStream( new LoggedDataOutputStream( session.getStdin() ) );
    }

    /** {@inheritDoc} */
    public void verify()
        throws AuthenticationException
    {
        try
        {
            open();
            verifyProtocol();
            close();
        }
        catch ( Exception e )
        {
            String message = "Failed to verify the connection: " + e.getMessage();
            throw new AuthenticationException( message, e, message );
        }
    }

    private void closeConnection()
    {
        try
        {
            if ( stderrReader != null )
            {
                while ( true )
                {
                    String line = stderrReader.readLine();
                    if ( line == null )
                    {
                        break;
                    }

                    System.err.println( line );
                }
            }
        }
        catch ( IOException e )
        {
            //nothing to do
        }

        if ( session != null )
        {
            System.out.println( "Exit code:" + session.getExitStatus().intValue() );
            session.close();
        }

        if ( connection != null )
        {
            connection.close();
        }

        reset();
    }

    private void reset()
    {
        connection = null;
        session = null;
        stderrReader = null;
        setInputStream( null );
        setOutputStream( null );
    }

    /** {@inheritDoc} */
    public void close()
        throws IOException
    {
        closeConnection();
    }

    /** {@inheritDoc} */
    public boolean isOpen()
    {
        return connection != null;
    }

    /** {@inheritDoc} */
    public int getPort()
    {
        return port;
    }

    /** {@inheritDoc} */
    public void modifyInputStream( ConnectionModifier modifier )
        throws IOException
    {
        modifier.modifyInputStream( getInputStream() );
    }

    /** {@inheritDoc} */
    public void modifyOutputStream( ConnectionModifier modifier )
        throws IOException
    {
        modifier.modifyOutputStream( getOutputStream() );
    }

    private File getPrivateKey()
    {
        // If user don't define a password, he want to use a private key
        File privateKey = null;
        if ( password == null )
        {
            String pk = System.getProperty( "maven.scm.cvs.java.ssh.privateKey" );
            if ( pk != null )
            {
                privateKey = new File( pk );
            }
            else
            {
                privateKey = findPrivateKey();
            }
        }
        return privateKey;
    }

    private String getPassphrase()
    {
        String passphrase = System.getProperty( "maven.scm.cvs.java.ssh.passphrase" );

        if ( passphrase == null )
        {
            passphrase = "";
        }

        return passphrase;
    }

    private File findPrivateKey()
    {
        String privateKeyDirectory = System.getProperty( "maven.scm.ssh.privateKeyDirectory" );

        if ( privateKeyDirectory == null )
        {
            privateKeyDirectory = System.getProperty( "user.home" );
        }

        File privateKey = new File( privateKeyDirectory, ".ssh/id_dsa" );

        if ( !privateKey.exists() )
        {
            privateKey = new File( privateKeyDirectory, ".ssh/id_rsa" );
        }

        return privateKey;
    }
}
