/*      $NetBSD: graph2.c,v 1.6 2021/01/28 18:52:43 joerg Exp $ */
/*-
* Copyright (c) 2009 The NetBSD Foundation, Inc.
* All rights reserved.
*
* This code is derived from software contributed to The NetBSD Foundation
* by Joerg Sonnenberger.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
*    notice, this list of conditions and the following disclaimer.
* 2. 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.
*
* 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 HOLDERS 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.
*/

#if HAVE_NBTOOL_CONFIG_H
#include "nbtool_config.h"
#endif

#include <sys/cdefs.h>
__RCSID("$NetBSD: graph2.c,v 1.6 2021/01/28 18:52:43 joerg Exp $");

#include <err.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "nbperf.h"
#include "graph2.h"

void
SIZED2(_setup)(struct SIZED(graph) *graph, uint32_t v, uint32_t e)
{
       graph->v = v;
       graph->e = e;

       graph->verts = calloc(sizeof(*graph->verts), v);
       graph->edges = calloc(sizeof(*graph->edges), e);
       graph->output_order = calloc(sizeof(uint32_t), e);

       if (graph->verts == NULL || graph->edges == NULL ||
           graph->output_order == NULL)
               err(1, "malloc failed");
}

void
SIZED2(_free)(struct SIZED(graph) *graph)
{
       free(graph->verts);
       free(graph->edges);
       free(graph->output_order);

       graph->verts = NULL;
       graph->edges = NULL;
       graph->output_order = NULL;
}

static struct nbperf *sorting_nbperf;
static struct SIZED(graph) *sorting_graph;
static int sorting_found;

static int sorting_cmp(const void *a_, const void *b_)
{
       const uint32_t *a = a_, *b = b_;
       int i;
       const struct SIZED(edge) *ea = &sorting_graph->edges[*a],
           *eb = &sorting_graph->edges[*b];
       for (i = 0; i < GRAPH_SIZE; ++i) {
               if (ea->vertices[i] < eb->vertices[i])
                       return -1;
               if (ea->vertices[i] > eb->vertices[i])
                       return 1;
       }
       if (sorting_nbperf->keylens[*a] < sorting_nbperf->keylens[*b])
               return -1;
       if (sorting_nbperf->keylens[*a] > sorting_nbperf->keylens[*b])
               return 1;
       i = memcmp(sorting_nbperf->keys[*a], sorting_nbperf->keys[*b],
           sorting_nbperf->keylens[*a]);
       if (i == 0)
               sorting_found = 1;
       return i;
}

static int
SIZED2(_check_duplicates)(struct nbperf *nbperf, struct SIZED(graph) *graph)
{
       size_t i;
       uint32_t *key_index = calloc(sizeof(*key_index), graph->e);

       if (key_index == NULL)
               err(1, "malloc failed");
       for (i = 0; i < graph->e; ++i)
               key_index[i] = i;

       sorting_nbperf = nbperf;
       sorting_graph = graph;
       sorting_found = 0;
       qsort(key_index, graph->e, sizeof(*key_index), sorting_cmp);
       if (sorting_found)
               goto found_dups;
       /*
        * Any duplicate must have been found as part of the qsort,
        * as it can't sort consecutive elements in the output without
        * comparing them at least once.
        */

       free(key_index);
       return 0;
found_dups:
       nbperf->has_duplicates = 1;
       return -1;
}

static inline void
SIZED2(_add_edge)(struct SIZED(graph) *graph, uint32_t edge)
{
       struct SIZED(edge) *e = graph->edges + edge;
       struct SIZED(vertex) *v;
       size_t i;

       for (i = 0; i < GRAPH_SIZE; ++i) {
               v = graph->verts + e->vertices[i];
               v->edges ^= edge;
               ++v->degree;
       }
}

static inline void
SIZED2(_remove_edge)(struct SIZED(graph) *graph, uint32_t edge)
{
       struct SIZED(edge) *e = graph->edges + edge;
       struct SIZED(vertex) *v;
       size_t i;

       for (i = 0; i < GRAPH_SIZE; ++i) {
               v = graph->verts + e->vertices[i];
               v->edges ^= edge;
               --v->degree;
       }
}

static inline void
SIZED2(_remove_vertex)(struct SIZED(graph) *graph, uint32_t vertex)
{
       struct SIZED(vertex) *v = graph->verts + vertex;
       uint32_t e;

       if (v->degree == 1) {
               e = v->edges;
               graph->output_order[--graph->output_index] = e;
               SIZED2(_remove_edge)(graph, e);
       }
}

int
SIZED2(_hash)(struct nbperf *nbperf, struct SIZED(graph) *graph)
{
       struct SIZED(edge) *e;
       uint32_t hashes[NBPERF_MAX_HASH_SIZE];
       size_t i, j;

#if GRAPH_SIZE == 2
       if (nbperf->allow_hash_fudging && (graph->v & 1) != 0)
               errx(1, "vertex count must have lowest bit clear");
#else
       if (nbperf->allow_hash_fudging && (graph->v & 3) != 0)
               errx(1, "vertex count must have lowest 2 bits clear");
#endif


       memset(graph->verts, 0, sizeof(*graph->verts) * graph->v);
       graph->hash_fudge = 0;

       for (i = 0; i < graph->e; ++i) {
               (*nbperf->compute_hash)(nbperf,
                   nbperf->keys[i], nbperf->keylens[i], hashes);
               e = graph->edges + i;
               for (j = 0; j < GRAPH_SIZE; ++j) {
                       e->vertices[j] = hashes[j] % graph->v;
                       if (j == 1 && e->vertices[0] == e->vertices[1]) {
                               if (!nbperf->allow_hash_fudging)
                                       return -1;
                               e->vertices[1] ^= 1; /* toogle bit to differ */
                               graph->hash_fudge |= 1;
                       }
#if GRAPH_SIZE == 3
                       if (j == 2 && (e->vertices[0] == e->vertices[2] ||
                           e->vertices[1] == e->vertices[2])) {
                               if (!nbperf->allow_hash_fudging)
                                       return -1;
                               graph->hash_fudge |= 2;
                               e->vertices[2] ^= 1;
                               e->vertices[2] ^= 2 * (e->vertices[0] == e->vertices[2] ||
                                   e->vertices[1] == e->vertices[2]);
                       }
#endif
               }
       }

       for (i = 0; i < graph->e; ++i)
               SIZED2(_add_edge)(graph, i);

       if (nbperf->check_duplicates) {
               nbperf->check_duplicates = 0;
               return SIZED2(_check_duplicates)(nbperf, graph);
       }

       return 0;
}

int
SIZED2(_output_order)(struct SIZED(graph) *graph)
{
       size_t i, j;
       struct SIZED(edge) *e2;

       graph->output_index = graph->e;

       for (i = 0; i < graph->v; ++i)
               SIZED2(_remove_vertex)(graph, i);

       for (i = graph->e; graph->output_index > 0 && i > graph->output_index;) {
               e2 = graph->edges + graph->output_order[--i];
               for (j = 0; j < GRAPH_SIZE; ++j)
                       SIZED2(_remove_vertex)(graph, e2->vertices[j]);
       }

       if (graph->output_index != 0) {
               return -1;
       }

       return 0;
}