package com.bigdata.gom.samples;

import java.util.LinkedHashSet;
import java.util.Set;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.log4j.Logger;
import org.eclipse.jetty.client.HttpClient;
import org.openrdf.model.Literal;
import org.openrdf.model.Resource;
import org.openrdf.model.URI;
import org.openrdf.model.Value;
import org.openrdf.model.vocabulary.RDFS;
import org.openrdf.query.BindingSet;

import com.bigdata.BigdataStatics;
import com.bigdata.gom.gpo.IGPO;
import com.bigdata.gom.gpo.ILinkSet;
import com.bigdata.gom.om.IObjectManager;
import com.bigdata.gom.om.NanoSparqlObjectManager;
import com.bigdata.rdf.model.BigdataURI;
import com.bigdata.rdf.sail.webapp.client.AutoCloseHttpClient;
import com.bigdata.rdf.sail.webapp.client.HttpClientConfigurator;
import com.bigdata.rdf.sail.webapp.client.RemoteRepositoryManager;
import com.bigdata.util.httpd.Config;

import cutthecrap.utils.striterators.ICloseableIterator;

/**
 * Simple graph program identifies friends of friends that are not directly
 * connected using a SPARQL query and then constructs an in-memory graph
 * consisting of those vertices and using the connection count as the link
 * weights connecting the vertices. The graph is symmetric.
 * <p>
 * To get started, start the NanoSparqlServer. Then LOAD the data set into the
 * server using SPARQL UPDATE (you will have to use the path the file on your
 * local machine).
 * 
 * <pre>
 * LOAD  <file:///Users/bryan/Documents/workspace/BIGDATA_RELEASE_1_2_0/bigdata-gom/src/samples/com/bigdata/gom/samples/example2.trig>
 * </pre>
 * 
 * You can then run this example, and it will construct the graph. You can load
 * a different data set if you want to test this out on your own data.
 * <p>
 * A real application could use the skin pattern to provide application specific
 * interfaces that simplify access to the vertex and link attributes of
 * interest.
 * 
 * @author <a href="mailto:[email protected]">Bryan Thompson</a>
 */
public class Example2 implements Callable<Void> {

    private static final transient Logger log = Logger
            .getLogger(Example2.class);

    private final IObjectManager om;
    
    public Example2(final IObjectManager om) {
        
        this.om = om;
        
    }

    /**
     * Return the string value of the property iff it exists and otherwise
     * <code>null</code>.
     * 
     * @param gpo
     *            The object.
     * @param property
     *            The property.
     *            
     * @return The string value -or- <code>null</code> if the property is not
     *         bound to a {@link Literal}.
     */
    static private String getStr(final IGPO gpo, final URI property) {

        final Value val = gpo.getValue(RDFS.LABEL);

        if (val instanceof Literal) {

            return ((Literal) val).getLabel();

        }

        return null;

    }

    /**
     * Return the link weight between two friends of friends.
     * 
     * @param gpo
     *            The link source.
     * @param connectTo
     *            The link property.
     * @param friendOfAFriend
     *            The link target.
     * @param weightProperty
     *            The link weight property.
     * 
     * @return The link weight -or- <code>null</code> if the link either does
     *         not exist or does not have the link weight property.
     */
    static private Integer getLinkWeight(final IGPO gpo, final URI connectTo,
            final IGPO friendOfAFriend, final URI weightProperty) {

        final IGPO link = gpo.getLink(connectTo, friendOfAFriend);

        if (link == null) {

            log.warn("No such link: source=" + gpo + ", linkType=" + connectTo
                    + ", target=" + friendOfAFriend);

            return null;

        }

        final Value linkWeight = link.getValue(weightProperty);

        if (linkWeight == null) {

            log.warn("No such link attribute: source=" + gpo + ", linkType="
                    + connectTo + ", target=" + friendOfAFriend
                    + ", linkAttribute=" + linkWeight);

            return null;

        }

        return ((Literal) linkWeight).intValue();

    }
    
