#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <pthread.h>
#include "drv.h"

#define AD_BUFFER_SIZE  	8192	// 512 * 16
#define DA_BUFFER_SIZE  	512	// 512 * 1

#define		DEF_OUTPUT_FILENAME	"/tmp/record.wav"
#define		DEF_INPUT_FILENAME	"/tmp/record.wav"
#define		DEF_DEVICE_NAME		"/dev/kt"
#define		DEF_OUTPUT_CH		1
#define		MAX_CHANNEL_NUM		16
#define		AD_LEVEL_DISP_VAL	30000

static int ad_data[AD_BUFFER_SIZE];
static short da_data[DA_BUFFER_SIZE];
static int main_end_flag;
static char print_buf[256];
static int print_sig = 0;

typedef struct
{
    char           riff[4];     // = "RIFF"
    unsigned long  total_size;  // 全体サイズ
    char           fmt[8];      // "WAVEfmt "
    unsigned long  fmt_size;    // fmt チャンクサイズ
    unsigned short format;      // フォーマットの種類
    unsigned short channel;     // チャンネル
    unsigned long  rate;        // サンプリングレート
    unsigned long  avgbyte;     // rate * block
    unsigned short block;       // channels * bit / 8
    unsigned short bit;         // ビット数
    char           data[4];     // = "data"
    unsigned long  data_size;   // データサイズ
} WAVEFORMAT;

void sigint_action(int signum, siginfo_t *info, void *ctx)
{
    main_end_flag = 1;
}

void level_display( char *str, int ch, int val )
{
    int i;

    i = sprintf( str, "[%d] ", ch );
    str += i;
    for( i = 1 ; i < 11 ; i++ )
    {
        if( val > ( AD_LEVEL_DISP_VAL * i )) *str++ = '#';
        else *str++ = ' ';
    }
    *str++ = ' ';
    *str++ = '\0';
}

void *thread_printf(void *ptr)
{
    while ( !main_end_flag )
    {
        if( print_sig )
        {
            printf( "%s", print_buf );
            print_sig = 0;
        }
        
    }
    pthread_exit( NULL );
}

/*
 * mic_util -a => execute AD. [default : no AD]
 *          -o => set output-filename for AD data. [default : /tmp/record.wav]
 *          -s => set AD data save to file. [default : no save]
 *          -d => execute DA over PCM output. [default : no DA]
 *          -i => set input-filename for DA data. [default : /tmp/record.wav]
 *          -c => set channel for DA data. (when -i is set, this parameter means the channel of input-filename, when -i is not set, this paramete means the channel of AD data(AD->DA shortcut)) [default : ch-1]
 *          -p => print AD data on console. [default : no console output]
 *          -n => set device-name [default : /dev/kt]
 *
 *          channel is ch-1 to ch-16.
 *          output console is very slowly, so this program use thread for console output.(when -p paramter is set!)
 */

int print_usage ( void )
{
    printf("\n");
    printf(" mic_util - RASP-MX MIC Check Utility\n");
    printf("\n");
    printf("  parameter  |                             |   default\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -a        | do AD.                      | no AD\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -o [file] | set output wav file for AD. | /tmp/record.wav\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -s        | save AD to wav file.        | no save\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -d        | do DA.                      | no DA\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -f        | output the wav file on DA.  | DA source is AD\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -i [file] | set input wav file for DA.  | /tmp/record.wav\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -c [ch]   | set source channel for DA.  | 1\n");
    printf("             |  *with    -f > ch of input wav file\n");
    printf("             |  *without -f > ch of AD\n");
    printf("             |  *ch is from 1 to 16.\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -p        | print AD on console.        | no console output\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -n [dev]  | set dev-name.               | /dev/kt\n");
    printf(" ------------|-----------------------------|-------------------\n");
    printf("   -h        | print usage\n");
    printf("\n");
}

