#include <stdio.h>

#include "utf_tab.h"
#include "cpk_uncompress.h"
#include "util.h"
#include "error_stuff.h"

void analyze_CRID(reader_t *infile, const char *infile_name, long file_length, int verbosity);

void usage(void)
{
    fflush(stdout);
    fprintf(stderr,
        "Incorrect program usage\n\nusage: usm_deinterleave file [-v|-vv|-q]\n\n"
        "-v  : verbose (header info)\n"
        "-vv : more verbose (header and block info)\n"
        "-q  : quiet (only output on error)\n");
}

enum
{
    verbose_quiet,
    verbose_normal,
    verbose_headers,
    verbose_blocks
};

int main(int argc, char **argv)
{
    int verbosity = verbose_normal;
    int args_ok = 0;

    if (argc == 2)
    {
    args_ok = 1;
    }
    else if (argc == 3)
    {
        if (!strcmp(argv[2],"-v"))
        {
            verbosity = verbose_headers;
            args_ok = 1;
        }
        else if (!strcmp(argv[2],"-vv"))
        {
            verbosity = verbose_blocks;
            args_ok = 1;
        }
        else if (!strcmp(argv[2],"-q"))
        {
            verbosity = verbose_quiet;
            args_ok = 1;
        }
    }

    if (verbosity >= verbose_normal)
    {
        printf("usm_deinterleave " VERSION "\n\n");
    }

    if (!args_ok)
    {
        usage();
        exit(EXIT_FAILURE);
    }

    /* open file */
    reader_t *infile = open_reader_file(argv[1]);
    CHECK_ERRNO(!infile, "fopen");

    long file_length = reader_length(infile);

    analyze_CRID(infile, argv[1], file_length, verbosity);

    exit(EXIT_SUCCESS);
}