    public Void call() throws Exception {

        final long begin = System.currentTimeMillis();
        
        /*
         * URI used to recommend possible connections.
         */
        final BigdataURI connectTo = om.getValueFactory().createURI(
                "http://example.org/connectTo");

        /*
         * URI used for the connection count (link weight).
         */
        final BigdataURI weightProperty = om.getValueFactory().createURI(
                "http://example.org/connectionCount");

        final ICloseableIterator<BindingSet> itr = om
                .evaluate("PREFIX foaf: <http://xmlns.com/foaf/0.1/> \n" + //
                        "SELECT ?x ?z (count(?y) as ?connectionCount) \n"+
                        "       (sample(?xname2) as ?xname) \n" + //
                        "       (sample(?zname2) as ?zname) \n" + //
                        "WHERE {\n" + //
                        "  ?x foaf:knows ?y . \n" + //
                        "  ?y foaf:knows ?z . \n" + //
                        "  FILTER NOT EXISTS { ?x foaf:knows ?z } . \n" + //
                        "  FILTER ( !sameTerm(?x,?z)) . \n"+//
                        "  OPTIONAL { ?x rdfs:label ?xname2 } .\n"+//
                        "  OPTIONAL { ?z rdfs:label ?zname2 } .\n"+//
                        "} \n" + //
                        "GROUP BY ?x ?z \n"//
                        /*
                         * Optionally, only see friends of a friend with more
                         * than one indirect connection.
                         */
//                        +"HAVING (?connectionCount > 1)\n"//
                );

        /*
         * Convert solutions into a graph, collecting the vertices of interest
         * for that graph.
         */
        // the [x] bindings.
        final Set<IGPO> roots = new LinkedHashSet<IGPO>();
        final Set<IGPO> all = new LinkedHashSet<IGPO>();
        try {

            while (itr.hasNext()) {

                final BindingSet bset = itr.next();

//                System.out.println(bset.toString());
                
                final Value xval = bset.getValue("x");
                
                final IGPO x = om.getGPO((Resource) xval);

                final Value zval = bset.getValue("z");
                if (!(zval instanceof Resource)) {
                    /*
                     * Bad FOAF data. The target should be a Resource, not a
                     * Literal.
                     */
                    log.warn("target of foaf:knows is not a Resource: source="
                            + xval + ", target=" + zval);
                    continue;
                }
                final IGPO z = om.getGPO((Resource) zval);

                final Literal connectionCount = (Literal) bset
                        .getValue("connectionCount");

                x.addValue(connectTo, z.getId());

                final IGPO link = x.getLink(connectTo, z);

                link.addValue(weightProperty, connectionCount);

                final Literal xname = (Literal) bset.getValue("xname");

                final Literal zname = (Literal) bset.getValue("zname");

                if (xname != null) {

                    x.setValue(RDFS.LABEL, xname);

                }

                if (zname != null) {

                    z.setValue(RDFS.LABEL, zname);

                }

                roots.add(x);
                all.add(x);
                all.add(z);
                all.add(link);

            }
            
        } finally {
            
            itr.close();
            
        }

        final long elapsed = System.currentTimeMillis() - begin;
        
        System.out.println("Found " + roots.size()
                + " friends having unconnected friends of friends in "
                + elapsed + "ms");

        for (IGPO gpo : roots) {

            final ILinkSet friends = gpo.getLinksOut(connectTo);

            if (!friends.isEmpty()) {

                System.out.println("---------");

                System.out.println("person=" + gpo.getId() + " (name="
                        + getStr(gpo, RDFS.LABEL) + ") has " + friends.size()
                        + " unconnected friends of friends.");

                for (IGPO friendOfAFriend : friends) {

                    final Integer connectionCount = getLinkWeight(gpo,
                            connectTo, friendOfAFriend, weightProperty);

                    System.out.println("   friendOfAFriend: "
                            + friendOfAFriend.getId() + " (name="
                            + getStr(friendOfAFriend, RDFS.LABEL)
                            + "), connectionCount=" + connectionCount);

                }

            }

//            System.out.println(gpo.pp());

        }

        System.out.println("size(x)="+roots.size()+", size(all)="+all.size());
        
        return null;
        
    }
    
    static public void main(final String[] args) throws Exception {

        /**
         * The top-level SPARQL end point for a NanoSparqlServer instance.
         */
        final String serviceURL = "http://localhost:" + Config.BLAZEGRAPH_HTTP_PORT + "/"
                + BigdataStatics.getContextPath();

        /**
         * The namespace of the KB instance that you want to connect to on that
         * server. The default namespace is "kb".
         */
        final String namespace = "kb";
        
        ExecutorService executor = null;

        RemoteRepositoryManager repo = null;

        HttpClient client = null;

        try {

            executor = Executors.newCachedThreadPool();
            
           	client = HttpClientConfigurator.getInstance().newInstance();

            repo = new RemoteRepositoryManager(
            		serviceURL, client, executor);

            final IObjectManager om = new NanoSparqlObjectManager(repo.getRepositoryForDefaultNamespace(),
                    namespace);

            new Example2(om).call();

        } finally {

            if (repo != null) {

            	repo.close();

            }

            if (client != null) {

            	client.stop();

            }

            if (executor != null) {

                executor.shutdownNow();

            }

        }

    }

}