#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <limits.h>

/*===========================================================*/
/* I/O                                                       */
/*===========================================================*/
void printbc(unsigned char v) {
    unsigned char mask = (char)1 << (sizeof(v) * CHAR_BIT - 1);
    do putchar(mask & v ? '1' : '0');
    while (mask >>= 1);
}

void printb(unsigned int v) {
    unsigned int mask = (int)1 << (sizeof(v) * CHAR_BIT - 1);
    do putchar(mask & v ? '1' : '0');
    while (mask >>= 1);
}

void putb(unsigned int v) {
  putchar('0'), putchar('b'), printb(v), putchar('\n');
}

char *read_str( FILE *fp,
                int len ){
    char *str = malloc(sizeof(char)*len);
    if( fread( str, sizeof(char), len, fp ) < len ){
        printf("Error while reading.\n");
        exit( EXIT_FAILURE );
    }
    return str;
}

int read_char( FILE *fp ){
    char c;
    if( fread( &c, sizeof(c), 1, fp ) < 1 ){
        printf("Error while reading.\n");
        exit( EXIT_FAILURE );
    }
    return c;
}

int read_uchar( FILE *fp ){
    unsigned char c;
    if( fread( &c, sizeof(c), 1, fp ) < 1 ){
        printf("Error while reading.\n");
        exit( EXIT_FAILURE );
    }
    return c;
}

short read_short( FILE *fp ){
    short n;
    if( fread( &n, sizeof(n), 1, fp ) < 1 ){
        printf("Error while reading.\n");
        exit( EXIT_FAILURE );
    }
    return n;
}

int read_int( FILE *fp ){
    int n;
    if( fread( &n, sizeof(n), 1, fp ) < 1 ){
        printf("Error while reading.\n");
        exit( EXIT_FAILURE );
    }
    return n;
}

int read_double( FILE *fp ){
    double f;
    if( fread( &f, sizeof(f), 1, fp ) < 1 ){
        printf("Error while reading.\n");
        exit( EXIT_FAILURE );
    }
    return f;
}


/*===========================================================*/
/* Decoder                                                   */
/*===========================================================*/
short  *levels;         /* レベル・雨量値換算表 */

struct{
    unsigned char level;        /* レベル値 */
    unsigned char rep;          /* 反復数   */
} tableLevRep[128];             /* レベル-反復数表 */

int unpack( unsigned char *data,    /* 圧縮されたデータ */
            int            size,    /* 圧縮されたデータのバイト数 */
            short         *out ){  /* 雨量値に展開されたデータ */
    int ii, ip = 0, op = 0;
    unsigned char d;
    /*printf("0x80: "); printb(0x80); printf("\n");
    printf("0xE0: "); printb(0xE0); printf("\n");
    printf("0xC0: "); printb(0xC0); printf("\n");*/
    while( ip < size ) {
        /*printf( "ip: %d, op: %d\n", ip, op);*/
        d = data[ip++];
        /*printf("d       : "); printbc(d); printf( " %d", d); printf("\n");
        printf("d & 0x80: "); printbc(d&0x80); printf("\n");
        printf("d & 0xE0: "); printbc(d&0xE0); printf("\n");
        printf("d & 0xC0: "); printbc(d&0xC0); printf("\n");*/

        if( ( d & 0x80 ) == 0 ) {             /* (a) */
            for( ii = tableLevRep[d].rep + 2 ; ii > 0 ; ii-- )
                out[op++] = levels[ tableLevRep[d].level ];

        } else if( ( d & 0xE0 ) == 0xC0 ) {   /* (b) */
            for( ii = data[ip++] + 2 ; ii > 0; ii-- )
                out[op++] = levels[ d & 0x1F ];

        } else if( ( d & 0xC0 ) == 0x80 ) {   /* (c) */
            out[op++] = levels[ d & 0x3F ];

        } else if( d == 0xFE ) {              /* (d) */
            out[op++] = levels[ data[ip++] ];

        } else {
            fprintf( stderr, "解凍できませんでした\n" );
            exit(1);
        }
    }
    return op;
}

