/*
 This file is part of GNUnet.
 (C) 2011, 2012 Christian Grothoff (and other contributing authors)

 GNUnet is free software; you can redistribute it and/or modify
 it under the terms of the GNU General Public License as published
 by the Free Software Foundation; either version 3, or (at your
 option) any later version.

 GNUnet is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 You should have received a copy of the GNU General Public License
 along with GNUnet; see the file COPYING.  If not, write to the
 Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 Boston, MA 02111-1307, USA.
 */

package org.gnunet.peerinfo;

import org.gnunet.hello.HelloMessage;
import org.gnunet.mq.Envelope;
import org.gnunet.peerinfo.messages.InfoEnd;
import org.gnunet.peerinfo.messages.InfoMessage;
import org.gnunet.peerinfo.messages.ListAllPeersMessage;
import org.gnunet.peerinfo.messages.ListPeerMessage;
import org.gnunet.requests.Request;
import org.gnunet.requests.SequentialRequestContainer;
import org.gnunet.util.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Interface to the service that maintains all known hosts.
 *
 * @author Florian Dold
 */
public class PeerInfo {
    private static final Logger logger = LoggerFactory
            .getLogger(PeerInfo.class);

    /**
     * Client that connects to the peerinfo service.
     */
    private final Client client;

    /**
     * All currently active iterate requests.
     */
    private SequentialRequestContainer<PeerIterateRequest> iterateRequests;

    private class PeerIterateRequest extends Request {
        public PeerIdentity peer;
        public PeerProcessor peerProcessor;
        public boolean friendOnly;
        public boolean canceled;

        public PeerIterateRequest(PeerIdentity peer, boolean friendOnly, PeerProcessor peerProcessor) {
            this.peer = peer;
            this.peerProcessor = peerProcessor;
            this.friendOnly = friendOnly;
        }

        @Override
        public Envelope assembleRequest() {
            if (peer == null) {
                ListAllPeersMessage m = new ListAllPeersMessage();
                m.includeFriendOnly = friendOnly ? 1 : 0;
                return new Envelope(m);
            } else {
                ListPeerMessage m = new ListPeerMessage();
                m.peer = peer;
                return new Envelope(m);
            }
        }

        public void onCancel() {
            canceled = true;
        }
    }

    private class PeerInfoMessageReceiver extends RunaboutMessageReceiver {
        public void visit(InfoEnd infoEnd) {
            PeerIterateRequest r = iterateRequests.getAndRetireRequest();
            if (!r.canceled)
                r.peerProcessor.onEnd();
        }
        public void visit(InfoMessage infoMessage) {
            PeerIterateRequest r = iterateRequests.getRequest();
            if (!r.canceled)
                r.peerProcessor.onPeer(infoMessage.peerIdentity, infoMessage.hello);
        }

        @Override
        public void handleError() {
            client.reconnect();
            iterateRequests.restart();
        }
    }


    /**
     * Create a connection to the peerinfo service.
     *
     * @param cfg configuration to use
     */
    public PeerInfo(Configuration cfg) {
        client = new Client("peerinfo", cfg);
        client.installReceiver(new PeerInfoMessageReceiver());
        iterateRequests = new SequentialRequestContainer<PeerIterateRequest>(client);
    }

    /**
     * Iterates over the HELLOs of all peers.
     *
     * @param timeout
     * @param processor
     * @return a handle to cancel the request
     */
    public Cancelable iterate(RelativeTime timeout, boolean friendOnly, PeerProcessor processor) {
        return iterate(timeout, null, friendOnly, processor);
    }

    /**
     * Iterates over the HELLOs of the given peer.
     * Can be called with peer=null to iterate over all peers.
     * @param timeout
     * @param peer
     * @param processor
     * @return a handle to cancel the request
     */
    public Cancelable iterate(RelativeTime timeout, PeerIdentity peer, boolean friendOnly, PeerProcessor processor) {
        PeerIterateRequest r = new PeerIterateRequest(peer, friendOnly, processor);
        return iterateRequests.addRequest(r);
    }

    /**
     * Disconnect from the peerinfo service.
     */
    public void disconnect() {
        client.disconnect();
    }

    public static void main(String... args) {
        new Program() {
            @Override
            public void run() {
                final PeerInfo peerInfo = new PeerInfo(getConfiguration());
                peerInfo.iterate(RelativeTime.FOREVER, false, new PeerProcessor() {
                    @Override
                    public void onPeer(PeerIdentity peerIdentity, HelloMessage hello) {
                        System.out.println("peer " + peerIdentity.toString());
                    }

                    @Override
                    public void onEnd() {
                        System.out.println("got peer end");
                        peerInfo.disconnect();
                    }
                });
            }
        }.start(args);
    }
}
