/*
 * functions for mapping k structs to perl variables
 */
#include "k.h"
#include "kparse.h"
#include <string.h>

#define MATH_INT64_NATIVE_IF_AVAILABLE
#include "perl_math_int64.h"

SV* sv_from_k(K k) {

    SV* result;

    if (k == NULL) {
        result = &PL_sv_undef;
    }
    else if (k->t < 0) {
        result = scalar_from_k(k);
    }
    else if (k->t > 0) {
        result = vector_from_k(k);
    }
    else {
        result = mixed_list_from_k(k);
    }

    return result;
}

SV* scalar_from_k(K k) {
    SV *result = NULL;

    switch (- k->t) {

        case KB: // boolean
            result = bool_from_k(k);
            break;

        case KG: // byte
            result = byte_from_k(k);
            break;

        case KC: // char
            result = char_from_k(k);
            break;

        case KH: // short
            result = short_from_k(k);
            break;

        case KI: // int
        case KM: // month
        case KD: // date
        case KU: // minute
        case KV: // second
        case KT: // time
            result = int_from_k(k);
            break;

        case KJ: // long
        case KN: // timespan
            result = long_from_k(k);
            break;

        case KP: // timestamp
            result = timestamp_from_k(k);
            break;

        case KE: // real
            result = real_from_k(k);
            break;

        case KF: // float
        case KZ: // time *don't use*
            result = float_from_k(k);
            break;

        case KS: // symbol
            result = symbol_from_k(k);
            break;

        case 128: // error
            croak(k->s);
            break;

        default:
            croak("unrecognized scalar type '%d'\n", k->t);
            break;
    }

    return result;
}

SV* vector_from_k(K k) {
    SV *result = NULL;

    switch (k->t) {

        case KB: // boolean
            result = bool_vector_from_k(k);
            break;

        case KG: // byte
            result = byte_vector_from_k(k);
            break;

        case KC: // char
            result = char_vector_from_k(k);
            break;

        case KH: // short
            result = short_vector_from_k(k);
            break;

        case KI: // int
        case KM: // month
        case KD: // date
        case KU: // minute
        case KV: // second
        case KT: // time
            result = int_vector_from_k(k);
            break;

        case KJ: // long
        case KN: // timespan
            result = long_vector_from_k(k);
            break;

        case KP: // timestamp
            result = timestamp_vector_from_k(k);
            break;

        case KE: // real
            result = real_vector_from_k(k);
            break;

        case KF: // float
        case KZ: // time *don't use*
            result = float_vector_from_k(k);
            break;

        case KS: // symbol
            result = symbol_vector_from_k(k);
            break;

        case XT: // table or flip
            result = table_from_k(k);
            break;

        case XD: // dict or table w/ primary keys
            result = xd_from_k(k);
            break;

        // enumerations (start at 20?) other stuff?

        case 100: // function
            return &PL_sv_undef;
            break;

        case 101: // generic null
            return &PL_sv_undef;
            break;

        case 102: // not sure actually. the q cmd '{:}[]' returns a 102h
            return &PL_sv_undef;
            break;

        default:
            croak("unrecognized vector type '%d'\n", k->t);
            break;
    }

    return result;
}

/*
 * K structs of type XD are either a partitioned table or a dictionary.
 * Dispath accordingly.
 */
SV* xd_from_k(K k) {
    if (kK(k)[0]->t == XT && kK(k)[1]->t == XT) {
        return ptable_from_k(k);
    }
    else if (kK(k)[0]->t == KS) {
        return dict_from_k(k);
    }
    else {
        croak("Unrecognized XD (dictionary) form");
    }
}

