package org.apache.maven.deploy.deployers;

/* ====================================================================
 *   Copyright 2001-2004 The Apache Software Foundation.
 *
 *   Licensed 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 com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.Session;

import org.apache.maven.deploy.DeployRequest;
import org.apache.maven.deploy.RepositoryInfo;
import org.apache.maven.deploy.exceptions.AuthenticationException;
import org.apache.maven.deploy.exceptions.TransferFailedException;
import org.apache.maven.deploy.util.Packager;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;

import java.util.LinkedList;
import java.util.List;

/**
 * An SSH2/SCP deployer
 *
 * @author Michal Maczka
 * @version $Revision: 1.8 $ $Date: 2004/03/02 14:59:31 $
 */
public class ScpDeployer extends GenericSshDeployer
{
    /**Protocol understood  by this deployer*/
    public final static String PROTOCOL = "scp://";

    /** SSH2 Channel name used to communicate with the server*/
    public final static String EXEC_CHANNEL = "exec";
    public final static String COMPRESS = "maven.deployer.scp.compress";

    /**
     *  I want to have compression by default
     *   so if system property is missing flag will be set to false
     *  that's why I am using deployNotCompressed variable name instead of 
     *   straight forward deployCompressed
     */
    public boolean deployNotCompressed = true;
    private List srcFiles = new LinkedList();
    private List destFiles = new LinkedList();

    /**
     * @see org.apache.maven.deploy.deployers.Deployer#release()
     */
    public void flush() throws TransferFailedException
    {
        if (!deployNotCompressed)
        {
            doDeployCompressed();
        }

        super.flush();
    }

    /**
     * @return
     */
    public boolean isDeployNotCompressed()
    {
        return deployNotCompressed;
    }

    /**
     * @param deployCompressed
     */
    public void setDeployNotCompressed(boolean deployNotCompressed)
    {
        this.deployNotCompressed = deployNotCompressed;
    }

    /**
     * @todo Find better way to configure deployer
     * @see org.apache.maven.deploy.deployers.Deployer#init(org.apache.maven.deploy.RepositoryInfo)
     */
    public void init(RepositoryInfo repoInfo) throws AuthenticationException
    {
        setDeployNotCompressed(Boolean.getBoolean(COMPRESS));
        super.init(repoInfo);
    }

    /* (non-Javadoc)
     * @see org.apache.maven.deploy.deployers.Deployer#deploy(org.apache.maven.deploy.DeployRequest)
     */
    public void deploy(DeployRequest request) throws TransferFailedException
    {
        if (deployNotCompressed)
        {
            doDeployNotCompressed(request);
        }
        else
        {
            srcFiles.add(request.getSrcFile());
            destFiles.add(request.getDestFile());
        }
    }

    /**
     * @see Deployer#deploy(DeployRequest)
     *
     */
    public void doDeployNotCompressed(DeployRequest request)
        throws TransferFailedException
    {
        Session session = getSession();
        String mkdirCmd =
            "mkdir -p "
                + getRepositoryInfo().getBasedir()
                + "/"
                + request.dirname()
                + "\n";

        executeSimpleCommand(session, mkdirCmd);

        doCopy(session, request);

        if (getRepositoryInfo().getGroup() != null)
        {
            String chgrpCmd =
                "chgrp "
                    + getRepositoryInfo().getGroup()
                    + " "
                    + getRepositoryInfo().getBasedir()
                    + "/"
                    + request.getDestFile()
                    + "\n";

            executeSimpleCommand(session, chgrpCmd);
        }
    }

