/* 
 * File:   julius_utils.h
 * Author: hara
 *
 * Created on 2013/08/27, 12:22
 */

#ifndef JULIUS_UTILS_H
#define	JULIUS_UTILS_H

#define JRECOGSTATUS_MASK_END       0x0003
#define JRECOGSTATUS_END_SUCCESS    0x0000
#define JRECOGSTATUS_END_BY_REQTERM 0x0001
#define JRECOGSTATUS_END_AT_PREPARE 0x0002
#define JRECOGSTATUS_END_BY_INVALID 0x0004

#define JRECOGSTATUS_MASK_PASS1       0x00f0
#define JRECOGSTATUS_PASS1_BEGIN      0x0010
#define JRECOGSTATUS_PASS1_END_SUCESS 0x0020
#define JRECOGSTATUS_PASS1_END_FAIL   0x0040

#define JRECOGSTATUS_MASK_PASS2       0x0f00
#define JRECOGSTATUS_PASS2_BEGIN      0x0100
#define JRECOGSTATUS_PASS2_END_SUCESS 0x0200
#define JRECOGSTATUS_PASS2_END_FAIL   0x0400
#define JRECOGSTATUS_PASS2_DO         0x0800

#include <julius/juliuslib.h>

/** 
 * <EN>
 * Recognition error handling.
 * </EN>
 * <JA>
 * エラーによる認識終了時の処理. 
 * </JA>
 * 
 * @param recog [in] engine instance
 * @param status [in] error status to be set
 * 
 */
static void
result_error(Recog *recog, int status) {
    MFCCCalc *mfcc;
    RecogProcess *r;
    boolean ok_p;

    for (r = recog->process_list; r; r = r->next) r->result.status = status;

    ok_p = FALSE;
    for (mfcc = recog->mfcclist; mfcc; mfcc = mfcc->next) {
        if (mfcc->f > 0) {
            ok_p = TRUE;
            break;
        }
    }
    if (ok_p) { /* had some input */
        /* output as rejected */
        callback_exec(CALLBACK_RESULT, recog);
#ifdef ENABLE_PLUGIN
        plugin_exec_process_result(recog);
#endif
    }
}

void
_recognize_stream_prepare(Recog* recog)
{
    RecogProcess *r;

    // update initial recognition process status.
    for (r = recog->process_list; r; r = r->next) {
        if (r->active > 0) {
            r->live = TRUE;
        } else if (r->active < 0) {
            r->live = FALSE;
        }
        r->active = 0;
    }
}

uint32_t
_recognize_stream_pass1_begin(Recog* recog)
{
    Jconf *jconf = recog->jconf;
    MFCCCalc *mfcc;

    fprintf(stderr, "Prepare\n");
    if (RealTimePipeLinePrepare(recog) == FALSE) {
        fprintf(stderr, "ERROR: failed to prepare for on-the-fly 1st pass decoding\n");
//        return (-1);
        return (JRECOGSTATUS_PASS1_BEGIN | JRECOGSTATUS_PASS1_END_FAIL);
    }
    return JRECOGSTATUS_PASS1_BEGIN;
}


uint32_t
_recognize_stream_pass1_valide_input(Recog* recog)
{
    Jconf *jconf = recog->jconf;
    RecogProcess *r;
    MFCCCalc *mfcc;

    float seclen, mseclen;

    /* -rejectshort 指定時, 入力が指定時間以下であれば探索失敗として */
    /* 第２パスを実行せずにここで終了する */
    /* when using "-rejectshort", and input was shorter than the specified
       length, terminate search here and output recognition failure */
    if (jconf->reject.rejectshortlen > 0) {
        mseclen = (float) recog->mfcclist->param->samplenum * (float) jconf->input.period * (float) jconf->input.frameshift / 10000.0;
        if (mseclen < jconf->reject.rejectshortlen) {
            result_error(recog, J_RESULT_STATUS_REJECT_SHORT);
            return JRECOGSTATUS_END_BY_INVALID;
        }
    }
    if (jconf->reject.rejectlonglen >= 0) {
        mseclen = (float) recog->mfcclist->param->samplenum * (float) jconf->input.period * (float) jconf->input.frameshift / 10000.0;
        if (mseclen >= jconf->reject.rejectlonglen) {
            result_error(recog, J_RESULT_STATUS_REJECT_LONG);
            return JRECOGSTATUS_END_BY_INVALID;
        }
    }
#ifdef POWER_REJECT
    if (power_reject(recog)) {
        result_error(recog, J_RESULT_STATUS_REJECT_POWER);
        return JRECOGSTATUS_END_BY_INVALID;
    }
#endif

    /* if GMM is specified and result are to be rejected, terminate search here */
    if (jconf->reject.gmm_reject_cmn_string != NULL) {
        if (!gmm_valid_input(recog)) {
            result_error(recog, J_RESULT_STATUS_REJECT_GMM);
            return JRECOGSTATUS_END_BY_INVALID;
        }
    }

}


