/*
 *	Bitrate histogram source file
 *
 *	Copyright (c) 2000 Mark Taylor
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Library General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.	 See the GNU
 * Library General Public License for more details.
 *
 * You should have received a copy of the GNU Library General Public
 * License along with this library; if not, write to the
 * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

/* $Id: brhist.c,v 1.36 2001/03/26 00:38:15 markt Exp $ */

#ifdef BRHIST

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

/* basic #define's */

#ifndef BRHIST_WIDTH
# define BRHIST_WIDTH    14
#endif
#ifndef BRHIST_RES
# define BRHIST_RES      14
#endif


/* #includes */

#ifdef STDC_HEADERS
# include <stdlib.h>
# include <string.h>
#else
# ifndef HAVE_STRCHR
#  define strchr index
#  define strrchr rindex
# endif
char *strchr (), *strrchr ();
# ifndef HAVE_MEMCPY
#  define memcpy(d, s, n) bcopy ((s), (d), (n))
#  define memmove(d, s, n) bcopy ((s), (d), (n))
# endif
#endif


#if defined(HAVE_TERMCAP)
#if defined(HAVE_TERMCAP_H)
# include <termcap.h>
#elif defined(HAVE_NCURSES_TERMCAP_H)
# include <ncurses/termcap.h>
#endif
#endif

#include "brhist.h"

#ifdef WITH_DMALLOC
#include <dmalloc.h>
#endif

/* Structure holding all data related to the Console I/O 
 * may be this should be a more global frontend structure. So it
 * makes sense to print all files instead with
 * printf ( "blah\n") with printf ( "blah%s\n", Console_IO.str_clreoln );
 */

Console_IO_t Console_IO;

static struct {
    int     vbr_bitrate_min_index;
    int     vbr_bitrate_max_index;
    int     kbps [BRHIST_WIDTH];
    int     hist_printed_lines;
    char    bar_asterisk [512 + 1];	/* buffer filled up with a lot of '*' to print a bar     */
    char    bar_percent  [512 + 1];	/* buffer filled up with a lot of '%' to print a bar     */
} brhist;

static int calculate_index ( const int* const array, const int len, const int value )
{
    int i;
    
    for ( i = 0; i < len; i++ )
        if ( array [i] == value )
	    return i;
    return -1;
}

int  brhist_init ( const lame_global_flags* gf, const int bitrate_kbps_min, const int bitrate_kbps_max )
{
#ifdef HAVE_TERMCAP
    char         term_buff [2048];  // see 1)
    const char*  term_name;
    char*        tp;
    char         tc [10];
    int          val;
#endif

    /* setup basics of brhist I/O channels */
    Console_IO.disp_width  = 80;
    Console_IO.disp_height = 25;
    brhist.hist_printed_lines = 0;
    Console_IO.Console_fp  = stderr;
    Console_IO.Error_fp    = stderr;
    Console_IO.Report_fp   = stderr;

    setvbuf ( Console_IO.Console_fp, Console_IO.Console_buff, _IOFBF, sizeof (Console_IO.Console_buff) );
//  setvbuf ( Console_IO.Error_fp  , NULL                   , _IONBF, 0                                );

#if defined(_WIN32)  &&  !defined(__CYGWIN__) 
    Console_IO.Console_Handle = GetStdHandle (STD_ERROR_HANDLE);
#endif

    strcpy ( Console_IO.str_up, "\033[A" );
    
    /* some internal checks */
    if ( bitrate_kbps_min > bitrate_kbps_max ) {
	fprintf ( Console_IO.Error_fp, "lame internal error: VBR min %d kbps > VBR max %d kbps.\n", bitrate_kbps_min, bitrate_kbps_max );
	return -1;
    }
    if ( bitrate_kbps_min < 8  ||  bitrate_kbps_max > 320 ) {
	fprintf ( Console_IO.Error_fp, "lame internal error: VBR min %d kbps or VBR max %d kbps out of range.\n", bitrate_kbps_min, bitrate_kbps_max );
	return -1;
    }

    /* initialize histogramming data structure */
    lame_bitrate_kbps ( gf, brhist.kbps );
    brhist.vbr_bitrate_min_index = calculate_index ( brhist.kbps, BRHIST_WIDTH, bitrate_kbps_min );
    brhist.vbr_bitrate_max_index = calculate_index ( brhist.kbps, BRHIST_WIDTH, bitrate_kbps_max );

    if ( brhist.vbr_bitrate_min_index >= BRHIST_WIDTH  ||  
         brhist.vbr_bitrate_max_index >= BRHIST_WIDTH ) {
	fprintf ( Console_IO.Error_fp, "lame internal error: VBR min %d kbps or VBR max %d kbps not allowed.\n", bitrate_kbps_min, bitrate_kbps_max );
	return -1;
    }

    memset (brhist.bar_asterisk, '*', sizeof (brhist.bar_asterisk)-1 );
    memset (brhist.bar_percent , '%', sizeof (brhist.bar_percent) -1 );

#ifdef HAVE_TERMCAP
    /* try to catch additional information about special console sequences */
    
    if ((term_name = getenv("TERM")) == NULL) {
	fprintf ( Console_IO.Error_fp, "LAME: Can't get \"TERM\" environment string.\n" );
	return -1;
    }
    if ( tgetent (term_buff, term_name) != 1 ) {
	fprintf ( Console_IO.Error_fp, "LAME: Can't find termcap entry for terminal \"%s\"\n", term_name );
	return -1;
    }
    
    val = tgetnum ("co");
    if ( val >= 40  &&  val <= 512 )
        Console_IO.disp_width   = val;
    val = tgetnum ("li");
    if ( val >= 16  &&  val <= 256 )
        Console_IO.disp_height  = val;
        
    *(tp = tc) = '\0';
    tp = tgetstr ("up", &tp);
    if (tp != NULL)
        strcpy ( Console_IO.str_up, tp );

    *(tp = tc) = '\0';
    tp = tgetstr ("ce", &tp);
    if (tp != NULL)
        strcpy ( Console_IO.str_clreoln, tp );

    *(tp = tc) = '\0';
    tp = tgetstr ("md", &tp);
    if (tp != NULL)
        strcpy ( Console_IO.str_emph, tp );

    *(tp = tc) = '\0';
    tp = tgetstr ("me", &tp);
    if (tp != NULL)
        strcpy ( Console_IO.str_norm, tp );
        
#endif /* HAVE_TERMCAP */

    return 0;
}

