/**
 * 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.HashMap;
import java.util.LinkedList;
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.concurrent.TimeUnit;
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();
      tx.close();
    }
  }

  long processCategory(Node root, RelationshipType type, Direction direction, String category) {
    long count = 0;
    int batchSize = 100;
    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) throws InterruptedException, ExecutionException {
    logger.info("Processing categories");
    final ExecutorService pool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    List<Future<Map<String, List<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<Map<String, List<Long>>> contentFuture = pool.submit(new CategoryProcessor(graphDb, root, category.getValue()));
      contentsFutures.add(contentFuture);
    }
    Map<String, List<Long>> toTag = new HashMap<String, List<Long>>();
    for (Future<Map<String, List<Long>>> contentFuture : contentsFutures) {
      final Map<String, List<Long>> resolved = contentFuture.get();
      toTag.putAll(resolved);
    }

    pool.shutdown();
    pool.awaitTermination(10, TimeUnit.DAYS);
    tx.success();
    tx.close();

    tx = graphDb.beginTx();
    for (Entry<String, List<Long>> t : toTag.entrySet()) {
      String category = t.getKey();
      logger.info("Tagging " + t.getValue().size() + " for " + category);
      final ExecutorService taggingPool = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
      List<Future<Boolean>> taggedFutures = new ArrayList<>();

      int partitionSize = 1000;
      List<List<Long>> partitions = new LinkedList<List<Long>>();
      for (int i = 0; i < t.getValue().size(); i += partitionSize) {
        partitions.add(t.getValue().subList(i, Math.min(i + partitionSize, t.getValue().size())));
      }

      for (List<Long> ids : partitions) {
        final Future<Boolean> contentFuture = taggingPool.submit(new CategoryLabeler(graphDb, ids, category));
        taggedFutures.add(contentFuture);
      }

      for (Future<Boolean> taggedFuture : taggedFutures) {
        taggedFuture.get();
      }
      taggingPool.shutdown();
      taggingPool.awaitTermination(10, TimeUnit.DAYS);
    }
    tx.success();
    tx.close();
  }
}

@@ -5526,24 +5526,292 @@
 ture.get();%0A
+%0A      String key = resolved.keySet().iterator().next();%0A      if (toTag.containsKey(key)) %7B // in case of many IRIs map to the same category%0A        List%3CLong%3E acc = toTag.get(key);%0A        acc.addAll(resolved.get(key));%0A        toTag.put(key, acc);%0A      %7D else %7B%0A  
       toTag.
@@ -5828,16 +5828,24 @@
 olved);%0A
+      %7D%0A
     %7D%0A%0A 

fix bug in category