package de.saumya.mojo.mavengem.wagon;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.Proxy.Type;
import java.net.SocketAddress;
import java.util.Map;
import java.util.HashMap;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.maven.wagon.StreamWagon;
import org.apache.maven.wagon.Wagon;
import org.apache.maven.wagon.InputData;
import org.apache.maven.wagon.OutputData;
import org.apache.maven.wagon.authentication.AuthenticationException;
import org.apache.maven.wagon.ConnectionException;
import org.apache.maven.wagon.TransferFailedException;
import org.apache.maven.wagon.resource.Resource;
import org.apache.maven.wagon.ResourceDoesNotExistException;

import org.apache.maven.wagon.authorization.AuthorizationException;
import org.apache.maven.wagon.proxy.ProxyInfo;

import de.saumya.mojo.mavengem.RubygemsFactory;
import de.saumya.mojo.mavengem.MavenGemURLConnection;

public class MavenGemWagon extends StreamWagon {

    public static final String MAVEN_GEM_PREFIX = "mavengem:";

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

    // configurable via the settings.xml
    private File cachedir;
    private String mirror;

    private void warn(String msg) {
	System.err.println("WARNING: " + msg);
    }

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

        Resource resource = inputData.getResource();

	try {

	    if (proxy != Proxy.NO_PROXY) {
		warn("proxy support is not implemented - ignoring proxy settings");
	    }

	    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);
    }

    @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);
	}
    }
}