/* copy the contents of hv into store_hv */
void hv_store_hv(HV *store_hv, HV *hv) {
    int i;
    int h_size = hv_iterinit(hv);
    HE *store_ret, *he;
    SV *key, *val;

    for (i = 0; i < h_size; i++) {

        he  = hv_iternext(hv);
        key = hv_iterkeysv(he);
        val = hv_iterval(hv, he);

        store_ret = hv_store_ent(store_hv, key, val, 0);
        if (store_ret == NULL) {
            croak("Failed to store hash entry");
        }

        SvREFCNT_inc(val);
    }
}

SV* ptable_from_k(K k) {
    HV *hv = newHV();

    K t0   = kK(k)[0]; // partitioned tables have 2 sub-tables
    K t1   = kK(k)[1];

    SV *t0_rv = table_from_k(t0);
    SV *t1_rv = table_from_k(t1);

    HV *t0_hv = (HV*) SvRV( t0_rv );
    HV *t1_hv = (HV*) SvRV( t1_rv );

    hv_store_hv(hv, t0_hv);
    hv_store_hv(hv, t1_hv);

    SvREFCNT_dec(t0_rv);
    SvREFCNT_dec(t1_rv);

    return newRV_noinc( (SV*)hv );
}

SV* dict_from_k(K k) {
    int i;
    SV **key;
    SV **val;
    HV *hv = newHV();
    HE *store_ret;

    SV* keys_ref = sv_from_k( kK(k)[0] );
    SV* vals_ref = sv_from_k( kK(k)[1] );

    AV* keys = (AV*) SvRV( keys_ref );
    AV* vals = (AV*) SvRV( vals_ref );

    int key_count = av_len(keys) + 1;

    /*  k dicts can have the same key multiple times.  When such dicts are
     *  referenced using a key, the value for the first occurance of the key
     *  is the one returned.  Perl has the opposite.  Here we go through the
     *  keys backward to ensure the value for any duplicate keys ends up being
     *  the value associated with the first occurance of the key as it would
     *  in k/q.
     */
    for (i = key_count -1; i >= 0; i--) {
        key = av_fetch(keys, i, 0);
        val = av_fetch(vals, i, 0);

        store_ret = hv_store_ent(hv, *key, *val, 0);
        if (store_ret == NULL) {
            croak("Failed to convert k hash entry to perl hash entry");
        }

        SvREFCNT_inc(*val);
    }

    SvREFCNT_dec(keys_ref);
    SvREFCNT_dec(vals_ref);

    return newRV_noinc( (SV*)hv );
}

SV* table_from_k(K k) {
    K dict = k->k;
    return dict_from_k(dict);
}

SV* mixed_list_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        av_push(av, sv_from_k( kK(k)[i] ) );
    }

    return newRV_noinc((SV* )av);
}

/*
 * scalar helpers
 */

SV* bool_from_k(K k) {
    if (k->g == 0) {
        return &PL_sv_undef;
    }

    return newSViv( k->g );
}

SV* byte_from_k(K k) {
    return newSVuv( k->g );
}

SV* char_from_k(K k) {
    char byte_str[1];
    byte_str[0] = k->g;
    return newSVpvn(byte_str, 1);
}

SV* short_from_k(K k) {
    if (k->h == nh) {
        return &PL_sv_undef;
    }

    if (k->h == wh) {
        return newSVpvn("inf", 3);
    }

    if (k->h == -wh) {
        return newSVpvn("-inf", 4);
    }

    return newSViv(k->h);
}

SV* int_from_k(K k) {
    if (k->i == ni) {
        return &PL_sv_undef;
    }

    if (k->i == wi) {
        return newSVpvn("inf", 3);
    }

    if (k->i == -wi) {
        return newSVpvn("-inf", 4);
    }

    return newSViv(k->i);
}

SV* timestamp_from_k(K k) {
    if (k->j == nj) {
        return &PL_sv_undef;
    }

    if (k->j == wj) {
        return newSVpvn("inf", 3);
    }

    if (k->j == -wj) {
        return newSVpvn("-inf", 4);
    }

    return newSVi64(k->j);
}

