package org.gnunet.transport;

import org.gnunet.hello.HelloMessage;
import org.gnunet.mq.Envelope;
import org.gnunet.mq.NotifySentHandler;
import org.gnunet.transport.messages.RequestConnectMessage;
import org.gnunet.transport.messages.StartMessage;
import org.gnunet.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.LinkedList;
import java.util.List;

/**
 * ...
 *
 * @author Florian Dold
 */
public class Transport {
    private static final Logger logger = LoggerFactory
            .getLogger(Transport.class);
    /**
     * Client that connects to the transport service,
     */
    private final Client client;

    /**
     * Hello of our peer. May be null if not received yet.
     */
    private HelloMessage myHello;

    /**
     * Callbacks waiting for a hello.
     */
    private List<GetHelloHolder> getHelloHolderList = new LinkedList<GetHelloHolder>();

    /**
     * Wrapper to give each get hello getRequestIdentifier a unique identity.
     */
    private class GetHelloHolder {
        HelloUpdateCallback cb;
    }

    private final class TransportReceiver extends RunaboutMessageReceiver {
        public void visit(HelloMessage m) {
            System.out.println("got hello");
            myHello = m;
            List<GetHelloHolder> list = getHelloHolderList;
            getHelloHolderList = new LinkedList<GetHelloHolder>();
            for (GetHelloHolder h : list) {
                h.cb.onHello(m);
            }
        }
        @Override
        public void handleError() {
            logger.warn("Error receiving from the transport service, reconnecting.");
            // FIXME: there is no backoff ...
            client.reconnect();
            sendStart();
        }
    }

    private void sendStart() {
        org.gnunet.transport.messages.StartMessage m = new StartMessage();
        // no self check or payload traffic for now
        m.options = 0;
        m.self = new PeerIdentity();
        client.send(m);
    }

    /**
     * Create a handle to the transport service.
     *
     * @param cfg configuration to use for connecting
     */
    public Transport(Configuration cfg) {
        client = new Client("transport", cfg);
        client.installReceiver(new TransportReceiver());
        sendStart();
    }

    /**
     * Ask the transport service to establish a connection to
     * the given peer.
     *
     * @param target who we should try to connect to
     * @param cb     callback to be called when getRequestIdentifier was transmitted to transport
     *               service
     * @return a handle to onCancel the operation
     */
    Cancelable tryConnect(PeerIdentity target, final TryConnectCallback cb) {
        RequestConnectMessage m = new RequestConnectMessage();
        m.peer = target;
        m.reserved = 0;
        final Envelope ev = new Envelope(m);
        ev.notifySent(new NotifySentHandler() {
            @Override
            public void onSent() {
                cb.onDone();
            }
        });
        client.send(ev);

        return new Cancelable() {
            @Override
            public void cancel() {
                ev.cancel();
            }
        };
    }


    /**
     * Obtain the HELLO message for this peer.
     *
     * @param rec function to call with the HELLO
     * @return handle to onCancel the operation
     */
    Cancelable getHello(final HelloUpdateCallback rec) {
        if (myHello != null) {
            return Scheduler.add(new Scheduler.Task() {
                @Override
                public void run(Scheduler.RunContext ctx) {
                    rec.onHello(myHello);
                }
            });
        }
        System.out.println("waiting for hello");
        final GetHelloHolder holder = new GetHelloHolder();
        holder.cb = rec;
        getHelloHolderList.add(holder);
        return new Cancelable() {
            @Override
            public void cancel() {
                getHelloHolderList.remove(holder);
            }
        };
    }

    /**
     * Offer the transport service the HELLO of another peer.  Note that
     * the transport service may just ignore this message if the HELLO is
     * malformed or useless due to our local configuration.
     *
     * @param hello the hello message
     * @param cont  continuation to call when HELLO has been sent
     * @return a GNUNET_TRANSPORT_OfferHelloHandle handle or NULL on failure,
     *         in case of failure cont will not be called
     */

    Cancelable offerHello(HelloMessage hello,
                          final OfferHelloContinuation cont) {
        final Envelope ev = new Envelope(hello);
        ev.notifySent(new NotifySentHandler() {
            @Override
            public void onSent() {
                cont.onDone();
            }
        });
        client.send(ev);
        return new Cancelable() {
            @Override
            public void cancel() {
                ev.cancel();
            }
        };
    }

    public void disconnect() {
        client.disconnect();
    }

}

