/*----------------------------------------------------------------------
|   main
+---------------------------------------------------------------------*/
int
main(int argc, char** argv)
{
    if (argc < 2) {
        PrintUsageAndExit();
    }

    // init the variables
    const char*  input_filename                = NULL;
    const char*  output_filename               = NULL;
    const char*  track_selector                = NULL;
    AP4_UI32     selected_track_id             = 0;
    unsigned int fragment_duration             = 0;
    bool         auto_detect_fragment_duration = true;
    bool         create_segment_index          = false;
    bool         quiet                         = false;
    AP4_UI32     timescale = 0;
    AP4_Result   result;

    Options.verbosity          = 1;
    Options.debug              = false;
    Options.trim               = false;
    Options.no_tfdt            = false;
    Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE;
    
    // parse the command line
    argv++;
    char* arg;
    while ((arg = *argv++)) {
        if (!strcmp(arg, "--verbosity")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --verbosity option\n");
                return 1;
            }
            Options.verbosity = strtoul(arg, NULL, 10);
        } else if (!strcmp(arg, "--debug")) {
            Options.debug = true;
        } else if (!strcmp(arg, "--index")) {
            create_segment_index = true;
        } else if (!strcmp(arg, "--quiet")) {
            quiet = true;
        } else if (!strcmp(arg, "--trim")) {
            Options.trim = true;
        } else if (!strcmp(arg, "--no-tfdt")) {
            Options.no_tfdt = true;
        } else if (!strcmp(arg, "--force-i-frame-sync")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --fragment-duration option\n");
                return 1;
            }
            if (!strcmp(arg, "all")) {
                Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_ALL;
            } else if (!strcmp(arg, "auto")) {
                Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_AUTO;
            } else {
                fprintf(stderr, "ERROR: unknown mode for --force-i-frame-sync\n");
                return 1;
            }
        } else if (!strcmp(arg, "--fragment-duration")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --fragment-duration option\n");
                return 1;
            }
            fragment_duration = strtoul(arg, NULL, 10);
            auto_detect_fragment_duration = false;
        } else if (!strcmp(arg, "--timescale")) {
            arg = *argv++;
            if (arg == NULL) {
                fprintf(stderr, "ERROR: missing argument after --timescale option\n");
                return 1;
            }
            timescale = strtoul(arg, NULL, 10);
        } else if (!strcmp(arg, "--track")) {
            track_selector = *argv++;
            if (track_selector == NULL) {
                fprintf(stderr, "ERROR: missing argument after --track option\n");
                return 1;
            }
        } else {
            if (input_filename == NULL) {
                input_filename = arg;
            } else if (output_filename == NULL) {
                output_filename = arg;
            } else {
                fprintf(stderr, "ERROR: unexpected argument '%s'\n", arg);
                return 1;
            }
        }
    }
    if (Options.debug && Options.verbosity == 0) {
        Options.verbosity = 1;
    }
    
    if (input_filename == NULL) {
        fprintf(stderr, "ERROR: no input specified\n");
        return 1;
    }
    AP4_ByteStream* input_stream = NULL;
    result = AP4_FileByteStream::Create(input_filename, 
                                        AP4_FileByteStream::STREAM_MODE_READ, 
                                        input_stream);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot open input (%d)\n", result);
        return 1;
    }
    if (output_filename == NULL) {
        fprintf(stderr, "ERROR: no output specified\n");
        return 1;
    }
    AP4_ByteStream* output_stream = NULL;
    result = AP4_FileByteStream::Create(output_filename, 
                                        AP4_FileByteStream::STREAM_MODE_WRITE,
                                        output_stream);
    if (AP4_FAILED(result)) {
        fprintf(stderr, "ERROR: cannot create/open output (%d)\n", result);
        return 1;
    }
    
    // parse the input MP4 file (moov only)
    AP4_File input_file(*input_stream, AP4_DefaultAtomFactory::Instance, true);
    
    // check the file for basic properties
    if (input_file.GetMovie() == NULL) {
        fprintf(stderr, "ERROR: no movie found in the file\n");
        return 1;
    }
    if (!quiet && input_file.GetMovie()->HasFragments()) {
        fprintf(stderr, "NOTICE: file is already fragmented, it will be re-fragmented\n");
    }
    
    // create a cusor list to keep track of the tracks we will read from
    AP4_Array<TrackCursor*> cursors;
    
    // iterate over all tracks
    TrackCursor*  video_track = NULL;
    TrackCursor*  audio_track = NULL;
    TrackCursor*  subtitles_track = NULL;
    unsigned int video_track_count = 0;
    unsigned int audio_track_count = 0;
    unsigned int subtitles_track_count = 0;
    for (AP4_List<AP4_Track>::Item* track_item = input_file.GetMovie()->GetTracks().FirstItem();
                                    track_item;
                                    track_item = track_item->GetNext()) {
        AP4_Track* track = track_item->GetData();

        // sanity check
        if (track->GetSampleCount() == 0 && !input_file.GetMovie()->HasFragments()) {
            fprintf(stderr, "WARNING: track %d has no samples, it will be skipped\n", track->GetId());
            continue;
        }

        // create a sample array for this track
        SampleArray* sample_array;
        if (input_file.GetMovie()->HasFragments()) {
            sample_array = new CachedSampleArray(track);
        } else {
            sample_array = new SampleArray(track);
        }

        // create a cursor for the track
        TrackCursor* cursor = new TrackCursor(track, sample_array);
        cursor->m_Tfra->SetTrackId(track->GetId());
        cursors.Append(cursor);

        if (track->GetType() == AP4_Track::TYPE_VIDEO) {
            if (video_track) {
                fprintf(stderr, "WARNING: more than one video track found\n");
            } else {
                video_track = cursor;
            }
            video_track_count++;
        } else if (track->GetType() == AP4_Track::TYPE_AUDIO) {
            if (audio_track == NULL) {
                audio_track = cursor;
            }
            audio_track_count++;
        } else if (track->GetType() == AP4_Track::TYPE_SUBTITLES) {
            if (subtitles_track == NULL) {
                subtitles_track = cursor;
            }
            subtitles_track_count++;
        }
    }

    if (cursors.ItemCount() == 0) {
        fprintf(stderr, "ERROR: no valid track found\n");
        return 1;
    }
    
    if (track_selector) {
        if (!strncmp("audio", track_selector, 5)) {
            if (audio_track) {
                selected_track_id = audio_track->m_Track->GetId();
            } else {
                fprintf(stderr, "ERROR: no audio track found\n");
                return 1;
            }
        } else if (!strncmp("video", track_selector, 5)) {
            if (video_track) {
                selected_track_id = video_track->m_Track->GetId();
            } else {
                fprintf(stderr, "ERROR: no video track found\n");
                return 1;
            }
        } else if (!strncmp("subtitles", track_selector, 9)) {
            if (subtitles_track) {
                selected_track_id = subtitles_track->m_Track->GetId();
            } else {
                fprintf(stderr, "ERROR: no subtitles track found\n");
                return 1;
            }
        } else {
            selected_track_id = (AP4_UI32)strtol(track_selector, NULL, 10);
            bool found = false;
            for (unsigned int i=0; i<cursors.ItemCount(); i++) {
                if (cursors[i]->m_Track->GetId() == selected_track_id) {
                    found = true;
                    break;
                }
            }
            if (!found) {
                fprintf(stderr, "ERROR: track not found\n");
                return 1;
            }
        }
    }
    
    if (video_track_count == 0 && audio_track_count == 0 && subtitles_track_count == 0) {
        fprintf(stderr, "ERROR: no audio, video, or subtitles track in the file\n");
        return 1;
    }
    AP4_AvcSampleDescription* avc_desc = NULL;
    if (video_track && (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE)) {
        // that feature is only supported for AVC
        AP4_SampleDescription* sdesc = video_track->m_Track->GetSampleDescription(0);
        if (sdesc) {
            avc_desc = AP4_DYNAMIC_CAST(AP4_AvcSampleDescription, sdesc);
        }
        if (avc_desc == NULL) {
            fprintf(stderr, "--force-i-frame-sync can only be used with AVC/H.264 video\n");
            return 1;
        }
    }
    
    // remember where the stream was
    AP4_Position position;
    input_stream->Tell(position);

    // for fragmented input files, we need to populate the sample arrays
    if (input_file.GetMovie()->HasFragments()) {
        AP4_LinearReader reader(*input_file.GetMovie(), input_stream);
        for (unsigned int i=0; i<cursors.ItemCount(); i++) {
            reader.EnableTrack(cursors[i]->m_Track->GetId());
        }
        AP4_UI32 track_id;
        AP4_Sample sample;
        do {
            result = reader.GetNextSample(sample, track_id);
            if (AP4_SUCCEEDED(result)) {
                for (unsigned int i=0; i<cursors.ItemCount(); i++) {
                    if (cursors[i]->m_Track->GetId() == track_id) {
                        cursors[i]->m_Samples->AddSample(sample);
                        break;
                    }
                }
            }
        } while (AP4_SUCCEEDED(result));
        
    } else if (video_track && (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE)) {
        AP4_Sample sample;
        if (Options.force_i_frame_sync == AP4_FRAGMENTER_FORCE_SYNC_MODE_AUTO) {
            // detect if this looks like an open-gop source
            for (unsigned int i=1; i<video_track->m_Samples->GetSampleCount(); i++) {
                if (AP4_SUCCEEDED(video_track->m_Samples->GetSample(i, sample))) {
                    if (sample.IsSync()) {
                        // we found a sync i-frame, assume this is *not* an open-gop source
                        Options.force_i_frame_sync = AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE;
                        if (Options.debug) {
                            printf("this does not look like an open-gop source, not forcing i-frame sync flags\n");
                        }
                        break;
                    }
                }
            }
        }
        if (Options.force_i_frame_sync != AP4_FRAGMENTER_FORCE_SYNC_MODE_NONE) {
            for (unsigned int i=0; i<video_track->m_Samples->GetSampleCount(); i++) {
                if (AP4_SUCCEEDED(video_track->m_Samples->GetSample(i, sample))) {
                    if (IsIFrame(sample, avc_desc)) {
                        video_track->m_Samples->ForceSync(i);
                    }
                }
            }
        }
    }

    // return the stream to its original position
    input_stream->Seek(position);

    // auto-detect the fragment duration if needed
    if (auto_detect_fragment_duration) {
        if (video_track) {
            fragment_duration = AutoDetectFragmentDuration(video_track);
        } else if (audio_track && input_file.GetMovie()->HasFragments()) {
            fragment_duration = AutoDetectAudioFragmentDuration(*input_stream, audio_track);
        }
        if (fragment_duration == 0) {
            if (Options.verbosity > 0) {
                fprintf(stderr, "unable to autodetect fragment duration, using default\n");
            }
            fragment_duration = AP4_FRAGMENTER_DEFAULT_FRAGMENT_DURATION;
        } else if (fragment_duration > AP4_FRAGMENTER_MAX_AUTO_FRAGMENT_DURATION) {
            if (Options.verbosity > 0) {
                fprintf(stderr, "auto-detected fragment duration too large, using default\n");
            }
            fragment_duration = AP4_FRAGMENTER_DEFAULT_FRAGMENT_DURATION;
        }
    }
    
    // fragment the file
    Fragment(input_file, *output_stream, cursors, fragment_duration, timescale, selected_track_id, create_segment_index);
    
    // cleanup and exit
    if (input_stream)  input_stream->Release();
    if (output_stream) output_stream->Release();

    return 0;
}
const std::vector<unsigned char> HdlcFrame::Dissect() const {
    bool l_bHasPayload = false;
    std::stringstream l_Output;
    if (IsIFrame()) {
        l_bHasPayload = true;
        l_Output << "HDLC frame, Addr=0x" << std::hex << (int)GetAddress() << std::dec << ", ";
        l_Output << "I-Frame, PF=" << IsPF() << ", SSeq=" << (int)GetSSeq() << ", RSeq=" << (int)GetRSeq();
    } else if (IsSFrame()) {
        l_Output << "HDLC frame, Addr=0x" << std::hex << (int)GetAddress() << std::dec << ", S-Frame: ";
        switch (GetHDLCFrameType()) {
            case HdlcFrame::HDLC_FRAMETYPE_S_RR: {
                l_Output << "RR";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_S_RNR: {
                l_Output << "RNR";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_S_REJ: {
                l_Output << "REJ";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_S_SREJ: {
                l_Output << "SREJ";
                break;
            }
            default: {
                break;
            }
        } // switch

        l_Output << ", PF=" << IsPF() << ", RSeq=" << (int)GetRSeq();
    } else if (IsUFrame()) {
        l_Output << "HDLC frame, Addr=0x" << std::hex << (int)GetAddress() << std::dec << ", U-Frame: ";
        switch (GetHDLCFrameType()) {
            case HdlcFrame::HDLC_FRAMETYPE_U_UI: {
                l_bHasPayload = true;
                l_Output << "UI";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_SIM: {
                l_Output << "SIM";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_SARM: {
                l_Output << "SARM";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_UP: {
                l_Output << "UP";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_SABM: {
                l_Output << "SABM";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_DISC: {
                l_Output << "DISC";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_UA: {
                l_Output << "UA";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_SNRM: {
                l_Output << "SNRM";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_CMDR: {
                l_bHasPayload = true;
                l_Output << "FRMR/CMDR";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_TEST: {
                l_bHasPayload = true;
                l_Output << "TEST";
                break;
            }
            case HdlcFrame::HDLC_FRAMETYPE_U_XID: {
                l_bHasPayload = true;
                l_Output << "XID";
                break;
            }
            default: {
                break;
            }
        } // switch
        
        l_Output << ", PF=" << IsPF();
    } else {
        l_Output << "Unparseable HDLC frame";
    } // else
    
    if (l_bHasPayload) {
        l_Output << ", with " << m_Payload.size() << " bytes payload:";
        for (auto it = m_Payload.begin(); it != m_Payload.end(); ++it) {
            l_Output << " " << std::hex << std::setw(2) << std::setfill('0') << int(*it);
        } // for
    } // if

    std::vector<unsigned char> l_DissectedFrame;
    std::string l_String(l_Output.str());
    l_DissectedFrame.insert(l_DissectedFrame.begin(), l_String.data(), (l_String.data() + l_String.size()));
    return l_DissectedFrame;
}