/* 
 * 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.felix.obrplugin;


import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;

import org.apache.maven.artifact.manager.WagonManager;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.logging.Log;
import org.apache.maven.settings.Proxy;
import org.apache.maven.settings.Settings;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.UnsupportedProtocolException;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.WagonException;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.proxy.ProxyInfo;
import org.apache.maven.wagon.repository.Repository;


/**
 * this class is used to manage all connections by wagon.
 * @author <a href="mailto:dev@felix.apache.org">Felix Project Team</a>
 */
public class RemoteFileManager
{
    /**
     * save the connection.
     */
    private Wagon m_wagon;

    /**
     * the wagon manager.
     */
    private WagonManager m_wagonManager;

    /**
     * the project settings.
     */
    private Settings m_settings;

    /**
     * logger instance.
     */
    private Log m_log;


    /**
     * initialize main information.
     * @param wm WagonManager provides by maven
     * @param settings settings of the current project provides by maven
     * @param log logger
     */
    public RemoteFileManager( WagonManager wm, Settings settings, Log log )
    {
        m_wagonManager = wm;
        m_settings = settings;
        m_log = log;
        m_wagon = null;
    }


    /**
     * disconnect the current object.
     */
    public void disconnect()
    {
        try
        {
            if ( m_wagon != null )
            {
                m_wagon.disconnect();
            }
        }
        catch ( ConnectionException e )
        {
            m_log.error( "Error disconnecting Wagon", e );
        }
    }


    /**
     * connect the current object to repository given in constructor.
     * @param id repository id
     * @param url repository url
     * @throws MojoExecutionException
     */
    public void connect( String id, String url ) throws MojoExecutionException
    {
        Repository repository = new Repository( id, url );

        try
        {
            m_wagon = m_wagonManager.getWagon( repository );
        }
        catch ( UnsupportedProtocolException e )
        {
            throw new MojoExecutionException( "Unsupported protocol: '" + repository.getProtocol() + "'", e );
        }
        catch ( WagonException e )
        {
            throw new MojoExecutionException( "Unable to configure Wagon: '" + repository.getProtocol() + "'", e );
        }

        try
        {
            ProxyInfo proxyInfo = getProxyInfo( m_settings );
            if ( proxyInfo != null )
            {
                m_wagon.connect( repository, m_wagonManager.getAuthenticationInfo( id ), proxyInfo );
            }
            else
            {
                m_wagon.connect( repository, m_wagonManager.getAuthenticationInfo( id ) );
            }
        }
        catch ( ConnectionException e )
        {
            throw new MojoExecutionException( "Connection failed", e );
        }
        catch ( AuthenticationException e )
        {
            throw new MojoExecutionException( "Authentication failed", e );
        }
    }


    /**
     * get a file from the current repository connected.
     * @param url url to the targeted file
     * @param suffix suggested file suffix
     * @return get a file descriptor on the required resource
     * @throws MojoExecutionException
     */
    public File get( String url, String suffix ) throws MojoExecutionException
    {
        if ( m_wagon == null )
        {
            m_log.error( "must be connected first!" );
            return null;
        }

        File file = null;
        try
        {
            file = File.createTempFile( String.valueOf( System.currentTimeMillis() ), suffix );
        }
        catch ( IOException e )
        {
            throw new MojoExecutionException( "I/O problem", e );
        }

        try
        {
            m_wagon.get( url, file );
        }
        catch ( TransferFailedException e )
        {
            file.delete(); // cleanup on failure
            throw new MojoExecutionException( "Transfer failed", e );
        }
        catch ( AuthorizationException e )
        {
            file.delete(); // cleanup on failure
            throw new MojoExecutionException( "Authorization failed", e );
        }
        catch ( ResourceDoesNotExistException e )
        {
            file.delete(); // return non-existent file
        }

        return file;
    }


