package org.torquebox.mojo.mavengem.wagon;

import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.OutputData;
import org.apache.maven.wagon.ResourceDoesNotExistException;
import org.apache.maven.wagon.StreamWagon;
import org.apache.maven.wagon.TransferFailedException;
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.resource.Resource;
import org.torquebox.mojo.mavengem.MavenGemURLConnection;
import org.torquebox.mojo.mavengem.RubygemsFactory;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.SocketAddress;
import java.net.URL;
import java.net.URLConnection;

public class MavenGemWagon extends StreamWagon {

    public static final String MAVEN_GEM_PREFIX = "mavengem:";

    private Proxy proxy = Proxy.NO_PROXY;
    private RubygemsFactory _factory_;

    // configurable and set via the settings.xml, i.e. names here need to match
    // the names in settings.xml
    private File cachedir;
    private String mirror;

    @Override
    public void fillInputData(InputData inputData)
            throws TransferFailedException, ResourceDoesNotExistException, AuthorizationException {

        Resource resource = inputData.getResource();

        try {

            URLConnection urlConnection = newConnection(resource.getName());
            InputStream is = urlConnection.getInputStream();

            inputData.setInputStream(is);
            resource.setLastModified(urlConnection.getLastModified());
            resource.setContentLength(urlConnection.getContentLength());

        } catch (MalformedURLException e) {
            throw new TransferFailedException("Invalid repository URL: " + e.getMessage(), e);
        } catch (FileNotFoundException e) {
            throw new ResourceDoesNotExistException("Unable to locate resource in repository", e);
        } catch (IOException e) {
            throw new TransferFailedException("Error transferring file: " + e.getMessage(), e);
        }
    }

    private RubygemsFactory rubygemsFactory()
            throws MalformedURLException {
        if (_factory_ == null) {
            // use the default if not set
            if (cachedir == null) {
                cachedir = RubygemsFactory.DEFAULT_CACHEDIR;
            }
            if (mirror != null) {
                _factory_ = new RubygemsFactory(cachedir, withAuthentication(mirror));
            } else {
                _factory_ = new RubygemsFactory(cachedir);
            }
        }
        return _factory_;
    }

    public URLConnection newConnection(String resourceName)
            throws MalformedURLException {
        return new MavenGemURLConnection(rubygemsFactory(),
                getRepositoryURL(),
                "/" + resourceName,
                proxy);
    }

    @Override
    public boolean resourceExists(String resourceName)
            throws TransferFailedException, AuthorizationException {
        try {
            newConnection(resourceName).connect();
            return true;
        } catch (FileNotFoundException e) {
            return false;
        } catch (MalformedURLException e) {
            throw new TransferFailedException("Invalid repository URL: " + e.getMessage(), e);
        } catch (IOException e) {
            throw new TransferFailedException("Error transferring file: " + e.getMessage(), e);
        }
    }

    @Override
    public void fillOutputData(OutputData outputData)
            throws TransferFailedException {
        throw new RuntimeException("only download is provided");
    }

    @Override
    public void closeConnection()
            throws ConnectionException {
    }

    private URL withAuthentication(String url)
            throws MalformedURLException {
        if (authenticationInfo != null && authenticationInfo.getUserName() != null) {
            String credentials = authenticationInfo.getUserName() + ":" + authenticationInfo.getPassword();
            url = url.replaceFirst("^(https?://)(.*)$", "$1" + credentials + "@$2");
        }
        return new URL(url);
    }

    private URL getRepositoryURL() throws MalformedURLException {
        String url = getRepository().getUrl().substring(MAVEN_GEM_PREFIX.length());
        return withAuthentication(url);
    }

    private Proxy getProxy(ProxyInfo proxyInfo) {
        return new Proxy(getProxyType(proxyInfo), getSocketAddress(proxyInfo));
    }

    private Type getProxyType(ProxyInfo proxyInfo) {
        if (ProxyInfo.PROXY_SOCKS4.equals(proxyInfo.getType()) || ProxyInfo.PROXY_SOCKS5.equals(proxyInfo.getType())) {
            return Type.SOCKS;
        } else {
            return Type.HTTP;
        }
    }

    private SocketAddress getSocketAddress(ProxyInfo proxyInfo) {
        return InetSocketAddress.createUnresolved(proxyInfo.getHost(), proxyInfo.getPort());
    }

    @Override
    protected void openConnectionInternal()
            throws ConnectionException, AuthenticationException {

        try {
            final ProxyInfo proxyInfo = getProxyInfo(getRepositoryURL().getProtocol(), getRepository().getHost());
            if (proxyInfo != null) {
                this.proxy = getProxy(proxyInfo);
            }
        } catch (MalformedURLException e) {
            throw new ConnectionException("cannot create repository url", e);
        }
    }
}