void analyze_CRID(reader_t *infile, const char *infile_name, long file_length, int verbosity)
{
    long stream_count = 0;
    struct stream_info *streams = NULL;
    char *CRIUSF_string_table = NULL;

    FILE **outfiles = NULL;
    char **outfile_names = NULL;

    enum
    {
        CRID_stmid = 0x43524944,  /* CRID */
        SFV_stmid = 0x40534656,   /* @SFV */
        SFA_stmid = 0x40534641    /* @SFA */
    };

    struct stream_info
    {
        const char *filename;
        uint32_t filesize;
        uint32_t datasize;
        uint32_t stmid;
        uint16_t chno;
        uint16_t minchk;
        uint32_t minbuf;
        uint32_t avbps;

        long bytes_read;
        long payload_bytes;
        unsigned int alive;
    };


    int live_streams = 0;
    int streams_setup = 0;

    /* dispense justice! */
    do
    {
        uint32_t stmid = get_32_be(infile);
        uint32_t block_size = get_32_be(infile);
        uint32_t block_type;
        uint16_t header_size, footer_size;
        size_t payload_bytes;
        int stream_idx = 0;

        if (!streams_setup)
        {
            CHECK_ERROR (CRID_stmid != stmid, "file should start with CRID");
        }
        else /* streams already set up */
        {
            CHECK_ERROR (0 == stream_count, "0 stream count should be impossible");
            /* find the stream */
            for (stream_idx=1; stream_idx < stream_count; stream_idx++)
            {
                if (stmid == streams[stream_idx].stmid)
                {
                    break;
                }
            }
            CHECK_ERROR (stream_idx == stream_count, "unknown stmid");

            CHECK_ERROR (!streams[stream_idx].alive, "stream was supposed to be ended");

            streams[stream_idx].bytes_read += block_size;
        }

        if (verbosity >= verbose_blocks)
        {
            printf("%08lx %d block_size: %08"PRIx32" ", (unsigned long)reader_tell(infile)-8, stream_idx, block_size);
        }

        /* block control */
        {
            header_size = get_16_be(infile);
            footer_size = get_16_be(infile);
            if (verbosity >= verbose_blocks)
            {
                printf("%04"PRIx16" %04"PRIx16"\n", header_size, footer_size);
            }

            CHECK_ERROR( 0x18 != header_size, "expected header size 0x18" );
            payload_bytes = block_size - header_size - footer_size;
        }

        /* block typs */
        block_type = get_32_be(infile);
        if (verbosity >= verbose_blocks)
        {
            printf("type %08"PRIx32"\n", block_type);
        }

        /* check the rest of the block header */
        {
            uint32_t byte1,byte2,byte3,byte4;

            byte1 = get_32_be(infile); /* granule (1/100 of a frame) */
            byte2 = get_32_be(infile); /* samples (based on whole block size at avg bitrate) */
            byte3 = get_32_be(infile);
            byte4 = get_32_be(infile);

            if (verbosity >= verbose_blocks)
            {
                printf("%08"PRIx32" %08"PRIx32" %08"PRIx32" %08"PRIx32"\n\n",
                        byte1,byte2,byte3,byte4);
            }

            CHECK_ERROR(
                    (//(0    != byte1) ||
                     //(0x1E != byte2) ||
                     (0    != byte3) ||
                     (0    != byte4)), "block unknown bytes mismatch");
        }

        if (!streams_setup)
        {
            /* handle first block, which describes the subsequent streams */

            long CRIUSF_offset = reader_tell(infile);
            CHECK_ERROR (-1 == CRIUSF_offset, "ftell");

            /* seems like it ought to be type 2, but it's type 1 */
            CHECK_ERROR (1 != block_type, "CRID should be type 1");

            if (verbosity >= verbose_headers)
            {
                analyze_utf(infile, CRIUSF_offset, 0, 1, NULL);
            }

            /* check CRIUSF stream list */
            {
                struct utf_query_result result = query_utf_nofail(infile, CRIUSF_offset, NULL);
                long CRIUSF_data_offset;

                CHECK_ERROR (result.rows < 1, "expected at least one row in CRIUSF");
                stream_count = result.rows;
                CRIUSF_data_offset = CRIUSF_offset + result.data_offset;

                CRIUSF_string_table = load_utf_string_table(infile, CRIUSF_offset);

                /* check that we're actually looking at a CRIUSF table */
                CHECK_ERROR (strcmp(&CRIUSF_string_table[result.name_offset],
                            "CRIUSF_DIR_STREAM"), "expected CRIUSF_DIR_STREAM");

            }

            /* check streams */
            {
                int i, j;

                streams = malloc(sizeof(struct stream_info)*stream_count);
                CHECK_ERRNO (!streams, "malloc");
                memset(streams, 0, sizeof(struct stream_info)*stream_count);

                for (i = 0; i < stream_count; i ++)
                {
                    struct stream_info * const s = &streams[i];
                    s->filename = query_utf_string(infile, CRIUSF_offset, i, "filename", CRIUSF_string_table);
                    s->filesize = query_utf_4byte(infile, CRIUSF_offset, i, "filesize");
                    s->datasize = query_utf_4byte(infile, CRIUSF_offset, i, "datasize");
                    s->stmid    = query_utf_4byte(infile, CRIUSF_offset, i, "stmid");
                    s->chno     = query_utf_2byte(infile, CRIUSF_offset, i, "chno");
                    s->minchk   = query_utf_2byte(infile, CRIUSF_offset, i, "minchk");
                    s->minbuf   = query_utf_4byte(infile, CRIUSF_offset, i, "minbuf");
                    s->avbps    = query_utf_4byte(infile, CRIUSF_offset, i, "avbps");

                    if (0 == i)
                    {
                        CHECK_ERROR ((0 != s->stmid), "expected 0 stmid for stream 0");

                        /* does this indicate that the normal type is signed, and
                           type 2 is unsigned? */
                        CHECK_ERROR ((65535 != s->chno), "expected -1 chno");
                    }
                    else
                    {
                        switch (s->stmid)
                        {
                            case SFV_stmid:
                                if (verbosity >= verbose_normal)
                                {
                                    printf("Stream %d: Video\n", i);
                                }
                                break;
                            case SFA_stmid:
                                if (verbosity >= verbose_normal)
                                {
                                    printf("Stream %d: Audio\n", i);
                                }
                                break;
                            default:
                                CHECK_ERROR (1, "unknown stmid");
                        }

                        /* possibility of multiple channels? probably sees use with audio */
                        CHECK_ERROR ((0 != s->chno), "expected 0 chno");
                    }

                    CHECK_ERROR ((0 != s->datasize), "expected 0 datasize");

                    /* check for redundant stmid */
                    for (j = 0; j < i; j++)
                    {
                        CHECK_ERROR (s->stmid == streams[j].stmid, "duplicate stmid");
                    }
                }
            }

            /* open output files */
            {
                int i;
                outfiles = malloc(sizeof(FILE*)*stream_count);
                CHECK_ERRNO (!outfiles, "malloc");
                outfile_names = malloc(sizeof(char*)*stream_count);
                CHECK_ERRNO (!outfile_names, "malloc");

                for (i = 0; i < stream_count; i++)
                {
                    if (0 == i)
                    {
                        outfiles[i] = NULL;
                        outfile_names[i] = NULL;
                    }
                    else
                    {
                        size_t outfile_name_length = strlen(infile_name) + 1 + 3 + 4 + 1;
                        char *name = malloc(outfile_name_length);
                        CHECK_ERRNO (!name, "malloc");

                        switch (streams[i].stmid)
                        {
                            case SFV_stmid:
                                snprintf(name,outfile_name_length,"%s_%d.mpg", infile_name, i);
                                break;
                            case SFA_stmid:
                                snprintf(name,outfile_name_length,"%s_%d.adx", infile_name, i);
                                break;
                        }
                        outfile_names[i] = name;
                        outfiles[i] = fopen(name, "wb");

                        CHECK_ERRNO(!outfiles[i], "fopen");
                    }
                }
            }

            /* initialize streams */
            {
                int i;
                streams[0].alive = 0;
                for (i=1; i < stream_count; i++)
                {
                    streams[i].alive = 1;
                    live_streams ++;
                    streams[i].bytes_read = 0;
                    streams[i].payload_bytes = 0;
                }
            }

            streams_setup = 1;

            /* seek to footer for check below */
            fseek_reader(infile, CRIUSF_offset+payload_bytes);
        }
        else    /* stream setup already complete */
        {
            /* handle different block types */
            switch (block_type)
            {

                case 0: /* data */
                    dump_from_here(infile, outfiles[stream_idx], payload_bytes);
                    streams[stream_idx].payload_bytes += payload_bytes;
                    break;
                case 1: /* header */
                case 3: /* metadata */
                    /* skip */
                    {
                        long current_offset = reader_tell(infile);
                        CHECK_ERROR (-1 == current_offset, "ftell");
                        if (
                            (block_type == 1 && verbosity >= verbose_headers) ||
                            (block_type == 3 && verbosity >= verbose_blocks))
                        {
                            analyze_utf(infile, current_offset, 0, 1, NULL);
                        }
                        fseek_reader(infile, current_offset+payload_bytes);
                    }
                    break;
                case 2: /* stream metadata */
                    {
                        char *metadata = malloc(payload_bytes);
                        CHECK_ERROR (!metadata, "malloc");

                        get_bytes(infile, (unsigned char *)metadata, payload_bytes);

                        if (!strncmp(metadata, "#HEADER END     ===============", payload_bytes))
                        {
                        }
                        else
                        if (!strncmp(metadata, "#METADATA END   ===============", payload_bytes))
                        {
                        }
                        else
                        if (!strncmp(metadata, "#CONTENTS END   ===============", payload_bytes))
                        {
                            streams[stream_idx].alive = 0;
                            live_streams --;
                        }
                        else
                        {
                            CHECK_ERROR (1, "unknown stream metadata");
                        }
                    }
                    break;
                default:
                    CHECK_ERROR (1, "unknown block type");
            }
        }

        /* check footer (0 padding) */
        {
            int i;
            for (i = 0; i < footer_size; i++)
            {
                CHECK_ERROR (0 != get_byte(infile), "nonzero padding");
            }
        }
    }
    while (live_streams > 0);

    CHECK_ERROR (!streams_setup, "no CRID found");

    if (reader_tell(infile) != file_length)
    {
        printf("Warning: read only 0x%lx bytes of 0x%lx byte file\n",
            (unsigned long)reader_tell(infile), (unsigned long)file_length);
    }

    /* cleanup */
    if (outfiles)
    {
        int i;
        for (i=0; i < stream_count; i++)
        {
            if (outfiles[i])
            {
                if (verbosity >= verbose_normal)
                {
                    printf("%d: %s: read %ld bytes, %ld bytes payload\n",
                            i, outfile_names[i], streams[i].bytes_read, streams[i].payload_bytes);
                }
                int rc = fclose(outfiles[i]);
                CHECK_ERRNO (EOF == rc, "fclose");
            }
        }

        free(outfiles);
    }

    if (outfile_names)
    {
        int i;
        for (i=0; i < stream_count; i++)
        {
            if (outfile_names[i])
            {
                free(outfile_names[i]);
            }
        }

        free(outfile_names);
    }

    if (streams)
    {
        free(streams);
        streams = NULL;
    }

    if (CRIUSF_string_table)
    {
        free_utf_string_table(CRIUSF_string_table);
        CRIUSF_string_table = NULL;
    }
}
