package edu.mit.drkp;


import com.limegroup.gnutella.BrowseHostHandler;
import com.limegroup.gnutella.Endpoint;
import com.limegroup.gnutella.GUID;
import com.limegroup.gnutella.RemoteFileDesc;
import com.limegroup.gnutella.RouterService;
import com.limegroup.gnutella.search.HostData;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Timer;
import java.util.TimerTask;
import org.limewire.io.IpPort;
import org.limewire.io.IpPortImpl;

public class GnutellaHooks
{
    private static final int QUERY_LIFETIME_MS = 5 * 60 * 100; // 5 min
    private static final int MAX_QUERIES = 50000;
    private static final int MAX_RESULTS = 50000;
    
    private static GnutellaHooks INSTANCE;
    private List<String> queries;
    private List<Map<String, Object>> queryResults;
    private Timer timer = new Timer();
    
    private GnutellaHooks ()
    {
        initQueries();
        initResults();
        
        timer.schedule(new TimerTask() {
                public void run()
                {
                    System.out.println("queries: " + Integer.toString(queries.size()));
                    System.out.println("results: " + Integer.toString(queryResults.size()));
                }
            }, 0, 1000);
    }

    public static GnutellaHooks getHooks()
    {
        if (INSTANCE == null) {
            INSTANCE = new GnutellaHooks();
        }

        return INSTANCE;
    }

    private void initQueries()
    {
        queries = Collections.synchronizedList(new ArrayList<String>());
    }

    private void initResults()
    {
        queryResults = Collections.
            synchronizedList(new ArrayList<Map<String, Object>>());
        
    }

    public void handleQuery(String q)
    {
        queries.add(q);

        if (queries.size() > MAX_QUERIES) {
            initQueries();
        }

    }

    public void handleQueryResult(RemoteFileDesc rfd,
                                  HostData data,
                                  Set<Endpoint> locs)
    {
        Map<String, Object> result = new HashMap<String, Object>();
        result.put("host", rfd.getHost());
        result.put("port", new Integer(rfd.getPort()));
        result.put("client-guid", rfd.getClientGUID());
        //        result.put("creation-time", new Long(rfd.getCreationTime()));
        result.put("name", rfd.getFileName());
        result.put("size", new Integer(rfd.getSize()));
        result.put("browse-host-enabled", rfd.browseHostEnabled());
        result.put("is-reply-to-multicast", rfd.isReplyToMulticast());
        result.put("supports-fw-transfer", rfd.supportsFWTransfer());
        result.put("is-firewalled", rfd.isFirewalled());

        
        
        List<String> pushProxies = new ArrayList<String>();
        for (IpPort ipp : rfd.getPushProxies()) {
            String s = ipp.getAddress() + ":" +
                Integer.toString(ipp.getPort());
            pushProxies.add(s);
        }
        result.put("push-proxies", pushProxies);
        
        queryResults.add(result);

        if (queryResults.size() > MAX_RESULTS) {
            initResults();
        }

    }
    
    public List<String> getAndClearQueries()
    {
        List<String> x = queries;
        initQueries();
        return x;
    }
    
    public List<Map<String, Object>> getAndClearQueryResults()
    {
        List<Map<String, Object>> x = queryResults;
        initResults();
        return x;
    }

    public GUID performQuery(String q)
    {
        byte [] guidBytes = RouterService.newQueryGUID();
        final GUID guid = new GUID(guidBytes);
        RouterService.query(guidBytes, q);

        timer.schedule(new TimerTask() {
                public void run()
                {
                    RouterService.stopQuery(guid);
                }
            }, QUERY_LIFETIME_MS);
        return guid;
    }

    public GUID browseHost(Map<String, Object> rfdMap)
    {
        // Decode the arguments
        boolean browseHostEnabled =
            (Boolean) rfdMap.get("browse-host-enabled");
        if (!browseHostEnabled) {
            return null;
        }
        String host = (String) rfdMap.get("host");
        int port = (Integer) rfdMap.get("port");
        byte [] clientGuid = (byte []) rfdMap.get("client-guid");
        String name = (String) rfdMap.get("name");
        int size = (Integer) rfdMap.get("size");
        boolean isReplyToMulticast =
            (Boolean) rfdMap.get("is-reply-to-multicast");
        boolean supportsFWTransfer =
            (Boolean) rfdMap.get("supports-fw-transfer");
        Set<IpPort> pushProxies = new HashSet<IpPort>();


        Object [] pushProxiesArray = (Object []) rfdMap.get("push-proxies");

        for (int i = 0; i < pushProxiesArray.length; i++) {
            try {
                String s = (String) pushProxiesArray[i];
                pushProxies.add(new IpPortImpl(s));        
            } catch (UnknownHostException e) {
                throw new RuntimeException("Can't resolve host", e);
            }

        }
        
         Set<IpPort> proxies = isReplyToMulticast ?
             IpPort.EMPTY_SET : pushProxies;
        GUID serventID = new GUID(clientGuid);

        GUID browseGuid = new GUID(GUID.makeGuid());
        BrowseHostHandler bhh =
            RouterService.doAsynchronousBrowseHost(host, port,
                                                   browseGuid,
                                                   serventID, proxies,
                                                   supportsFWTransfer);
        return browseGuid;
    }
    
}
