/**
 * Copyright (C) 2014 The SciGraph authors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.scigraph.owlapi;

import io.scigraph.frames.CommonProperties;
import io.scigraph.frames.Concept;
import io.scigraph.neo4j.GraphUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.logging.Logger;

import javax.inject.Named;

import org.neo4j.graphdb.Direction;
import org.neo4j.graphdb.DynamicLabel;
import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.GraphDatabaseService;
import org.neo4j.graphdb.Label;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.Path;
import org.neo4j.graphdb.Relationship;
import org.neo4j.graphdb.RelationshipType;
import org.neo4j.graphdb.Result;
import org.neo4j.graphdb.Transaction;
import org.neo4j.graphdb.index.ReadableIndex;
import org.neo4j.graphdb.traversal.Uniqueness;

public class OwlPostprocessor {

  private static final Logger logger = Logger.getLogger(OwlPostprocessor.class.getName());

  private final GraphDatabaseService graphDb;

  private final Map<String, String> categoryMap;

  public OwlPostprocessor(GraphDatabaseService graphDb, @Named("owl.categories") Map<String, String> categoryMap) {
    this.graphDb = graphDb;
    this.categoryMap = categoryMap;
  }

  public void postprocess() throws InterruptedException, ExecutionException {
    processSomeValuesFrom();
    processCategories(categoryMap);
  }

  public void processSomeValuesFrom() {
    logger.info("Processing someValuesFrom classes");
    try (Transaction tx = graphDb.beginTx()) {
      Result results = graphDb.execute("MATCH (n)-[relationship]->(svf:someValuesFrom)-[:property]->(p) " + "RETURN n, relationship, svf, p");
      while (results.hasNext()) {
        Map<String, Object> result = results.next();
        Node subject = (Node) result.get("n");
        Relationship relationship = (Relationship) result.get("relationship");
        Node svf = (Node) result.get("svf");
        Node property = (Node) result.get("p");
        for (Relationship r : svf.getRelationships(OwlRelationships.FILLER)) {
          Node object = r.getEndNode();
          String relationshipName = GraphUtil.getProperty(property, CommonProperties.IRI, String.class).get();
          RelationshipType type = DynamicRelationshipType.withName(relationshipName);
          String propertyUri = GraphUtil.getProperty(property, CommonProperties.IRI, String.class).get();
          Relationship inferred = subject.createRelationshipTo(object, type);
          inferred.setProperty(CommonProperties.IRI, propertyUri);
          inferred.setProperty(CommonProperties.CONVENIENCE, true);
          inferred.setProperty(CommonProperties.OWL_TYPE, relationship.getType().name());
        }
      }
      tx.success();
    }
  }

  long processCategory(Node root, RelationshipType type, Direction direction, String category) {
    long count = 0;
    int batchSize = 100_000;
    Label label = DynamicLabel.label(category);
    Transaction tx = graphDb.beginTx();
    for (Path position : graphDb.traversalDescription().uniqueness(Uniqueness.NODE_GLOBAL).depthFirst().relationships(type, direction)
        .relationships(OwlRelationships.RDF_TYPE, Direction.INCOMING).relationships(OwlRelationships.OWL_EQUIVALENT_CLASS, Direction.BOTH)
        .traverse(root)) {
      Node end = position.endNode();
      GraphUtil.addProperty(end, Concept.CATEGORY, category);
      end.addLabel(label);
      if (0 == ++count % batchSize) {
        logger.fine("Commiting " + count);
        tx.success();
        tx.close();
        tx = graphDb.beginTx();
      }
    }
    tx.success();
    tx.close();
    return count;
  }

  // public void processCategories(Map<String, String> categories) {
  // logger.info("Processing categories");
  // for (Entry<String, String> category : categories.entrySet()) {
  // Set<Node> roots = new HashSet<>();
  // try (Transaction tx = graphDb.beginTx()) {
  // ReadableIndex<Node> nodeIndex = graphDb.index().getNodeAutoIndexer().getAutoIndex();
  // Node root = nodeIndex.get(CommonProperties.IRI, category.getKey()).getSingle();
  // if (null == root) {
  // logger.warning("Failed to locate " + category.getKey() + " while processing categories");
  // continue;
  // }
  // roots.add(root);
  // for (Relationship equiv: root.getRelationships(OwlRelationships.OWL_EQUIVALENT_CLASS)) {
  // roots.add(equiv.getOtherNode(root));
  // }
  // tx.success();
  // }
  // if (roots.isEmpty()) {
  // logger.warning("Failed to locate " + category.getKey() + " while processing categories");
  // } else {
  // for (Node root: roots) {
  // logger.info("Processing category: " + category);
  // long count = processCategory(root, OwlRelationships.RDFS_SUBCLASS_OF, Direction.INCOMING,
  // category.getValue());
  // logger.info("Processsed " + count + " nodes for " + category);
  // }
  // }
  // }
  // }

  public void processCategories(Map<String, String> categories) throws InterruptedException, ExecutionException {
    logger.info("Processing categories");
    final ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    List<Future<Long>> contentsFutures = new ArrayList<>();

    Transaction tx = graphDb.beginTx();
    for (Entry<String, String> category : categories.entrySet()) {
      ReadableIndex<Node> nodeIndex = graphDb.index().getNodeAutoIndexer().getAutoIndex();
      Node root = nodeIndex.get(CommonProperties.IRI, category.getKey()).getSingle();
      final Future<Long> contentFuture = pool.submit(new CategoryProcessor(graphDb, root, category.getValue()));
      contentsFutures.add(contentFuture);
    }

    for (Future<Long> contentFuture : contentsFutures) {
      final Long content = contentFuture.get();
      // ...process contents
      logger.fine("Processed: " + content);
    }
    tx.success();
  }
}

@@ -4405,1224 +4405,8 @@
  %7D%0A%0A
-  // public void processCategories(Map%3CString, String%3E categories) %7B%0A  // logger.info(%22Processing categories%22);%0A  // for (Entry%3CString, String%3E category : categories.entrySet()) %7B%0A  // Set%3CNode%3E roots = new HashSet%3C%3E();%0A  // try (Transaction tx = graphDb.beginTx()) %7B%0A  // ReadableIndex%3CNode%3E nodeIndex = graphDb.index().getNodeAutoIndexer().getAutoIndex();%0A  // Node root = nodeIndex.get(CommonProperties.IRI, category.getKey()).getSingle();%0A  // if (null == root) %7B%0A  // logger.warning(%22Failed to locate %22 + category.getKey() + %22 while processing categories%22);%0A  // continue;%0A  // %7D%0A  // roots.add(root);%0A  // for (Relationship equiv: root.getRelationships(OwlRelationships.OWL_EQUIVALENT_CLASS)) %7B%0A  // roots.add(equiv.getOtherNode(root));%0A  // %7D%0A  // tx.success();%0A  // %7D%0A  // if (roots.isEmpty()) %7B%0A  // logger.warning(%22Failed to locate %22 + category.getKey() + %22 while processing categories%22);%0A  // %7D else %7B%0A  // for (Node root: roots) %7B%0A  // logger.info(%22Processing category: %22 + category);%0A  // long count = processCategory(root, OwlRelationships.RDFS_SUBCLASS_OF, Direction.INCOMING,%0A  // category.getValue());%0A  // logger.info(%22Processsed %22 + count + %22 nodes for %22 + category);%0A  // %7D%0A  // %7D%0A  // %7D%0A  // %7D%0A%0A
   pu
@@ -5280,37 +5280,8 @@
 ();%0A
-      // ...process contents%0A
     

clean up