/*===========================================================================
*
*                            PUBLIC DOMAIN NOTICE
*               National Center for Biotechnology Information
*
*  This software/database is a "United States Government Work" under the
*  terms of the United States Copyright Act.  It was written as part of
*  the author's official duties as a United States Government employee and
*  thus cannot be copyrighted.  This software/database is freely available
*  to the public for use. The National Library of Medicine and the U.S.
*  Government have not placed any restriction on its use or reproduction.
*
*  Although all reasonable efforts have been taken to ensure the accuracy
*  and reliability of the software and data, the NLM and the U.S.
*  Government do not and cannot warrant the performance or results that
*  may be obtained by using this software or data. The NLM and the U.S.
*  Government disclaim all warranties, express or implied, including
*  warranties of performance, merchantability or fitness for any particular
*  purpose.
*
*  Please cite the author in any work or product based on this material.
*
* ===========================================================================
*
*/

#include "pileup_v2.h"

#ifndef _h_klib_out_
#include <klib/out.h>
#endif

#ifndef _h_dyn_string_
#include "dyn_string.h"
#endif

#ifndef _h_ref_walker_
#include "ref_walker.h"
#endif

#ifndef _h_4na_ascii_
#include "4na_ascii.h"
#endif

/*
static rc_t CC pileup_test_enter_ref( ref_walker_data * rwd ) {
    return KOutMsg( "\nentering >%s<\n", rwd->ref_name );
}

static rc_t CC pileup_test_exit_ref( ref_walker_data * rwd ) {
    return KOutMsg( "exit >%s<\n", rwd->ref_name );
}

static rc_t CC pileup_test_enter_ref_window( ref_walker_data * rwd ) {
    return KOutMsg( "   enter window >%s< [ %,lu ... %,lu ]\n", rwd->ref_name, rwd->ref_start, rwd->ref_end );
}

static rc_t CC pileup_test_exit_ref_window( ref_walker_data * rwd ) {
    return KOutMsg( "   exit window >%s< [ %,lu ... %,lu ]\n", rwd->ref_name, rwd->ref_start, rwd->ref_end );
}

static rc_t CC pileup_test_enter_ref_pos( ref_walker_data * rwd ) {
    return KOutMsg( "   enter pos [ %,lu ], d=%u\n", rwd->pos, rwd->depth );
}

static rc_t CC pileup_test_exit_ref_pos( ref_walker_data * rwd ) {
    return KOutMsg( "   exit pos [ %,lu ], d=%u\n", rwd->pos, rwd->depth );
}

static rc_t CC pileup_test_enter_spot_group( ref_walker_data * rwd ) {
    return KOutMsg( "       enter spot-group [ %,lu ], %.*s\n", rwd->pos, rwd->spot_group_len, rwd->spot_group );
}

static rc_t CC pileup_test_exit_spot_group( ref_walker_data * rwd ) {
    return KOutMsg( "       exit spot-group [ %,lu ], %.*s\n", rwd->pos, rwd->spot_group_len, rwd->spot_group );
}

static rc_t CC pileup_test_alignment( ref_walker_data * rwd ) {
    rc_t rc = KOutMsg( "          alignment\t" );
    if ( rc == 0 ) {
        if ( !rwd->valid ) {
            KOutMsg( "invalid" );
        } else {
            KOutMsg( "%s%s", rwd->reverse ? "<" : ">", rwd->match ? "." : "!" );
        }
    }
    KOutMsg( "\n" );
    return rc;
}

static rc_t pileup_test( Args * args, pileup_options *options ) {
    struct ref_walker * walker;
    rc_t rc = ref_walker_create( &walker );
    if ( rc == 0 ) {
        uint32_t idx, count;
        rc = ArgsParamCount( args, &count );
        for ( idx = 0; idx < count && rc == 0; ++idx ) {
            const char * src = NULL;
            rc = ArgsParamValue( args, idx, (const void **)&src );
            if ( rc == 0 && src != NULL ) {
                rc = ref_walker_add_source( walker, src );
            }
        }
        if ( rc == 0 ) {
            rc = ArgsOptionCount( args, OPTION_REF, &count );
            for ( idx = 0; idx < count && rc == 0; ++idx ) {
                const char * s = NULL;
                rc = ArgsOptionValue( args, OPTION_REF, idx, (const void **)&s );
                if ( rc == 0 && s != NULL ) {
                    rc = ref_walker_parse_and_add_range( walker, s );
                }
            }
        }
        if ( rc == 0 ) {
            ref_walker_callbacks callbacks = 
                {   pileup_test_enter_ref,
                    pileup_test_exit_ref,
                    pileup_test_enter_ref_window,
                    pileup_test_exit_ref_window,
                    pileup_test_enter_ref_pos,
                    pileup_test_exit_ref_pos,
                    pileup_test_enter_spot_group,
                    pileup_test_exit_spot_group,
                    pileup_test_alignment };
            rc = ref_walker_set_callbacks( walker, &callbacks );
        }

        if ( rc == 0 ) {
            rc = ref_walker_walk( walker, NULL );
        }
        ref_walker_destroy( walker );
    }
    return rc;
}
*/

