/*
 * Copyright (c) 2005-2017 National Technology & Engineering Solutions
 * of Sandia, LLC (NTESS).  Under the terms of Contract DE-NA0003525 with
 * NTESS, the U.S. Government retains certain rights in this software.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *
 *     * Redistributions in binary form must reproduce the above
 *       copyright notice, this list of conditions and the following
 *       disclaimer in the documentation and/or other materials provided
 *       with the distribution.
 *
 *     * Neither the name of NTESS nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 */

#include "defs.h"
#include "params.h"
#include "smalloc.h"
#include "structs.h"
#include <stdio.h>

void divide(struct vtx_data **graph,         /* graph data structure */
            int               nvtxs,         /* number of vertices in graph */
            int               nedges,        /* number of edges in graph */
            int               using_vwgts,   /* are vertex weights being used? */
            int               using_ewgts,   /* are edge weights being used? */
            double *          vwsqrt,        /* sqrt of vertex weights (length nvtxs+1) */
            int               igeom,         /* geometry dimension for inertial method */
            float **          coords,        /* coordinates for inertial method */
            int *             assignment,    /* set number of each vtx (length n) */
            double *          goal,          /* desired set sizes */
            int               architecture,  /* 0 => hypercube, d => d-dimensional mesh */
            float *           term_wgts[],   /* weights for terminal propagation */
            int               global_method, /* global partitioning algorithm */
            int               local_method,  /* local partitioning algorithm */
            int               rqi_flag,      /* should I use multilevel eigensolver? */
            int               vmax,          /* if so, # vertices to coarsen down to */
            int               ndims,         /* number of eigenvectors */
            double            eigtol,        /* tolerance on eigenvectors */
            int (*hop_mtx)[MAXSETS],         /* between-set hop costs for KL */
            int nsets,                       /* number of sets to partition into */
            int striping                     /* partition by striping into pieces? */
)
{
  extern int           DEBUG_TRACE;        /* trace main execution path? */
  extern int           DEBUG_CONNECTED;    /* debug flag for connected components */
  extern int           DEBUG_KL;           /* debug flag for KL variants */
  extern int           COARSE_NLEVEL_KL;   /* how often to invoke KL while uncoarsening */
  extern int           MAPPING_TYPE;       /* how to map from eigenvectors to partition */
  extern int           LANCZOS_TYPE;       /* which Lanczos variant to use */
  extern int           MAKE_CONNECTED;     /* force connectivity in spectral method? */
  extern double        KL_IMBALANCE;       /* fractional imbalance allowed in KL */
  extern int           VERTEX_SEPARATOR;   /* vertex instead of edge separator? */
  extern int           VERTEX_COVER;       /* form/improve vtx separator via matching? */
  extern int           COARSE_BPM;         /* apply matching/flow while uncoarsening? */
  struct connect_data *cdata;              /* data for enforcing connectivity */
  double *             yvecs[MAXDIMS + 1]; /* space for pointing to eigenvectors */
  double               evals[MAXDIMS + 1]; /* corresponding eigenvalues */
  double               weights[MAXSETS];   /* vertex weight in each set */
  double               maxdeg;             /* maximum weighted degree of a vertex */
  double               total_weight;       /* weight of all vertices */
  double               temp_goal[2];       /* goal to simulate bisection while striping */
  double *             fake_goal;          /* either goal or temp_goal */
  int *                active;             /* keeping track of vtxs in BFS (length nvtxs) */
  int *                bndy_list;          /* list of boundary vtxs */
  int *                null_ptr;           /* argument to klspiff */
  int *                mark;               /* for marking vtxs in BFS (length nvtxs+1) */
  int                  vwgt_max;           /* largest vertex weight */
  int                  max_dev;            /* largest allowed deviation from balance */
  int                  mediantype;         /* how to map from eigenvectors to partition */
  int                  solver_flag;        /* which Lanczos variant to use */
  int                  mkconnected;        /* enforce connectivity in spectral method? */
  int                  i;                  /* loop counters */
  int                  simple_type;        /* which type of simple partitioning to use */
  int                  find_bndy(), find_side_bndy();
  void                 make_connected(), print_connected(), coarsen_kl(), eigensolve();
  void                 coarsen_klv(), klvspiff();
  void                 assign(), make_unconnected(), inertial(), klspiff(), simple_part();
  void                 count_weights(), bpm_improve(), countup_vtx_sep();
  double               find_maxdeg();

  maxdeg = 0;

  if (DEBUG_TRACE > 0) {
    printf("<Entering divide, nvtxs = %d, nedges = %d>\n", nvtxs, nedges);
  }

  if (nvtxs <= 0) {
    return;
  }
  if (nedges == 0 && global_method != 3) {
    global_method = 4;
    local_method  = 2;
  }

  for (i = 0; i < MAXSETS; i++) {
    weights[i] = 0.0;
  }

  bndy_list = NULL;

  if (striping) {
    ndims      = 1;
    mediantype = 4;

    temp_goal[0] = temp_goal[1] = 0;
    for (i = 0; 2 * i + 1 < nsets; i++) {
      temp_goal[0] += goal[i];
      temp_goal[1] += goal[nsets - 1 - i];
    }
    i = nsets / 2;
    if (2 * i != nsets) {
      temp_goal[0] += .5 * goal[i];
      temp_goal[1] += .5 * goal[i];
    }
    fake_goal = temp_goal;
  }
  else {
    mediantype = MAPPING_TYPE;
    fake_goal  = goal;
  }

  if (using_vwgts) {
    vwgt_max = 0;
    for (i = 1; i <= nvtxs; i++) {
      if (graph[i]->vwgt > vwgt_max) {
        vwgt_max = graph[i]->vwgt;
      }
    }
  }
  else {
    vwgt_max = 1;
  }

  /* Perform one of the global partitionings on this sub-graph. */
  if (global_method == 1) { /* Multilevel method. */
    mkconnected = MAKE_CONNECTED;
    solver_flag = LANCZOS_TYPE;

    if (VERTEX_SEPARATOR) {
      coarsen_klv(graph, nvtxs, nedges, using_vwgts, using_ewgts, term_wgts, igeom, coords,
                  vwgt_max, assignment, goal, architecture, hop_mtx, solver_flag, ndims, nsets,
                  vmax, mediantype, mkconnected, eigtol, COARSE_NLEVEL_KL, 0, &bndy_list, weights,
                  FALSE);
      if (VERTEX_COVER && !COARSE_BPM) {
        max_dev      = vwgt_max;
        total_weight = 0;
        for (i = 0; i < nsets; i++) {
          total_weight += goal[i];
        }
        total_weight *= KL_IMBALANCE / nsets;
        if (total_weight > max_dev) {
          max_dev = total_weight;
        }
        bpm_improve(graph, assignment, goal, max_dev, &bndy_list, weights, using_vwgts);
      }
    }
    else {
      coarsen_kl(graph, nvtxs, nedges, using_vwgts, using_ewgts, term_wgts, igeom, coords, vwgt_max,
                 assignment, goal, architecture, hop_mtx, solver_flag, ndims, nsets, vmax,
                 mediantype, mkconnected, eigtol, COARSE_NLEVEL_KL, 0, &bndy_list, weights, FALSE);

      if (VERTEX_COVER) {
        max_dev      = vwgt_max;
        total_weight = 0;
        for (i = 0; i < nsets; i++) {
          total_weight += goal[i];
        }
        total_weight *= KL_IMBALANCE / nsets;
        if (total_weight > max_dev) {
          max_dev = total_weight;
        }
        if (goal[0] - weights[0] <= goal[1] - weights[1]) {
          i = 0;
        }
        else {
          i = 1;
        }
        find_side_bndy(graph, nvtxs, assignment, i, 2, &bndy_list);
        count_weights(graph, nvtxs, assignment, nsets + 1, weights, using_vwgts);
        if (DEBUG_KL > 0) {
          printf("After KL, before bpm_improve\n");
          countup_vtx_sep(graph, nvtxs, assignment);
        }
        bpm_improve(graph, assignment, goal, max_dev, &bndy_list, weights, using_vwgts);
      }
    }
  }

  else if (global_method == 2) { /* Spectral method. */
    mkconnected = MAKE_CONNECTED;
    solver_flag = LANCZOS_TYPE;

    active = smalloc(nvtxs * sizeof(int));
    for (i = 1; i <= ndims; i++) {
      yvecs[i] = smalloc((nvtxs + 1) * sizeof(double));
    }

    if (mkconnected) {
      /* If doing multi-level KL, this happens on coarse graph. */
      mark = (int *)&(yvecs[1][0]);
      make_connected(graph, nvtxs, &nedges, mark, active, &cdata, using_ewgts);
      if (DEBUG_CONNECTED > 0) {
        printf("Enforcing connectivity\n");
        print_connected(cdata);
      }
    }
    else if (DEBUG_CONNECTED > 0) {
      printf("Not enforcing connectivity\n");
    }

    maxdeg = find_maxdeg(graph, nvtxs, using_ewgts, (float *)NULL);
    eigensolve(graph, nvtxs, nedges, maxdeg, vwgt_max, vwsqrt, using_vwgts, using_ewgts, term_wgts,
               igeom, coords, yvecs, evals, architecture, assignment, fake_goal, solver_flag,
               rqi_flag, vmax, ndims, mediantype, eigtol);

    assign(graph, yvecs, nvtxs, ndims, architecture, nsets, vwsqrt, assignment, active, mediantype,
           goal, vwgt_max);

    for (i = 1; i <= ndims; i++) {
      sfree(yvecs[i]);
    }

    if (mkconnected) {
      make_unconnected(graph, &nedges, &cdata, using_ewgts);
    }

    sfree(active);
  }

  else if (global_method == 3) { /* Inertial method. */
    inertial(graph, nvtxs, architecture, nsets, igeom, coords, assignment, goal, using_vwgts);
  }

  else if (global_method == 4) { /* Linear ordering. */
    simple_type = 3;
    simple_part(graph, nvtxs, assignment, nsets, simple_type, goal);
  }

  else if (global_method == 5) { /* Random partitioning. */
    simple_type = 2;
    simple_part(graph, nvtxs, assignment, nsets, simple_type, goal);
  }

  else if (global_method == 6) { /* Scattered partitioning. */
    simple_type = 1;
    simple_part(graph, nvtxs, assignment, nsets, simple_type, goal);
  }

  /* Perform a local refinement, if specified, on the global partitioning. */
  if (local_method == 1 && global_method != 1) {
    /* If global_method == 1, already did KL as part of multilevel-KL. */
    null_ptr     = NULL;
    max_dev      = vwgt_max;
    total_weight = 0;
    for (i = 0; i < nsets; i++) {
      total_weight += goal[i];
    }
    total_weight *= KL_IMBALANCE / nsets;
    if (total_weight > max_dev) {
      max_dev = total_weight;
    }
    if (VERTEX_SEPARATOR) {
      find_bndy(graph, nvtxs, assignment, 2, &bndy_list);
      count_weights(graph, nvtxs, assignment, nsets + 1, weights, (vwgt_max != 1));
      klvspiff(graph, nvtxs, assignment, goal, max_dev, &bndy_list, weights);
      if (VERTEX_COVER) {
        bpm_improve(graph, assignment, goal, max_dev, &bndy_list, weights, using_vwgts);
      }
    }
    else {
      if (global_method != 2) {
        maxdeg = find_maxdeg(graph, nvtxs, using_ewgts, (float *)NULL);
      }
      count_weights(graph, nvtxs, assignment, nsets, weights, (vwgt_max != 1));
      klspiff(graph, nvtxs, assignment, nsets, hop_mtx, goal, term_wgts, max_dev, maxdeg,
              using_ewgts, &null_ptr, weights);
      if (VERTEX_COVER) {
        if (goal[0] - weights[0] <= goal[1] - weights[1]) {
          i = 0;
        }
        else {
          i = 1;
        }
        find_side_bndy(graph, nvtxs, assignment, i, 2, &bndy_list);
        count_weights(graph, nvtxs, assignment, nsets + 1, weights, (vwgt_max != 1));
        if (DEBUG_KL > 0) {
          printf("After KL, before bpm_improve\n");
          countup_vtx_sep(graph, nvtxs, assignment);
        }
        bpm_improve(graph, assignment, goal, max_dev, &bndy_list, weights, using_vwgts);
      }
    }
  }
  else if (global_method != 1 && VERTEX_COVER) {
    find_bndy(graph, nvtxs, assignment, 2, &bndy_list);
    count_weights(graph, nvtxs, assignment, nsets + 1, weights, (vwgt_max != 1));
    if (DEBUG_KL > 0) {
      printf("Before bpm_improve\n");
      countup_vtx_sep(graph, nvtxs, assignment);
    }
    max_dev      = vwgt_max;
    total_weight = 0;
    for (i = 0; i < nsets; i++) {
      total_weight += goal[i];
    }
    total_weight *= KL_IMBALANCE / nsets;
    if (total_weight > max_dev) {
      max_dev = total_weight;
    }
    bpm_improve(graph, assignment, goal, max_dev, &bndy_list, weights, using_vwgts);
  }

  if (bndy_list != NULL) {
    sfree(bndy_list);
  }
}