/*===========================================================*/
/* Main                                                      */
/*===========================================================*/
int main( int argc, char *argv[] ){

    if( argc != 3 ){
        fprintf( stderr, "Invalid number of argument.\n" );
        fprintf( stderr, "[Usage] PROGRAM file_inp file_out\n" );
        exit( EXIT_FAILURE );
    }
    char *file_inp = argv[1];
    char *file_out = argv[2];

    FILE *fp = fopen( file_inp, "rb" );
    if( fp == NULL ){
        fprintf( stderr, "Can't open a file: %s\n", file_inp );
        exit( EXIT_FAILURE );
    }

    FILE *wfp = fopen( file_out, "wb" );
    if( wfp == NULL ){
        printf( "Can't open a file: %s\n", file_out );
        exit( EXIT_FAILURE );
    }

    /*=======================================================*/
    /* Read header                                           */
    /*=======================================================*/
    char *identifier     = read_str( fp, 6 );
    char *version        = read_str( fp, 5 );
    char *editor_comment = read_str( fp, 66 );

    unsigned char num1 = read_uchar( fp );
    unsigned char num2 = read_uchar( fp );
    unsigned char num3 = read_uchar( fp );

    printf( "識別子        : %s\n", identifier );
    printf( "版番号        : %s\n", version );
    printf( "作成者コメント: %s\n", editor_comment );

    printf( "<CR>   %d\n", num1 );
    printf( "<LF>   %d\n", num2 );
    printf( "<NULL> %d\n", num3 );

    int nmax_data;
    fread( &nmax_data, sizeof(nmax_data), 1, fp );
    printf( "データ数: %d\n", nmax_data );

    short YYYY;
    unsigned char MM;
    unsigned char DD;
    unsigned char hh;
    unsigned char mm;
    short kind;
    char *spare_index;
    int position;

    int i;
    int imax = nmax_data;
    for( i=0; i<imax; i++ ){
        YYYY        = read_short( fp );
        MM          = read_uchar( fp );
        DD          = read_uchar( fp );
        hh          = read_uchar( fp );
        mm          = read_uchar( fp );
        kind        = read_short( fp );
        spare_index = read_str( fp, 8 );
        position    = read_int( fp );
        printf( "%2d %04d/%02d/%02d_%02d:%02d\n", i,YYYY,MM,DD,hh,mm );
    }

    char *spare_grid1 = read_str( fp, 2);
    short map_type    = read_short( fp );
    int lon           = read_int( fp );
    int lat           = read_int( fp );
    int intvl_h       = read_int( fp );
    int intvl_v       = read_int( fp );
    short hmax        = read_short( fp );
    short vmax        = read_short( fp );
    char *spare_grid2 = read_str( fp, 16 );

    printf( "地図種別: %d\n", map_type );
    printf( "経度: %d, 緯度: %d\n", lon, lat );
    printf( "横格子間隔: %d, 縦格子間隔: %d\n", intvl_h, intvl_v );
    printf( "横格子数: %d, 縦格子数: %d\n", hmax, vmax );
    /*printf(  "%s\n", spare_grid2 );*/

    short compress_mode = read_short( fp );
    short level_max     = read_short( fp );
    printf( "圧縮方法: %d\n", compress_mode );
    printf( "レベル数: %d\n", level_max );

    levels = malloc(sizeof(short)*level_max);

    printf( "  レベル値 雨量(0.1 mm)\n");
    imax = level_max;
    for( i=0; i<imax; i++ ){
        short val = read_short( fp );
        levels[i] = val;
        printf( "  %d %d\n", i,val );
    }

    short size_table = read_short( fp );
    printf( "表の大きさ: %d\n", size_table );

    imax = size_table;
    printf( "  No.  レベル値 反復数-2 \n");
    for( i=0; i<imax; i++ ){
        unsigned char level = read_uchar( fp );
        unsigned char repeat = read_uchar( fp );
        tableLevRep[i].level = level;
        tableLevRep[i].rep = repeat;
        printf( "%3d %4d %6d\n", i, level, repeat );
    }

    /*=======================================================*/
    /* Read data                                             */
    /*=======================================================*/
    printf( "Decoding.\n" );

    int n_data;
    for( n_data=0; n_data<nmax_data; n_data++ ){

        printf( "Data %2d\n", n_data );

        /* 圧縮データのサイズを取得 */
        int size = read_int( fp );
        printf( "  圧縮後の大きさ: %d\n", size );

        /* 圧縮データ読み込み */
        unsigned char data[size];
        imax = size;
        for( i=0; i<imax; i++ ){
            data[i] = read_uchar( fp );
        }

        /* 解凍 */
        short out[hmax*vmax];
        if( unpack( data, size, out) != hmax*vmax ){
            printf ( "Extended data size is not equal to hmax*vmax." );
            exit( EXIT_FAILURE );
        }

        /* 書き出し */
        if( fwrite( out, sizeof(out[0]), hmax*vmax, wfp ) < hmax*vmax ){
            printf( "Writing failed.\n" );
            exit( EXIT_FAILURE );
        }

        /* レーダー運用状況等取得 */
        char *state = read_str( fp, 8 );
        int n_amedas = read_int( fp );
        printf( "  解析に使用したアメダスの総数: %d\n", n_amedas );

    }

    if( fclose( wfp ) == EOF ){
        fprintf( stderr, "File closing failed.\n" );
        exit( EXIT_FAILURE );
    }

    return 0;
}