uint32_t
_recognize_stream_pass1_end(Recog* recog)
{
    RecogProcess *r;
    uint32_t ret = 0;

    RealTimeParam(recog);
    fprintf(stderr, "\n");

    /* execute callback for 1st pass result */
    /* result.status <0 must be skipped inside callback */
    callback_exec(CALLBACK_RESULT_PASS1, recog);
    /* execute callback at end of pass1 */
    callback_exec(CALLBACK_EVENT_PASS1_END, recog);
    /* output frame length */
    callback_exec(CALLBACK_STATUS_PARAM, recog);

    // Validate the input streamdata
    ret += _recognize_stream_pass1_valide_input(recog);
    if( (ret & JRECOGSTATUS_END_BY_INVALID) == JRECOGSTATUS_END_BY_INVALID ) {
        return ret;
    }

    /* for instances with "-1pass", copy 1st pass result as final */
    /* execute stack-decoding search */
    /* they will be skipepd in the next pass */
    for (r = recog->process_list; r; r = r->next) {
        if (!r->live) continue;
        /* skip if 1st pass was failed */
        if (r->result.status < 0) continue;
        /* already stored on word recognition, so skip this */
        if (r->lmvar == LM_DFA_WORD) continue;
        if (r->config->compute_only_1pass) {
            if (verbose_flag) {
                jlog("%02d %s: \"-1pass\" specified, output 1st pass result as a final result\n", r->config->id, r->config->name);
            }
            /* prepare result storage */
            result_sentence_malloc(r, 1);
            /* finalize result when no hypothesis was obtained */
            pass2_finalize_on_no_result(r, TRUE);
        }
    }

    return JRECOGSTATUS_PASS1_END_SUCESS;
}

uint32_t
_recognize_stream_pass2_prepare(Recog* recog)
{
    uint32_t ret = 0;
    RecogProcess *r;

    /***********************************************/
    /* 2nd-pass --- forward search with heuristics */
    /***********************************************/
//    boolean pass2_p = FALSE;
    for (r = recog->process_list; r; r = r->next) {
        if (!r->live) continue;
        /* if [-1pass] is specified, skip 2nd pass */
        if (r->config->compute_only_1pass) continue;
        /* if search already failed on 1st pass, skip 2nd pass */
        if (r->result.status < 0) continue;
//        pass2_p = TRUE;
        ret |= JRECOGSTATUS_PASS2_DO;
        break;
    }
//    if (pass2_p) callback_exec(CALLBACK_EVENT_PASS2_BEGIN, recog);
    if (ret & JRECOGSTATUS_PASS2_DO == JRECOGSTATUS_PASS2_DO)
        callback_exec(CALLBACK_EVENT_PASS2_BEGIN, recog);

#if !defined(PASS2_STRICT_IWCD) || defined(FIX_35_PASS2_STRICT_SCORE)    
    /* adjust trellis score not to contain outprob of the last frames */
    for (r = recog->process_list; r; r = r->next) {
        if (!r->live) continue;
        /* if [-1pass] is specified, skip 2nd pass */
        if (r->config->compute_only_1pass) continue;
        /* if search already failed on 1st pass, skip 2nd pass */
        if (r->result.status < 0) continue;
        if (!r->am->hmminfo->multipath) {
            bt_discount_pescore(r->wchmm, r->backtrellis, r->am->mfcc->param);
        }
#ifdef LM_FIX_DOUBLE_SCORING
        if (r->lmtype == LM_PROB) {
            bt_discount_lm(r->backtrellis);
        }
#endif
    }
#endif
    return ret | JRECOGSTATUS_PASS2_BEGIN;
}