static int  digits ( unsigned number )
{
    int  ret = 1;
    
    if ( number >= 100000000 ) { ret += 8; number /= 100000000; }
    if ( number >=     10000 ) { ret += 4; number /=     10000; }
    if ( number >=       100 ) { ret += 2; number /=       100; }
    if ( number >=        10 ) { ret += 1;                      }
    
    return ret;
}


static void  brhist_disp_line ( const lame_global_flags*  gf, int i, int br_hist_TOT, int br_hist_LR, int full, int frames )
{
    char    brppt [14];  /* [%] and max. 10 characters for kbps */
    int     barlen_TOT;
    int     barlen_LR;
    int     ppt  = 0;

    if ( full != 0 ) {
        // some problems when br_hist_TOT \approx br_hist_LR: You can't see that there are still MS frames
        barlen_TOT = (br_hist_TOT * (Console_IO.disp_width-BRHIST_RES) + full-1 ) / full;  /* round up */
        barlen_LR  = (br_hist_LR  * (Console_IO.disp_width-BRHIST_RES) + full-1 ) / full;  /* round up */
    } else {
        barlen_TOT = barlen_LR = 0;
    }

    if (frames > 0)
        ppt = (1000 * br_hist_TOT + frames/2) / frames;                                  /* round nearest */

    // bar graph gives a visual indication of percentages.
    // so lets not print perswitched back 
    // mt 11/00
#if 0
    if ( br_hist_TOT == 0 )
        sprintf ( brppt,  " [   ]" );
    else if ( ppt < br_hist_TOT/10000 )
        sprintf ( brppt," [%%..]" );
    else if ( ppt <  10 )
        sprintf ( brppt," [%%.%1u]", ppt );
    else if ( ppt < 995 )
        sprintf ( brppt, " [%2u%%]", (ppt+5)/10 );
    else
        sprintf ( brppt, "[%3u%%]", (ppt+5)/10 );
#else
    sprintf ( brppt, " [%*i]", digits(frames), br_hist_TOT );
#endif
          
    if ( Console_IO.str_clreoln [0] ) /* ClearEndOfLine available */
        fprintf ( Console_IO.Console_fp, "\n%3d%s %.*s%.*s%s", 
	          brhist.kbps [i], brppt, 
                  barlen_LR, brhist.bar_percent, 
                  barlen_TOT - barlen_LR, brhist.bar_asterisk, 
		  Console_IO.str_clreoln );
    else
        fprintf ( Console_IO.Console_fp, "\n%3d%s %.*s%.*s%*s", 
	          brhist.kbps [i], brppt, 
                  barlen_LR, brhist.bar_percent, 
                  barlen_TOT - barlen_LR, brhist.bar_asterisk, 
		  Console_IO.disp_width - BRHIST_RES - barlen_TOT, "" );
		  
    brhist.hist_printed_lines++;
}


