/******************************************************************************
 * $Id$
 *
 * Project:  MapServer
 * Purpose:  Functions for basic undirected, weighted graph support.
 * Author:   Steve Lime and the MapServer team.
 *
 ******************************************************************************
 * Copyright (c) 1996-2005 Regents of the University of Minnesota.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies of this Software or works derived from this Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 ****************************************************************************/

#include "mapserver.h"
#include "mapgraph.h"
#include <algorithm> // for std::swap

graphObj *msCreateGraph(signed int numnodes)
{
  graphObj *graph=nullptr;

  if(numnodes <= 0) return nullptr;

  graph = (graphObj *) malloc(sizeof(graphObj));
  if(!graph) return nullptr;

  graph->head = (graphNodeObj **) calloc(numnodes, sizeof(graphNodeObj*));
  if(!graph->head) {
    free(graph);
    return nullptr;
  }
  graph->numnodes = numnodes;

  return graph;
}

void msFreeGraph(graphObj *graph)
{
  if(!graph) return;

  graphNodeObj *tmp=nullptr;
  
  for(int i=0; i<graph->numnodes; i++) {
    while(graph->head[i] != nullptr) {
      tmp = graph->head[i];
      graph->head[i] = graph->head[i]->next;
      free(tmp);
    }
  }

  free(graph->head);
  free(graph);
}

int msGraphAddEdge(graphObj *graph, int src, int dest, double weight)
{
  graphNodeObj *node=nullptr;

  if(!graph) return MS_FAILURE;

  // src -> dest
  node = (graphNodeObj *) malloc(sizeof(graphNodeObj));
  if(!node) return MS_FAILURE;

  node->dest = dest;
  node->weight = weight;
  node->next = graph->head[src];
  graph->head[src] = node;

  // dest -> src
  node = (graphNodeObj *) malloc(sizeof(graphNodeObj));
  if(!node) return MS_FAILURE;

  node->dest = src;
  node->weight = weight;
  node->next = graph->head[dest];
  graph->head[dest] = node;

  return MS_SUCCESS;
}

void msPrintGraph(graphObj *graph)
{
  int i;

  if(!graph) return;

  for(i=0; i<graph->numnodes; i++) {
    graphNodeObj *node = graph->head[i];
    if(node != nullptr) {
      do {
        msDebug("%d -> %d (%.6f)\t", i, node->dest, node->weight);
        node = node->next;
      } while (node != nullptr);
      msDebug("\n");
    }
  }
}

/*
** Derived from an number web resources including:
**
**   https://www.geeksforgeeks.org/dijkstras-algorithm-for-adjacency-list-representation-greedy-algo-8/
**   https://youtube.com/watch?v=pSqmAO-m7Lk
**   https://en.wikipedia.org/wiki/Dijkstra%27s_algorithm
**
** Much of the bulk here is for min-heap (key,value) management.
*/

typedef struct {
  int idx;
  double dist;
} minHeapNodeObj;

typedef struct {
  int size;
  int capacity;
  int *pos;
  minHeapNodeObj **nodes;
} minHeapObj;

static minHeapNodeObj *newMinHeapNode(int idx, double dist)
{
  minHeapNodeObj *node = (minHeapNodeObj *) malloc(sizeof(minHeapNodeObj));
  if(!node) return nullptr;
  node->idx = idx;
  node->dist = dist;
  return node;
}

static void freeMinHeap(minHeapObj *minHeap)
{
  if(!minHeap) return;

  free(minHeap->pos);
  for(int i=0; i<minHeap->size; i++) {
    free(minHeap->nodes[i]);
  }
  free(minHeap->nodes);
  free(minHeap);
}

static minHeapObj *createMinHeap(signed int capacity)
{
  minHeapObj *minHeap = (minHeapObj *) malloc(sizeof(minHeapObj));
  if(!minHeap) return nullptr;

  minHeap->pos = (int *) malloc(capacity * sizeof(int));
  if(!minHeap->pos) {
    free(minHeap);
    return nullptr;
  }
  minHeap->size = 0;
  minHeap->capacity = capacity;
  minHeap->nodes = (minHeapNodeObj **) malloc(capacity * sizeof(minHeapNodeObj *));
  if(!minHeap->nodes) {
    free(minHeap->pos);
    free(minHeap);
    return nullptr;
  }
  return minHeap;
}

static void minHeapify(minHeapObj *minHeap, int idx)
{
  int smallest = idx;
  const int left = 2*idx + 1;
  const int right = 2*idx + 2;
 
  if (left < minHeap->size && minHeap->nodes[left]->dist < minHeap->nodes[smallest]->dist)
    smallest = left;
 
  if (right < minHeap->size && minHeap->nodes[right]->dist < minHeap->nodes[smallest]->dist)
    smallest = right;
 
  if (smallest != idx) {
    minHeapNodeObj *smallestNode = minHeap->nodes[smallest];
    minHeapNodeObj *idxNode = minHeap->nodes[idx];
 
    minHeap->pos[smallestNode->idx] = idx; // swap positions
    minHeap->pos[idxNode->idx] = smallest;
 
    std::swap(minHeap->nodes[smallest], minHeap->nodes[idx]); // swap nodes
    minHeapify(minHeap, smallest);
  }
}

