//------------------------------------------------------------------------------
// GB_hyper_hash_build: construct A->Y for a hypersparse matrix A
//------------------------------------------------------------------------------

// SuiteSparse:GraphBLAS, Timothy A. Davis, (c) 2017-2023, All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

//------------------------------------------------------------------------------

// JIT: not needed.  Only one variant possible.

#define GB_FREE_WORKSPACE               \
{                                       \
    GB_FREE (&I_work, I_work_size) ;    \
    GB_FREE (&J_work, J_work_size) ;    \
    GB_FREE (&X_work, X_work_size) ;    \
}

#define GB_FREE_ALL                     \
{                                       \
    GB_FREE_WORKSPACE ;                 \
    GB_phybix_free (A) ;                \
}

#include "builder/GB_build.h"

GrB_Info GB_hyper_hash_build    // construct A->Y if not already constructed
(
    GrB_Matrix A,
    GB_Werk Werk
)
{ 

    //--------------------------------------------------------------------------
    // check inputs
    //--------------------------------------------------------------------------

    if (!GB_hyper_hash_need (A))
    { 
        // quick return: A is NULL, not hypersparse, A->Y already computed,
        // or A does not have enough non-empty vectors to warrant the creation
        // of the A->Y hyper_hash
        return (GrB_SUCCESS) ;
    }

    GrB_Info info ;
    int64_t *restrict I_work = NULL ; size_t I_work_size = 0 ;
    int64_t *restrict J_work = NULL ; size_t J_work_size = 0 ;
    uint64_t *restrict X_work = NULL ; size_t X_work_size = 0 ;

    ASSERT_MATRIX_OK (A, "A for hyper_hash", GB0) ;
    GB_BURBLE_MATRIX (A, "(build hyper hash) ") ;

    //--------------------------------------------------------------------------
    // allocate A->Y
    //--------------------------------------------------------------------------

    // A->Y is (A->vdim)-by-(hash table size for A->h), with one vector per
    // hash bucket.

    const int64_t *restrict Ah = A->h ;
    int64_t anvec = A->nvec ;
    // this ensures a load factor of 0.5 to 1:
    int64_t yvdim = ((uint64_t) 1) << (GB_FLOOR_LOG2 (anvec) + 1) ;
    // divide by 4 to get a load factor of 2 to 4:
    yvdim = yvdim / 4 ;
    yvdim = GB_IMAX (yvdim, 4) ;
    int64_t yvlen = A->vdim ;
    int64_t hash_bits = (yvdim - 1) ;   // yvdim is always a power of 2

    GB_OK (GB_new (&(A->Y), // new dynamic header, do not allocate any content
        GrB_UINT64, yvlen, yvdim, GB_Ap_null, true, GxB_SPARSE,
        -1, 0)) ;
    GrB_Matrix Y = A->Y ;

    //--------------------------------------------------------------------------
    // create the tuples for A->Y
    //--------------------------------------------------------------------------

    I_work = GB_MALLOC (anvec, int64_t, &I_work_size) ;
    J_work = GB_MALLOC (anvec, int64_t, &J_work_size) ;
    X_work = GB_MALLOC (anvec, uint64_t, &X_work_size) ;
    if (I_work == NULL || J_work == NULL || X_work == NULL)
    { 
        // out of memory
        GB_FREE_ALL ;
        return (GrB_OUT_OF_MEMORY) ;
    }

    int nthreads_max = GB_Context_nthreads_max ( ) ;
    double chunk = GB_Context_chunk ( ) ;
    int nthreads = GB_nthreads (anvec, chunk, nthreads_max) ;

    int64_t k ;
    #pragma omp parallel for num_threads(nthreads) schedule(static)
    for (k = 0 ; k < anvec ; k++)
    {
        int64_t j = Ah [k] ;
        I_work [k] = j ;
        J_work [k] = GB_HASHF2 (j, hash_bits) ;     // in range 0 to yvdim-1
        X_work [k] = (uint64_t) k ;
    }

    //--------------------------------------------------------------------------
    // build A->Y, initially hypersparse
    //--------------------------------------------------------------------------

    GB_OK (GB_builder (
        Y,                      // create Y using a dynamic header
        GrB_UINT64,             // Y->type
        yvlen,                  // Y->vlen
        yvdim,                  // Y->vdim
        true,                   // Y->is_csc
        (int64_t **) &I_work,   // row indices
        &I_work_size,
        (int64_t **) &J_work,   // column indices
        &J_work_size,
        (GB_void **) &X_work,   // values
        &X_work_size,
        false,                  // tuples need to be sorted
        true,                   // no duplicates
        anvec,                  // size of I_work and J_work in # of tuples
        true,                   // is_matrix: unused
        NULL, NULL,             // original I,J tuples
        NULL,                   // no scalar iso value
        false,                  // Y is never iso
        anvec,                  // # of tuples
        NULL,                   // no duplicates, so dup is NUL
        GrB_UINT64,             // the type of X_work
        false,                  // no burble (already burbled above)
        Werk
    )) ;

    Y->hyper_switch = -1 ;              // never make Y hypersparse
    Y->sparsity_control = GxB_SPARSE ;  // Y is always sparse CSC
    ASSERT (GB_IS_HYPERSPARSE (Y)) ;    // Y is currently hypersparse

    // workspace has been freed by GB_builder, or transplanted in to Y
    ASSERT (I_work == NULL) ;
    ASSERT (J_work == NULL) ;
    ASSERT (X_work == NULL) ;

    //--------------------------------------------------------------------------
    // convert A->Y to sparse
    //--------------------------------------------------------------------------

    // GB_builder always constructs its matrix as hypersparse.  Y is now
    // conformed to its required sparsity format: always sparse.  No burble;
    // (already burbled above).

    GB_OK (GB_convert_hyper_to_sparse (Y, false)) ;
    ASSERT (anvec == GB_nnz (Y)) ;
    ASSERT (GB_IS_SPARSE (Y)) ;         // Y is now sparse and will remain so

    //--------------------------------------------------------------------------
    // return result
    //--------------------------------------------------------------------------

    ASSERT_MATRIX_OK (A, "A from hyper_hash", GB0) ;
    ASSERT (!GB_ZOMBIES (Y)) ;
    ASSERT (!GB_JUMBLED (Y)) ;
    ASSERT (!GB_PENDING (Y)) ;
    return (GrB_SUCCESS) ;
}