uint32_t
_recognize_stream_pass2_proc(Recog* recog)
{
    RecogProcess *r;

    /* execute stack-decoding search */
    for (r = recog->process_list; r; r = r->next) {
        if (!r->live) continue;
        /* if [-1pass] is specified, just copy from 1st pass result */
        if (r->config->compute_only_1pass) continue;
        /* if search already failed on 1st pass, skip 2nd pass */
        if (r->result.status < 0) continue;
        /* prepare result storage */
        if (r->lmtype == LM_DFA && r->config->output.multigramout_flag) {
            result_sentence_malloc(r, r->config->output.output_hypo_maxnum * multigram_get_all_num(r->lm));
        } else {
            result_sentence_malloc(r, r->config->output.output_hypo_maxnum);
        }
        /* do 2nd pass */
        if (r->lmtype == LM_PROB) {
            wchmm_fbs(r->am->mfcc->param, r, 0, 0);
        } else if (r->lmtype == LM_DFA) {
            if (r->config->output.multigramout_flag) {
                /* execute 2nd pass multiple times for each grammar sequencially */
                /* to output result for each grammar */
                MULTIGRAM *m;
                boolean has_success = FALSE;
                for (m = r->lm->grammars; m; m = m->next) {
                    if (m->active) {
                        jlog("STAT: execute 2nd pass limiting words for gram #%d\n", m->id);
                        wchmm_fbs(r->am->mfcc->param, r, m->cate_begin, m->dfa->term_num);
                        if (r->result.status == J_RESULT_STATUS_SUCCESS) {
                            has_success = TRUE;
                        }
                    }
                }
                r->result.status = (has_success == TRUE) ? J_RESULT_STATUS_SUCCESS : J_RESULT_STATUS_FAIL;
            } else {
                /* only the best among all grammar will be output */
                wchmm_fbs(r->am->mfcc->param, r, 0, r->lm->dfa->term_num);
            }
        }
    }

    /* do forced alignment if needed */
    for (r = recog->process_list; r; r = r->next) {
        if (!r->live) continue;
        /* if search failed on 2nd pass, skip this */
        if (r->result.status < 0) continue;
        /* do needed alignment */
        do_alignment_all(r, r->am->mfcc->param);
    }

    return JRECOGSTATUS_PASS2_END_SUCESS;
}

uint32_t
_recognize_stream_pass2_finish(Recog* recog, uint32_t ret)
{
    RecogProcess *r;

    uint8_t output_flag = 0; // 2 = graph_lattice, 4 = graph_confnet
    
    /* output result */
    callback_exec(CALLBACK_RESULT, recog);
#ifdef ENABLE_PLUGIN
    plugin_exec_process_result(recog);
#endif

    /* output graph */
    /* r->result.wg == NULL should be skipped inside the callback */
    /* output confnet */
    /* r->result.confnet == NULL should be skipped inside the callback */
    for (r = recog->process_list; r; r = r->next) {
        if (!r->live) continue;
        if (r->config->compute_only_1pass) continue;
        if (r->result.status < 0) continue;

        if (r->config->graph.lattice) output_flag |= 2;
        if (r->config->graph.confnet) output_flag |= 4;
    }

    if (output_flag & 2 != 0) callback_exec(CALLBACK_RESULT_GRAPH, recog);
    if (output_flag & 4 != 0) callback_exec(CALLBACK_RESULT_CONFNET, recog);

    /* clear work area for output */
    for (r = recog->process_list; r; r = r->next) {
        if (!r->live) continue;
        clear_result(r);
    }

    /* output end of 2nd pass */
//    if (pass2_p) callback_exec(CALLBACK_EVENT_PASS2_END, recog);
    if (ret & JRECOGSTATUS_PASS2_DO == JRECOGSTATUS_PASS2_DO)
        callback_exec(CALLBACK_EVENT_PASS2_END, recog);

    return JRECOGSTATUS_PASS2_END_SUCESS;
}

static uint32_t
_test_recognize_stream(Recog *recog)
{
    uint32_t ret = 0;
    // UPDATE process
    _recognize_stream_prepare(recog);
    
    //
    // 1st pass
    //
    // wave-input/1st-pass: begin
    ret |= _recognize_stream_pass1_begin(recog);
    if( (ret & JRECOGSTATUS_PASS1_END_FAIL) == JRECOGSTATUS_PASS1_END_FAIL ) {
        return ret;
    }

    // wave-input/1st-pass: process data
    {
        char* filename = "sample.le";
        SP16 buf[4096];
        size_t nRead = 0;

        fprintf(stderr, "PipeLine\n");
        FILE *fp = fopen(filename, "rb");
        while ((nRead = fread(buf, sizeof (SP16), 4096, fp)) > 0) {
            fprintf(stderr, "%d, ", (int) nRead);
            RealTimePipeLine(buf, nRead, recog);
        }
        fclose(fp);
    }

    // wave-input/1st-pass: end
    ret |= _recognize_stream_pass1_end(recog);
    
    // Check terminate signal
    if (recog->process_want_terminate) {
        result_error(recog, J_RESULT_STATUS_TERMINATE);
        return ret | JRECOGSTATUS_END_BY_REQTERM;
    }
    
    //
    // 2nd pass
    //
    ret |= _recognize_stream_pass2_prepare(recog);
    ret |= _recognize_stream_pass2_proc(recog);
    ret |= _recognize_stream_pass2_finish(recog, ret);

    return ret;
}



#endif	/* JULIUS_UTILS_H */