static bool isEmpty(const minHeapObj *minHeap)
{
  return minHeap->size == 0;
}

static minHeapNodeObj *extractMin(minHeapObj *minHeap)
{
  if (isEmpty(minHeap)) return nullptr;
 
  // store root node
  minHeapNodeObj *root = minHeap->nodes[0];
 
  // replace root node with last node
  minHeapNodeObj *lastNode = minHeap->nodes[minHeap->size - 1];
  minHeap->nodes[0] = lastNode;
 
  // update position of last node
  minHeap->pos[root->idx] = minHeap->size - 1;
  minHeap->pos[lastNode->idx] = 0;
 
  // Reduce heap size and heapify root
  --minHeap->size;
  minHeapify(minHeap, 0);
 
  return root;
}

static void decreaseKey(minHeapObj *minHeap, int idx, int dist)
{
  // get the index of idx in min heap nodes
  int i = minHeap->pos[idx];
 
  // get the node and update its dist value
  minHeap->nodes[i]->dist = dist;
 
  // travel up while the complete tree is not hepified (this is a O(Logn) loop)
  while (i && minHeap->nodes[i]->dist < minHeap->nodes[(i - 1) / 2]->dist) {
    // swap this node with its parent
    minHeap->pos[minHeap->nodes[i]->idx] = (i-1)/2;
    minHeap->pos[minHeap->nodes[(i-1)/2]->idx] = i;
    std::swap(minHeap->nodes[i], minHeap->nodes[(i - 1) / 2]);
 
    // move to parent index
    i = (i - 1) / 2;
  }
}

static bool isInMinHeap(const minHeapObj *minHeap, int idx)
{
  return minHeap->pos[idx] < minHeap->size;
}

typedef struct {
  double *dist;
  int *prev;
} dijkstraOutputObj;

static dijkstraOutputObj *dijkstra(graphObj *graph, int src)
{
  int n = graph->numnodes;

  minHeapObj *minHeap = createMinHeap(n); // priority queue implemented as a min heap structure
  if(!minHeap) return nullptr;

  dijkstraOutputObj *output = nullptr;
  output = (dijkstraOutputObj *) malloc(sizeof(dijkstraOutputObj));
  output->dist = (double *) malloc(n * sizeof(double));
  output->prev = (int *) malloc(n * sizeof(int));
  if(!output->dist || !output->prev) {
    msFree(output->dist);
    msFree(output->prev);
    free(output);
    freeMinHeap(minHeap);
    return nullptr;
  }

  // initialize
  for (int i=0; i<n; i++) {
    output->dist[i] = HUGE_VAL;
    output->prev[i] = -1;
    minHeap->nodes[i] = newMinHeapNode(i, output->dist[i]); // allocate a min heap node for each graph node
    minHeap->pos[i] = i;
  }
 
  // make dist value of src vertex as 0 so that it is extracted first
  minHeap->pos[src] = src;
  output->dist[src] = 0;
  decreaseKey(minHeap, src, output->dist[src]);
 
  // initially size of min heap is equal to graph->numnodes (n)
  minHeap->size = n;
 
  // In the following loop, minHeap contains all nodes
  // whose shortest distance is not yet finalized.
  while (!isEmpty(minHeap)) {

    // extract the vertex with minimum distance value and store the node index
    minHeapNodeObj *minHeapNode = extractMin(minHeap);
    int u = minHeapNode->idx;
    free(minHeapNode); // done with this node

    // traverse through all adjacent nodes of u and update their distance values
    graphNodeObj *node = graph->head[u];
    while (node != nullptr) {
      int v = node->dest;
 
      // if shortest distance to v is not finalized yet, and distance to v
      // through u is less than its previously calculated distance
      if (isInMinHeap(minHeap, v) && output->dist[u] != HUGE_VAL && node->weight + output->dist[u] < output->dist[v]) {
        output->dist[v] = output->dist[u] + node->weight;
        output->prev[v] = u;
        decreaseKey(minHeap, v, output->dist[v]);
      }
      node = node->next;
    }
  }
 
  freeMinHeap(minHeap);

  return output;
}

int *msGraphGetLongestShortestPath(graphObj *graph, int src, int *path_size, double *path_dist)
{
  if(!graph || src < 0 || src > graph->numnodes) return nullptr;

  int* path = (int *) malloc((graph->numnodes)*sizeof(int)); // worst case is path traverses all nodes
  if(!path) return nullptr;

  dijkstraOutputObj *output = dijkstra(graph, src);
  if(!output) {
    free(path);
    return nullptr; // algorithm failed for some reason
  }

  // get longest shortest distance from src to another node (our dest)
  *path_dist = -1;
  int dest = -1;
  for(int i=0; i<graph->numnodes; i++) {
    if(output->dist[i] != HUGE_VAL && *path_dist < output->dist[i]) {
      *path_dist = output->dist[i];
      dest = i;
    }
  }

  if(dest == -1) { // unable to determine destination node
    free(path);
    free(output->dist);
    free(output->prev);
    free(output);
    return nullptr;
  }

  // construct the path from src to dest
  int j = 0;
  for(int i=dest; i!=-1; i=output->prev[i],j++) path[j] = i;
  std::reverse(path, path + j);
  *path_size = j;

  // clean up dijkstra output
  free(output->dist);
  free(output->prev);
  free(output);

  return path;
}
