/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements. See the NOTICE file distributed with this
 * work for additional information regarding copyright ownership. The ASF
 * licenses this file to You 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 org.apache.hugegraph.job.algorithm.cent;

import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import org.apache.hugegraph.backend.id.Id;
import org.apache.tinkerpop.gremlin.structure.Edge;
import org.apache.tinkerpop.gremlin.structure.Vertex;

import org.apache.hugegraph.job.UserJob;
import org.apache.hugegraph.structure.HugeEdge;
import org.apache.hugegraph.traversal.algorithm.steps.EdgeStep;
import org.apache.hugegraph.type.define.Directions;

public class DegreeCentralityAlgorithm extends AbstractCentAlgorithm {

    @Override
    public String name() {
        return "degree_centrality";
    }

    @Override
    public void checkParameters(Map<String, Object> parameters) {
        direction(parameters);
        edgeLabel(parameters);
        top(parameters);
    }

    @Override
    public Object call(UserJob<Object> job, Map<String, Object> parameters) {
        try (Traverser traverser = new Traverser(job)) {
            return traverser.degreeCentrality(direction(parameters),
                                              edgeLabel(parameters),
                                              top(parameters));
        }
    }

    private static class Traverser extends AlgoTraverser {

        public Traverser(UserJob<Object> job) {
            super(job);
        }

        public Object degreeCentrality(Directions direction,
                                       String label,
                                       long topN) {
            if (direction == null || direction == Directions.BOTH) {
                return this.degreeCentralityForBothDir(label, topN);
            }
            assert direction == Directions.OUT || direction == Directions.IN;
            assert topN >= 0L || topN == NO_LIMIT;

            Iterator<Edge> edges = this.edges(direction);

            JsonMap degrees = new JsonMap();
            TopMap<Id> tops = new TopMap<>(topN);
            Id vertex = null;
            Id labelId = this.getEdgeLabelId(label);
            long degree = 0L;
            long totalEdges = 0L;

            degrees.startObject();
            while (edges.hasNext()) {
                HugeEdge edge = (HugeEdge) edges.next();
                this.updateProgress(++totalEdges);

                Id schemaLabel = edge.schemaLabel().id();
                if (labelId != null && !labelId.equals(schemaLabel)) {
                    continue;
                }

                Id source = edge.ownerVertex().id();
                if (source.equals(vertex)) {
                    // edges belong to same source vertex
                    degree++;
                    continue;
                }

                if (vertex != null) {
                    // next vertex found
                    if (topN <= 0L && topN != NO_LIMIT) {
                        degrees.append(vertex, degree);
                    } else {
                        tops.put(vertex, degree);
                    }
                }
                vertex = source;
                degree = 1L;
            }

            if (vertex != null) {
                if (topN <= 0L && topN != NO_LIMIT) {
                    degrees.append(vertex, degree);
                } else {
                    tops.put(vertex, degree);
                    degrees.append(tops.entrySet());
                }
            }

            degrees.endObject();

            return degrees.asJson();
        }

        protected Object degreeCentralityForBothDir(String label, long topN) {
            assert topN >= 0L || topN == NO_LIMIT;
            long totalVertices = 0L;
            JsonMap degrees = new JsonMap();
            TopMap<Id> tops = new TopMap<>(topN);

            Iterator<Vertex> vertices = this.vertices();

            degrees.startObject();
            while (vertices.hasNext()) {
                Id source = (Id) vertices.next().id();
                this.updateProgress(++totalVertices);

                long degree = this.degree(source, label);
                if (degree > 0L) {
                    if (topN <= 0L && topN != NO_LIMIT) {
                        degrees.append(source, degree);
                    } else {
                        tops.put(source, degree);
                    }
                }
            }

            if (tops.size() > 0) {
                degrees.append(tops.entrySet());
            }
            degrees.endObject();

            return degrees.asJson();
        }

        private long degree(Id source, String label) {
            List<String> labels = label == null ? null : Collections.singletonList(label);
            EdgeStep step = new EdgeStep(this.graph(), Directions.BOTH,
                                         labels, null, NO_LIMIT, 0);
            return this.edgesCount(source, step);
        }
    }
}