/* =========================================================================================== */

typedef struct pileup_v2_ctx {
    struct dyn_string * bases;
    struct dyn_string * qual;
    bool print_qual;
    bool div_by_spotgrp;
    bool debug;
} pileup_v2_ctx;

static rc_t CC pileup_v2_enter_ref_pos( ref_walker_data * rwd ) {
    pileup_v2_ctx * ctx = rwd -> data;
    /* make shure that bases/qual have the necessary length ( depth * 2 ) */
    uint32_t l = ( rwd -> depth * 2 );
    rc_t rc = ds_expand( ctx -> bases, l );
    if ( rc == 0 ) {
        ds_reset( ctx -> bases );
    }
    if ( rc == 0 && ctx -> print_qual ) {
        rc = ds_expand( ctx -> qual, l );
        if ( rc == 0 ) {
            ds_reset( ctx -> qual );
        }
    }
    return rc;
}

static rc_t CC pileup_v2_exit_ref_pos( ref_walker_data * rwd ) {
    pileup_v2_ctx * ctx = rwd->data;
    rc_t rc = KOutMsg( "%s\t%u\t%c\t%u\t", rwd->ref_name, rwd->pos + 1, rwd->ascii_ref_base, rwd->depth );
    if ( rc == 0 ) {
        rc = ds_print( ctx -> bases );
    }
    if ( rc == 0 && ctx -> print_qual ) {
        rc = KOutMsg( "\t" );
        if ( rc == 0 ) {
            rc = ds_print( ctx -> qual );
        }
    }
    if ( rc == 0 ) {
        rc = KOutMsg( "\n" );
    }
    return rc;
}

static rc_t CC pileup_v2_enter_spot_group( ref_walker_data * rwd ) {
    rc_t rc = 0;
    pileup_v2_ctx * ctx = rwd -> data;
    if ( ctx -> div_by_spotgrp ) {
        if ( ds_len( ctx -> bases ) > 0 ) {
            rc = ds_add_char( ctx -> bases, '\t' );
        }
        if ( rc == 0 && ctx -> print_qual && ds_len( ctx -> qual ) > 0 ) {
            rc = ds_add_char( ctx -> qual, '\t' );
        }
    }
    return rc;
}

static rc_t CC pileup_v2_alignment( ref_walker_data * rwd ) {
    rc_t rc = 0;
    pileup_v2_ctx * ctx = rwd->data;

    if ( !( rwd -> valid ) ) {
        rc = ds_add_char( ctx -> bases, '?' );
        if ( rc == 0 && ctx -> print_qual ) {
            rc = ds_add_char( ctx -> qual, '?' );
        }
    } else {
        if ( rwd->first ) {
            char s[ 3 ];
            int32_t c = rwd->mapq + 33;
            if ( c > '~' ) { c = '~'; }
            if ( c < 33 ) { c = 33; }
            s[ 0 ] = '^';
            s[ 1 ] = c;
            s[ 2 ] = 0;
            rc = ds_add_str( ctx -> bases, s );
        }

        if ( rc == 0 ) {
            if ( rwd -> skip ) {
                if ( rwd -> reverse ) {
                    rc = ds_add_char( ctx -> bases, '<' );
                } else {
                    rc = ds_add_char( ctx -> bases, '>' );
                }
            } else {
                if ( rwd -> match ) {
                    rc = ds_add_char( ctx -> bases, ( rwd -> reverse ? ',' : '.' ) );
                } else {
                    rc = ds_add_char( ctx -> bases, rwd -> ascii_alignment_base );
                }
            }
        }
        if ( rc == 0 && rwd -> ins ) {
            uint32_t i, n = rwd -> ins_bases_count;
            
            rc = ds_add_fmt( ctx -> bases, "+%u", rwd -> ins_bases_count );
            for ( i = 0; i < n && rc == 0; ++i ) {
                rc = ds_add_char( ctx -> bases, _4na_to_ascii( rwd->ins_bases[ i ], rwd -> reverse ) );
            }
        }
        if ( rc == 0 && rwd -> del && rwd -> del_bases_count > 0 && rwd -> del_bases != NULL ) {
            uint32_t i, n = rwd -> del_bases_count;
            rc = ds_add_fmt( ctx -> bases, "-%u", n );
            for ( i = 0; i < n && rc == 0; ++i ) {
                rc = ds_add_char( ctx -> bases, _4na_to_ascii( rwd -> del_bases[ i ], rwd -> reverse ) );
            }
        }

        if ( rc == 0 && rwd -> last ) {
            rc = ds_add_char( ctx->bases, '$' );
        }
        if ( rc == 0 && ctx -> print_qual ) {
            rc = ds_add_char( ctx -> qual, rwd -> quality );
        }
    }
    return rc;
}