int main(int argc, char **argv)
{
    struct sigaction sa_sigint;
    
    int fd;
    int stat;
    
    int    ret_val;
    struct timeval time_out;
    fd_set rfds, wfds;
    
    int rbyte;
    int wbyte;
    
    char str[64];

    int i, j, rdcnt, smpl;
    int wave[MAX_CHANNEL_NUM], peek[MAX_CHANNEL_NUM];

    int result;
    int set_ad, set_da, output_ch, set_save, set_print, set_da_file, len;
    char output_filename[256], input_filename[256], device_name[256];
    const char on_off[][4] = { "OFF", "ON " };

    FILE *fp[2];
    fpos_t pos;
    WAVEFORMAT header[2];

    pthread_t tid;

    /* variable initialize */
    set_ad = 0;
    set_da = 0;
    output_ch = DEF_OUTPUT_CH;
    set_save = 0;
    set_print = 0;
    set_da_file = 0;
    strcpy( output_filename, DEF_OUTPUT_FILENAME );
    strcpy( input_filename, DEF_INPUT_FILENAME );
    strcpy( device_name, DEF_DEVICE_NAME );
    for( i = 0 ; i < MAX_CHANNEL_NUM ; i++ )
    {
        wave[i] = 0;
        peek[i] = 0;
    }
    rdcnt = 0;
    memset( &header[0], 0, sizeof(header[0]) );
    memset( &header[1], 0, sizeof(header[1]) );

    /* check command line parameter */
    while(( result = getopt( argc, argv, "ao:sdi:c:pn:h" )) != -1 )
    {
        switch( result )
        {
            case 'a':
                set_ad = 1;
                break;
            case 'o':
                strcpy( output_filename, optarg );
                break;
            case 's':
                set_save = 1;
                break;
            case 'd':
                set_da = 1;
                break;
            case 'i':
                strcpy( input_filename, optarg );
                set_da_file = 1;
                break;
            case 'c':
                output_ch = atoi( optarg );
                break;
            case 'p':
                set_print = 1;
                break;
            case 'n':
                strcpy( device_name, optarg );
                break;
            case 'h':
            case '?':
                print_usage();
                goto FIN;
        }
    }

    if( !set_ad && !set_da )
    {
        print_usage();
        goto FIN;
    }

    /* print parameters */
    printf("\n");
    printf(" AD: %s   [SAVE: %s   OUT: %s   PRINT:%s]\n", on_off[set_ad], on_off[set_save], output_filename, on_off[set_print] );
    printf(" DA: %s   [IN: %s   CH: %d]\n", on_off[set_da], input_filename, output_ch );
    printf("DEV: %s\n", device_name );
    printf("\n");

    /* set signal interrupt handler */
    memset( &sa_sigint, 0, sizeof(sa_sigint) );
    sa_sigint.sa_sigaction = sigint_action;
    sa_sigint.sa_flags = SA_RESTART | SA_SIGINFO;
    if ( sigaction( SIGINT, &sa_sigint, NULL ) < 0 )
    {
        printf("sigaction error.\n");
    }

    /* thread create */
    if( set_print )
    {
        if( pthread_create( &tid, NULL, thread_printf, NULL ) != 0 )
        {
            printf("pthread_create error!\n");
            goto FIN;
        }
    }

    /* wavefie header */
    strncpy( header[0].riff, "RIFF", 4 );
    header[0].total_size = sizeof( header[0] );
    strncpy( header[0].fmt, "WAVEfmt ", 8 );
    header[0].fmt_size = 16;
    header[0].format = 1;	/* PCM */
    header[0].channel = MAX_CHANNEL_NUM;
    header[0].rate = 16000;
    header[0].bit = 24;
    header[0].block = header[0].channel * header[0].bit / 8;
    header[0].avgbyte = header[0].rate * header[0].block;
    strncpy( header[0].data, "data", 4 );
    header[0].data_size = 0;
    header[0].total_size = sizeof( header[0] );

    if( set_save )
    {
        /* Open file */
        if ((fp[0] = fopen(output_filename,"wb")) == NULL ) {
            printf("file open error!\n");
            goto FIN;
        }
        fgetpos( fp[0], &pos );
        if( fwrite( &header[0], sizeof( header[0] ), 1, fp[0] ) < 1 ) {
            printf("file write error!\n");
            goto FIN;
        }
    }

    /* device open */
    fd = open( device_name, O_RDWR, 0666 );
    if( fd < 0 )
    {
        printf( "%s open error.\n", device_name );
        return -1;
    }

    if( set_ad )
    {
        /* AD Start */
        stat = ioctl( fd, FIO_AD_START );
        if( stat < 0 )
        {
            printf("ioctl error! (%d)\n", stat);
            goto FIN;
        }
    }
    /* main loop */    
    while( !main_end_flag )
    {
        rdcnt++;
        for( i = 0 ; i < 7 ; i++ )
        {
            peek[i] = rdcnt & 0xfffff;
        }
        if(( rdcnt % 699999 ) == 0 )
                {
                    for( j = 0, len = 0 ; j < MAX_CHANNEL_NUM ; j++ )
                    {
                        if( peek[j] )
                        {
                            level_display( str, j + 1, peek[j] );
                            strcpy( &print_buf[len], str );
                            len += strlen( str );
                            peek[j] = 0;
                        }
                    }
                    print_buf[len++] = '\n';
                    print_buf[len++] = '\0';
                    print_sig = 1;
                }
    }

    /* main loop */    
    while( !main_end_flag )
    {
        do
        {
            FD_ZERO( &rfds );
            FD_ZERO( &wfds );
            FD_SET( fd, &rfds );
            FD_SET( fd, &wfds );
            
            time_out.tv_sec  = 0;
            time_out.tv_usec = 10 * 1000;
            ret_val = select( fd + 1, &rfds, &wfds, NULL, &time_out );
        }while( ret_val == 0 );
        
        if( ret_val < 0 )
        {
            printf("ret_val = %d\n", ret_val);
            goto FIN;
        }

        /* データ取得処理 */
        if( FD_ISSET( fd, &rfds ))
        {
            rbyte = read(fd, &ad_data[0], sizeof(ad_data));
            smpl = rbyte / sizeof(int) / MAX_CHANNEL_NUM;
            for( i = 0 ; i < smpl ; i++ )
            {
                for( j = 0 ; j < MAX_CHANNEL_NUM ; j++ )
                {
                    wave[j] = ad_data[i * MAX_CHANNEL_NUM + j];
                    if( abs(wave[j]) > abs(peek[j]) )
                        peek[j] = abs(wave[j]);
                    if( set_save )
                    {
                        if( fwrite( &wave[j], header[0].bit / 8, 1, fp[0] ) < 1 )
                        {
                            printf("file full!\n");
                            goto FIN;
                        }
                        header[0].data_size += ( header[0].bit / 8 );
                    }
                }
                rdcnt++;
                if(( rdcnt % 1000 ) == 0 )
                {
                    for( j = 0, len = 0 ; j < MAX_CHANNEL_NUM ; j++ )
                    {
                        if( peek[j] )
                        {
                            level_display( str, j + 1, peek[j] );
                            strcpy( &print_buf[len], str );
                            len += strlen( str );
                            peek[j] = 0;
                        }
                    }
                    print_buf[len++] = '\n';
                    print_buf[len++] = '\0';
                    print_sig = 1;
                }
            }
        }
    }

    if( set_ad )
    {
        /* AD Stop */
        stat = ioctl(fd, FIO_AD_STOP);
        if(stat < 0)
        {
	    printf("ioctl error! (%d)\n", stat);
        }
    }

FIN:
    if( fd )
    {
        close(fd);
    }

    if( set_save && fp[0] )
    {
        /* Write Wave Header */
        header[0].total_size += header[0].data_size;
        fsetpos( fp[0], &pos );
        fwrite( &header[0], sizeof( header[0] ), 1, fp[0] );

        /* Close device */
        fclose( fp[0] );
    }

    if( tid )
    {
        main_end_flag = 1;
        pthread_join( tid, NULL );
    }
    
    return 0;
}

/* EOF */