SV* long_from_k(K k) {
    if (k->j == nj) {
        return &PL_sv_undef;
    }

    if (k->j == wj) {
        return newSVpvn("inf", 3);
    }

    if (k->j == -wj) {
        return newSVpvn("-inf", 4);
    }

    return newSVi64(k->j);
}

SV* real_from_k(K k) {
    if (isnan(k->e)) {
        return &PL_sv_undef;
    }

    return newSVnv(k->e);
}

SV* float_from_k(K k) {
    if (isnan(k->f)) {
        return &PL_sv_undef;
    }

    return newSVnv(k->f);
}

SV* symbol_from_k(K k) {
    if (strncmp(k->s, "", k->n) == 0) {
        return &PL_sv_undef;
    }

    return newSVpv(k->s, 0);
}

/*
 * vector helpers
 */

SV* bool_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (kG(k)[i] == 0) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        av_push(av, newSViv( kG(k)[i]) );
    }

    return newRV_noinc( (SV*)av );
}

SV* byte_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        av_push(av, newSVuv( kG(k)[i] ));
    }

    return newRV_noinc( (SV*)av );
}

SV* char_vector_from_k(K k) {
    AV *av = newAV();
    char byte_str[1];
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (kG(k)[i] == 0) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        byte_str[0] = kG(k)[i];
        av_push(av, newSVpvn(byte_str, 1));
    }

    return newRV_noinc( (SV*)av );
}

SV* short_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (kH(k)[i] == nh) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        if (kH(k)[i] == wh) {
            av_push(av, newSVpvn("inf", 3));
            continue;
        }

        if (kH(k)[i] == -wh) {
            av_push(av, newSVpvn("-inf", 4));
            continue;
        }

        av_push(av, newSViv( kH(k)[i]) );
    }

    return newRV_noinc( (SV*)av );
}

SV* int_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (kI(k)[i] == ni) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        if (kI(k)[i] == wi) {
            av_push(av, newSVpvn("inf", 3));
            continue;
        }

        if (kI(k)[i] == -wi) {
            av_push(av, newSVpvn("-inf", 4));
            continue;
        }

        av_push(av, newSViv( kI(k)[i]) );
    }

    return newRV_noinc( (SV*)av );
}

SV* long_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (kJ(k)[i] == nj) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        if (kJ(k)[i] == wj) {
            av_push(av, newSVpvn("inf", 3));
            continue;
        }

        if (kJ(k)[i] == -wj) {
            av_push(av, newSVpvn("-inf", 4));
            continue;
        }

        av_push(av, newSVi64(kJ(k)[i]) );
    }

    return newRV_noinc( (SV*)av );
}

SV* timestamp_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (kJ(k)[i] == nj) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        if (kJ(k)[i] == wj) {
            av_push(av, newSVpvn("inf", 3));
            continue;
        }

        if (kJ(k)[i] == -wj) {
            av_push(av, newSVpvn("-inf", 4));
            continue;
        }

        av_push(av, newSVi64(kJ(k)[i]) );
    }

    return newRV_noinc( (SV*)av );
}

SV* real_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (isnan( kE(k)[i] )) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        av_push(av, newSVnv( kE(k)[i] ) );
    }

    return newRV_noinc( (SV*)av );
}

SV* float_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;

    for (i = 0; i < k->n; i++) {
        if (isnan( kF(k)[i] )) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        av_push(av, newSVnv( kF(k)[i] ) );
    }

    return newRV_noinc( (SV*)av );
}

SV* symbol_vector_from_k(K k) {
    AV *av = newAV();
    int i = 0;
    char *sym = NULL;

    for (i = 0; i < k->n; i++) {
        sym = kS(k)[i];

        if (strncmp(sym, "", strlen(sym)) == 0) {
            av_push(av, &PL_sv_undef);
            continue;
        }

        av_push(av, newSVpv( kS(k)[i], 0 ) );
    }

    return newRV_noinc( (SV*)av );
}
