package org.eclipse.jetty.spdy.proxy;

import java.net.InetSocketAddress;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.eclipse.jetty.spdy.api.GoAwayInfo;
import org.eclipse.jetty.spdy.api.Headers;
import org.eclipse.jetty.spdy.api.PingInfo;
import org.eclipse.jetty.spdy.api.RstInfo;
import org.eclipse.jetty.spdy.api.Session;
import org.eclipse.jetty.spdy.api.Stream;
import org.eclipse.jetty.spdy.api.StreamFrameListener;
import org.eclipse.jetty.spdy.api.StreamStatus;
import org.eclipse.jetty.spdy.api.SynInfo;
import org.eclipse.jetty.spdy.api.server.ServerSessionFrameListener;
import org.eclipse.jetty.spdy.http.HTTPSPDYHeader;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;

/**
 * <p>{@link ProxyEngineSelector} is the main entry point for syn stream events of a jetty SPDY proxy. It receives the
 * syn stream frames from the clients, checks if there's an appropriate {@link ProxyServerInfo} for the given target
 * host and forwards the syn to a {@link ProxyEngine} for the protocol defined in {@link ProxyServerInfo}.</p>
 *
 * <p>If no {@link ProxyServerInfo} can be found for the given target host or no {@link ProxyEngine} can be found for
 * the given protocol, it resets the client stream.</p>
 *
 * <p>This class also provides configuration for the proxy rules.</p>
 */
public class ProxyEngineSelector extends ServerSessionFrameListener.Adapter
{
    protected final Logger logger = Log.getLogger(getClass());
    private final Map<String, ProxyServerInfo> proxyInfos = new ConcurrentHashMap<>();
    private final Map<String, ProxyEngine> proxyEngines = new ConcurrentHashMap<>();

    @Override
    public final StreamFrameListener onSyn(final Stream clientStream, SynInfo clientSynInfo)
    {
        logger.debug("C -> P {} on {}", clientSynInfo, clientStream);

        final Session clientSession = clientStream.getSession();
        short clientVersion = clientSession.getVersion();
        Headers headers = new Headers(clientSynInfo.getHeaders(), false);

        Headers.Header hostHeader = headers.get(HTTPSPDYHeader.HOST.name(clientVersion));
        if (hostHeader == null)
        {
            rst(clientStream);
            return null;
        }

        String host = hostHeader.value();
        int colon = host.indexOf(':');
        if (colon >= 0)
            host = host.substring(0, colon);

        ProxyServerInfo proxyServerInfo = getProxyServerInfo(host);
        if (proxyServerInfo == null)
        {
            rst(clientStream);
            return null;
        }

        String protocol = proxyServerInfo.getProtocol();
        ProxyEngine proxyEngine = proxyEngines.get(protocol);
        if (proxyEngine == null)
        {
            rst(clientStream);
            return null;
        }

        return proxyEngine.proxy(clientStream, clientSynInfo, proxyServerInfo);
    }

    @Override
    public void onPing(Session clientSession, PingInfo pingInfo)
    {
        // We do not know to which upstream server
        // to send the PING so we just ignore it
    }

    @Override
    public void onGoAway(Session session, GoAwayInfo goAwayInfo)
    {
        // TODO:
    }

    public Map<String, ProxyEngine> getProxyEngines()
    {
        return new HashMap<>(proxyEngines);
    }

    public void setProxyEngines(Map<String, ProxyEngine> proxyEngines)
    {
        this.proxyEngines.clear();
        this.proxyEngines.putAll(proxyEngines);
    }

    public ProxyEngine getProxyEngine(String protocol)
    {
        return proxyEngines.get(protocol);
    }

    public void putProxyEngine(String protocol, ProxyEngine proxyEngine)
    {
        proxyEngines.put(protocol, proxyEngine);
    }

    public Map<String, ProxyServerInfo> getProxyServerInfos()
    {
        return new HashMap<>(proxyInfos);
    }

    protected ProxyServerInfo getProxyServerInfo(String host)
    {
        return proxyInfos.get(host);
    }

    public void setProxyServerInfos(Map<String, ProxyServerInfo> proxyServerInfos)
    {
        this.proxyInfos.clear();
        this.proxyInfos.putAll(proxyServerInfos);
    }

    public void putProxyServerInfo(String host, ProxyServerInfo proxyServerInfo)
    {
        proxyInfos.put(host, proxyServerInfo);
    }

    private void rst(Stream stream)
    {
        RstInfo rstInfo = new RstInfo(stream.getId(), StreamStatus.REFUSED_STREAM);
        stream.getSession().rst(rstInfo);
    }

    public static class ProxyServerInfo
    {
        private final String protocol;
        private final String host;
        private final InetSocketAddress address;

        public ProxyServerInfo(String protocol, String host, int port)
        {
            this.protocol = protocol;
            this.host = host;
            this.address = new InetSocketAddress(host, port);
        }

        public String getProtocol()
        {
            return protocol;
        }

        public String getHost()
        {
            return host;
        }

        public InetSocketAddress getAddress()
        {
            return address;
        }
    }
}

@@ -2197,32 +2197,94 @@
 null)%0A        %7B%0A
+            logger.debug(%22No host header found: %22 + headers);%0A
             rst(
@@ -2290,32 +2290,32 @@
 (clientStream);%0A
-
             retu
@@ -2592,32 +2592,108 @@
 null)%0A        %7B%0A
+            logger.debug(%22No matching ProxyServerInfo found for: %22 + host);%0A
             rst(
@@ -2887,32 +2887,32 @@
 Engine == null)%0A
-
         %7B%0A      
@@ -2897,32 +2897,108 @@
 null)%0A        %7B%0A
+            logger.debug(%22No matching ProxyEngine found for: %22 + protocol);%0A
             rst(

add logging when ProxyEngineSelector resets a stream