    /**
     * put a file on the current repository connected.
     * @param file file to upload
     * @param url url to copy file
     * @throws MojoExecutionException
     */
    public void put( File file, String url ) throws MojoExecutionException
    {
        if ( m_wagon == null )
        {
            m_log.error( "must be connected first!" );
            return;
        }

        try
        {
            m_wagon.put( file, url );
        }
        catch ( TransferFailedException e )
        {
            throw new MojoExecutionException( "Transfer failed", e );
        }
        catch ( AuthorizationException e )
        {
            throw new MojoExecutionException( "Authorization failed", e );
        }
        catch ( ResourceDoesNotExistException e )
        {
            throw new MojoExecutionException( "Resource does not exist:" + file, e );
        }
    }


    /**
     * Convenience method to map a Proxy object from the user system settings to a ProxyInfo object.
     * @param settings project settings given by maven
     * @return a proxyInfo object instancied or null if no active proxy is define in the settings.xml
     */
    public static ProxyInfo getProxyInfo( Settings settings )
    {
        ProxyInfo proxyInfo = null;
        if ( settings != null && settings.getActiveProxy() != null )
        {
            Proxy settingsProxy = settings.getActiveProxy();

            proxyInfo = new ProxyInfo();
            proxyInfo.setHost( settingsProxy.getHost() );
            proxyInfo.setType( settingsProxy.getProtocol() );
            proxyInfo.setPort( settingsProxy.getPort() );
            proxyInfo.setNonProxyHosts( settingsProxy.getNonProxyHosts() );
            proxyInfo.setUserName( settingsProxy.getUsername() );
            proxyInfo.setPassword( settingsProxy.getPassword() );
        }

        return proxyInfo;
    }


    public void lockFile( String fileName, boolean ignoreLock ) throws MojoExecutionException
    {
        if ( !ignoreLock )
        {
            int countError = 0;
            while ( isLockedFile( fileName ) && countError < 2 )
            {
                countError++;
                m_log.warn( "File is currently locked, retry in 10s" );
                try
                {
                    Thread.sleep( 10000 );
                }
                catch ( InterruptedException e )
                {
                    m_log.warn( "Sleep interrupted" );
                }
            }

            if ( countError == 2 )
            {
                m_log.error( "File " + fileName + " is locked. Use -DignoreLock to force uploading" );
                throw new MojoExecutionException( "Remote file locked" );
            }
        }

        File file = null;
        try
        {
            // create a non-empty file used to lock the remote file
            file = File.createTempFile( String.valueOf( System.currentTimeMillis() ), ".lock" );

            Writer writer = new BufferedWriter( new FileWriter( file ) );
            writer.write( "LOCKED" );
            writer.close();

            put( file, fileName + ".lock" );
        }
        catch ( IOException e )
        {
            throw new MojoExecutionException( "I/O problem", e );
        }
        finally
        {
            if ( null != file )
            {
                file.delete();
            }
        }
    }


    public void unlockFile( String fileName ) throws MojoExecutionException
    {
        File file = null;
        try
        {
            // clear the contents of the file used to lock the remote file
            file = File.createTempFile( String.valueOf( System.currentTimeMillis() ), ".lock" );

            Writer writer = new BufferedWriter( new FileWriter( file ) );
            writer.write( " " ); // write 1 byte to force wagon upload
            writer.close();

            put( file, fileName + ".lock" );
        }
        catch ( IOException e )
        {
            throw new MojoExecutionException( "I/O problem", e );
        }
        finally
        {
            if ( null != file )
            {
                file.delete();
            }
        }
    }


    /**
     * this method indicates if the targeted file is locked or not.
     * @param fileName name targeted
     * @return  true if the required file is locked, else false
     * @throws MojoExecutionException
     */
    public boolean isLockedFile( String fileName ) throws MojoExecutionException
    {
        File file = null;
        try
        {
            file = get( fileName + ".lock", ".lock" );

            // file is locked with contents "LOCKED"
            if ( null != file && file.length() <= 2 )
            {
                return false;
            }
        }
        finally
        {
            if ( null != file )
            {
                file.delete();
            }
        }

        return true;
    }


    public String toString()
    {
        return m_wagon.getRepository().getUrl();
    }
}