    /**
     * @see Deployer#deploy(DeployRequest)
     *
     */
    public void doDeployCompressed() throws TransferFailedException
    {
        Session session = getSession();
        File zip = null;

        try
        {
            zip = File.createTempFile("maven-deployer-", ".zip");
            Packager.createZip(srcFiles, destFiles, zip);
        }
        catch (IOException e)
        {
            if ((zip != null) && zip.exists())
            {
                try
                {
                    zip.delete();
                }
                catch (Exception ee)
                {
                    // ignore
                    // file is left in temp directory
                    // no tragedy
                }
            }

            throw new TransferFailedException("Cannot create ZIP file");
        }

        try
        {
            DeployRequest request =
                new DeployRequest(zip.getAbsolutePath(), zip.getName());

            doCopy(session, request);

            String unzipCmd =
                "unzip -d -u "
                    + getRepositoryInfo().getBasedir()
                    + " "
                    + getRepositoryInfo().getBasedir()
                    + "/"
                    + request.getDestFile()
                    + "\n";
            executeSimpleCommand(session, unzipCmd);

            //@ this is not working!
            String rmCmd =
                "rm -f "
                    + getRepositoryInfo().getBasedir()
                    + "/"
                    + request.getDestFile()
                    + "\n";

            executeSimpleCommand(session, rmCmd);

            if (getRepositoryInfo().getGroup() != null)
            {
                String chgrpCmd =
                    "chgrp -r"
                        + getRepositoryInfo().getGroup()
                        + " "
                        + getRepositoryInfo().getBasedir()
                        + "\n";

                executeSimpleCommand(session, chgrpCmd);
            }

        }
        finally
        {
            if ((zip != null) && zip.exists())
            {
                try
                {
                    zip.delete();
                }
                catch (Exception ee)
                {
                    // ignore
                    // file is left in temp directory
                    // no tragedy
                }
            }
        }
    }

    /**
     * Execute <i>"simple"</i> remote command using exec channel
     */
    private void executeSimpleCommand(Session session, String command)
        throws TransferFailedException
    {
        System.out.println("Executing command: " + command);

        ChannelExec channel = null;

        try
        {
            channel = (ChannelExec) session.openChannel(EXEC_CHANNEL);
            channel.setCommand(command);

            OutputStream out = channel.getOutputStream();
            InputStream in = channel.getInputStream();

            channel.connect();
        }
        catch (Exception e)
        {
            throw new TransferFailedException(
                "Cannot execute remote command: " + command);
        }
        finally
        {
            if (channel != null)
            {
                channel.disconnect();
            }
        }
    }

    /**
     * Copy artifact to remote host.
     * Code extracted from JSCH exaples
     */
    private void doCopy(Session session, DeployRequest request)
        throws TransferFailedException
    {
        ChannelExec channel = null;

        try
        {
            String srcFile = request.getSrcFile();
            String destFile =
                getRepositoryInfo().getBasedir() + "/" + request.getDestFile();

            // exec 'scp -t rfile' remotely
            String command = "scp -t " + destFile;

            System.out.println("Executing command: " + command);
            channel = (ChannelExec) session.openChannel(EXEC_CHANNEL);
            channel.setCommand(command);

            // get I/O streams for remote scp
            OutputStream out = channel.getOutputStream();
            InputStream in = channel.getInputStream();

            channel.connect();

            byte[] tmp = new byte[1];

            // wait for '\0'
            do
            {
                in.read(tmp, 0, 1);
            }
            while (tmp[0] != 0);

            // send "C0644 filesize filename", where filename should not include '/'
            int filesize = (int) (new File(srcFile)).length();

            command = "C0644 " + filesize + " ";

            if (srcFile.lastIndexOf('/') > 0)
            {
                command += srcFile.substring(srcFile.lastIndexOf('/') + 1);
            }
            else
            {
                command += srcFile;
            }

            command += "\n";

            out.write(command.getBytes());
            out.flush();

            // wait for '\0'
            do
            {
                in.read(tmp, 0, 1);
            }
            while (tmp[0] != 0);

            // send a content of inputFile
            FileInputStream fis = new FileInputStream(srcFile);
            byte[] buf = new byte[1024];

            while (true)
            {
                int len = fis.read(buf, 0, buf.length);

                if (len <= 0)
                {
                    break;
                }

                out.write(buf, 0, len);
                out.flush();
            }

            // send '\0'
            buf[0] = 0;
            out.write(buf, 0, 1);
            out.flush();

            // wait for '\0'
            do
            {
                in.read(tmp, 0, 1);
            }
            while (tmp[0] != 0);
        }
        catch (Exception e)
        {
            String msg =
                "Error occured while deploying to remote host:"
                    + getRepositoryInfo().getHost();

            throw new TransferFailedException(msg, e);
        }
        finally
        {
            if (channel != null)
            {
                channel.disconnect();
            }
        }
    }
}