rc_t pileup_v2( Args * args, pileup_options *options ) {
    struct ref_walker * walker;

    /* create walker */
    rc_t rc = ref_walker_create( &walker );
    if ( rc == 0 ) {
        uint32_t idx, count;

        /* add sources to walker */
        rc = ArgsParamCount( args, &count );
        for ( idx = 0; idx < count && rc == 0; ++idx ) {
            const char * src = NULL;
            rc = ArgsParamValue( args, idx, (const void **)&src );
            if ( rc == 0 && src != NULL ) {
                rc = ref_walker_add_source( walker, src );
            }
        }

        /* add ranges to walker */
        if ( rc == 0 ) {
            rc = ArgsOptionCount( args, OPTION_REF, &count );
            for ( idx = 0; idx < count && rc == 0; ++idx ) {
                const char * s = NULL;
                rc = ArgsOptionValue( args, OPTION_REF, idx, (const void **)&s );
                if ( rc == 0 && s != NULL ) {
                    rc = ref_walker_parse_and_add_range( walker, s );
                }
            }
        }

        /* set callbacks for walker */
        if ( rc == 0 ) {
            ref_walker_callbacks callbacks = {
                    NULL,
                    NULL,
                    NULL,
                    NULL,
                    pileup_v2_enter_ref_pos,
                    pileup_v2_exit_ref_pos,
                    pileup_v2_enter_spot_group,
                    NULL,
                    pileup_v2_alignment };
            rc = ref_walker_set_callbacks( walker, &callbacks );
        }

        /* translate the commandline options into walker 'INTERESTS' */
        if ( rc == 0 ) {
            uint32_t interest = RW_INTEREST_INDEL | RW_INTEREST_BASE;

            if ( options -> process_dups ) { interest |= RW_INTEREST_DUPS; }
            if ( !options -> cmn . omit_qualities ) { interest |= RW_INTEREST_QUAL; }
            if ( !options -> no_skip ) { interest |= RW_INTEREST_SKIP; }
            if ( options -> show_id ) { interest |= RW_INTEREST_DEBUG; }
            if ( options -> use_seq_name ) { interest |= RW_INTEREST_SEQNAME; }
            if ( options -> cmn . tab_select & primary_ats ) { interest |= RW_INTEREST_PRIM; }
            if ( options -> cmn . tab_select & secondary_ats ) { interest |= RW_INTEREST_SEC; }
            if ( options -> cmn . tab_select & evidence_ats ) { interest |= RW_INTEREST_EV; }

            rc = ref_walker_set_interest( walker, interest );
            if ( rc == 0 ) {
                rc = ref_walker_set_min_mapq( walker, options->minmapq );
            }
        }

        /* let the walker call the callbacks while iterating over the sources/ranges */
        if ( rc == 0 ) {
            pileup_v2_ctx ctx;
            memset( &ctx, 0, sizeof ctx );
            rc = ds_allocate( &ctx.bases, 1000 );
            if ( rc == 0 ) {
                rc = ds_allocate( &ctx.qual, 1000 );
                if ( rc == 0 ) {
                    ctx . print_qual = !( options -> cmn . omit_qualities );
                    ctx . div_by_spotgrp = options -> div_by_spotgrp;
                    ctx . debug = options -> show_id;

                    /***********************************/
                    rc = ref_walker_walk( walker, &ctx );
                    /***********************************/

                    ds_free( ctx.qual );
                }
                ds_free( ctx.bases );
            }
        }
        /* destroy the walker */
        ref_walker_destroy( walker );
    }
    return rc;
}
