/*
 This file is part of GNUnet.
  (C) 2012, 2013 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.gns;

import org.gnunet.gns.messages.ClientLookupMessage;
import org.gnunet.gns.messages.ClientLookupResultMessage;
import org.gnunet.requests.FixedMessageRequest;
import org.gnunet.requests.MatchingRequestContainer;
import org.gnunet.requests.RequestIdentifier;
import org.gnunet.util.*;
import org.gnunet.util.crypto.EcdsaPrivateKey;
import org.gnunet.util.crypto.EcdsaPublicKey;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * API to the GNUnet name system.
 */
public class Gns {
    private static final Logger logger = LoggerFactory
            .getLogger(Gns.class);

    /**
     * Defaults, look in cache, then in DHT.
     */
    public static final int LOOKUP_OPTION_DEFAULT = 0;

    /**
     * Never look in the DHT, keep request to local cache.
     */
    public static final int LOOKUP_OPTION_NO_DHT = 1;

    /**
     * For the rightmost label, only look in the cache (it
     * is our master zone), for the others, the DHT is OK.
     */
    public static final int LOOKUP_OPTION_LOCAL_MASTER = 2;

    /**
     * All pending and active lookup requests.
     */
    private MatchingRequestContainer<Long, FixedMessageRequest<LookupResultProcessor>> lookupRequests;

    /**
     * Request ID for lookup requests.
     */
    private long nextUID = 1;

    /**
     * Client connected to the GNS service.
     */
    private Client client;

    private RelativeTime reconnectBackoff = RelativeTime.STD_BACKOFF;


    public class GNSMessageReceiver extends RunaboutMessageReceiver {
        public void visit(ClientLookupResultMessage m) {
            RequestIdentifier<FixedMessageRequest<LookupResultProcessor>> r = lookupRequests.getRequestIdentifier(m.id);
            if (null == r) {
                logger.warn("no matching getRequestIdentifier for lookup result");
                return;
            }
            r.getRequest().getContext().process(m.records);
        }

        @Override
        public void handleError() {
            logger.warn("Error receiving from GNS service, reconnecting.");
            Scheduler.addDelayed(reconnectBackoff, new Scheduler.Task() {
                @Override
                public void run(Scheduler.RunContext ctx) {
                    client.reconnect();
                    // re-send active requests
                    lookupRequests.restart();
                }
            });
            reconnectBackoff = reconnectBackoff.backoff();
        }
    }

    /**
     * Connect to the GNS service
     *
     * @param cfg configuration to use
     */
    public Gns(Configuration cfg) {
        client = new Client("gns", cfg);
        lookupRequests = new MatchingRequestContainer<Long, FixedMessageRequest<LookupResultProcessor>>(client);
        client.installReceiver(new GNSMessageReceiver());
    }

    /**
     * Perform an asynchronous lookup operation on the GNS.
     *
     * @param name the name to look up
     * @param zone zone to look in
     * @param type the GNS record type to look for
     * @param lookupOption a Gns.LOOKUP_OPTION_* value
     * @param shortenZoneKey the private key of the shorten zone (can be NULL);
     *                    specify to enable automatic shortening (given a PSEU
     *                    record, if a given pseudonym is not yet used in the
     *                    shorten zone, we automatically add the respective zone
     *                    under that name)
     * @param proc function to call on result
     * @return handle to the queued getRequestIdentifier
     */
    public Cancelable lookup(String name,
                             EcdsaPublicKey zone,
                             long type, int lookupOption,
                             EcdsaPrivateKey shortenZoneKey,
                             LookupResultProcessor proc) {
        ClientLookupMessage m = new ClientLookupMessage();

        if (null != shortenZoneKey) {
            m.haveKey = 1;
            m.shortenKey = shortenZoneKey;
        } else {
            m.haveKey = 0;
            m.shortenKey = EcdsaPrivateKey.zeroKey();
        }
        m.id = nextUID++;
        m.name = name;
        m.onlyCached = lookupOption;
        m.type = type;
        m.zone = zone;


        return lookupRequests.addRequest(m.id, new FixedMessageRequest<LookupResultProcessor>(m, proc));
    }


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