/* Yes, not very good */
#define LR  0
#define MS  2

void  brhist_disp ( const lame_global_flags*  gf )
{
    int   i;
    int   br_hist [BRHIST_WIDTH];       /* how often a frame size was used */
    int   br_sm_hist [BRHIST_WIDTH] [4];/* how often a special frame size/stereo mode commbination was used */
    int   frames;                       /* total number of encoded frames */
    int   most_often;                   /* usage count of the most often used frame size, but not smaller than Console_IO.disp_width-BRHIST_RES (makes this sense?) and 1 */
    
    brhist.hist_printed_lines = 0;  /* printed number of lines for the brhist functionality, used to skip back the right number of lines */
	
    lame_bitrate_hist             ( gf, br_hist    );
    lame_bitrate_stereo_mode_hist ( gf, br_sm_hist );

    frames = most_often = 0;
    for (i = 0; i < BRHIST_WIDTH; i++) {
        frames += br_hist[i];
        if (most_often < br_hist[i]) most_often = br_hist[i];
    }

    for ( i = 0; i < BRHIST_WIDTH; i++ )
        if ( br_hist [i]  ||  (i >= brhist.vbr_bitrate_min_index  &&  i <= brhist.vbr_bitrate_max_index) )
            brhist_disp_line ( gf, i, br_hist [i], br_sm_hist [i][LR], most_often, frames );

    fputs ( "\r", Console_IO.Console_fp );
    fflush ( Console_IO.Console_fp );   /* fflush is ALSO needed for Windows! */
}

void brhist_jump_back( void )
{
#if defined(_WIN32)  &&  !defined(__CYGWIN__) 
    if ( GetFileType (Console_IO.Console_Handle) != FILE_TYPE_PIPE ) {
        COORD                       Pos;
        CONSOLE_SCREEN_BUFFER_INFO  CSBI;
	
        GetConsoleScreenBufferInfo ( Console_IO.Console_Handle, &CSBI );
        Pos.Y = CSBI.dwCursorPosition.Y - brhist.hist_printed_lines ;
        Pos.X = 0;
        SetConsoleCursorPosition ( Console_IO.Console_Handle, Pos );
    }
#else
    while ( brhist.hist_printed_lines-- > 0 )
        fputs ( Console_IO.str_up, Console_IO.Console_fp );
#endif
}


void  brhist_disp_total ( const lame_global_flags* gf )
{
    int i;
    int br_hist [BRHIST_WIDTH];
    int st_mode [4];
    int st_frames = 0;
    int br_frames = 0;
    double sum = 0.;
    
    lame_stereo_mode_hist ( gf, st_mode );
    lame_bitrate_hist     ( gf, br_hist );
    
    for (i = 0; i < BRHIST_WIDTH; i++) {
        br_frames += br_hist[i];
	sum       += br_hist[i] * brhist.kbps[i];
    }

    for (i = 0; i < 4; i++) {
        st_frames += st_mode[i];
    }

    if (0==br_frames) return;

    fprintf ( Console_IO.Console_fp, "\naverage: %5.1f kbps", sum / br_frames);

    // I'm very unhappy because this is only printed out in VBR modes
    if (st_frames > 0) {
        if ( st_mode[LR] > 0 )
            fprintf ( Console_IO.Console_fp, "   LR: %d (%#5.4g%%)", st_mode[LR], 100. * st_mode[LR] / st_frames );
        else
            fprintf ( Console_IO.Console_fp, "                 " );
        if ( st_mode[MS] > 0 )
            fprintf ( Console_IO.Console_fp, "   MS: %d (%#5.4g%%)", st_mode[MS], 100. * st_mode[MS] / st_frames );
    }
    fprintf ( Console_IO.Console_fp, "\n" );
    fflush  ( Console_IO.Console_fp );
}

/* 
 * 1)
 *
 * Taken from Termcap_Manual.html:
 *
 * With the Unix version of termcap, you must allocate space for the description yourself and pass
 * the address of the space as the argument buffer. There is no way you can tell how much space is
 * needed, so the convention is to allocate a buffer 2048 characters long and assume that is
 * enough.  (Formerly the convention was to allocate 1024 characters and assume that was enough.
 * But one day, for one kind of terminal, that was not enough.)
 */

#endif
/* end of brhist.c */
