package net.i2p.i2ptunnel.socks;

import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.Map;

import net.i2p.data.Destination;
import net.i2p.i2ptunnel.udp.*;

/**
 * Implements a UDP port and Socks encapsulation / decapsulation.
 * This is for a single port. If there is demuxing for multiple
 * ports, it happens outside of here.
 *
 * TX:
 *   UDPSource -&gt; SOCKSUDPUnwrapper -&gt; ReplyTracker ( -&gt; I2PSink in SOCKSUDPTunnel)
 *
 * RX:
 *   UDPSink &lt;- SOCKSUDPWrapper ( &lt;- MultiSink &lt;- I2PSource in SOCKSUDPTunnel)
 *
 * The Unwrapper passes headers to the Wrapper through a cache.
 * The ReplyTracker passes sinks to MultiSink through a cache.
 *
 * @author zzz
 */
public class SOCKSUDPPort implements Source, Sink {

    public SOCKSUDPPort(InetAddress host, int port, Map<Destination, SOCKSUDPPort> replyMap) {

        // this passes the host and port from UDPUnwrapper to UDPWrapper
        Map<Destination, SOCKSHeader> cache = new ConcurrentHashMap<Destination, SOCKSHeader>(4);

        // rcv from I2P and send to a port
        this.wrapper = new SOCKSUDPWrapper(cache);
        this.udpsink = new UDPSink(host, port);
        this.wrapper.setSink(this.udpsink);
        
        // rcv from the same port and send to I2P
        DatagramSocket sock = this.udpsink.getSocket();
        this.udpsource = new UDPSource(sock);
        this.unwrapper = new SOCKSUDPUnwrapper(cache);
        this.udpsource.setSink(this.unwrapper);
        this.udptracker = new ReplyTracker<SOCKSUDPPort>(this, replyMap);
        this.unwrapper.setSink(this.udptracker);
    }

    /** Socks passes this back to the client on the TCP connection */
    public int getPort() {
        return this.udpsink.getPort();
    }

    public void setSink(Sink sink) {
        this.udptracker.setSink(sink);
    }

    public void start() {
        // the other Sources don't use start
        this.udpsource.start();
    }

    public void stop() {
        this.udpsink.stop();
        this.udpsource.stop();
    }

    /**
     *  May throw RuntimeException from underlying sink
     *  @throws RuntimeException
     */
    public void send(Destination from, byte[] data) {
        this.wrapper.send(from, data);
    }
    

    private UDPSink udpsink;
    private UDPSource udpsource;
    private SOCKSUDPWrapper wrapper;
    private SOCKSUDPUnwrapper unwrapper;
    private ReplyTracker<SOCKSUDPPort> udptracker;
}
