/*
 * Copyright (c) 2025, Alliance for Open Media. All rights reserved
 *
 * This source code is subject to the terms of the BSD 3-Clause Clear License
 * and the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear
 * License was not distributed with this source code in the LICENSE file, you
 * can obtain it at aomedia.org/license/software-license/bsd-3-c-c/.  If the
 * Alliance for Open Media Patent License 1.0 was not distributed with this
 * source code in the PATENTS file, you can obtain it at
 * aomedia.org/license/patent-license/.
 */

// multi-streams muxing test
// ===========================
//
// This is an example demonstrating how to mux multiple sub-bitstreams.
//

#include "tools/stream_mux.h"

// When 1, rewrite TD with 2-byte header (extension_flag=1, xlayer_id=GLOBAL).
// When 0, preserve the original 1-byte TD header from the input stream.
#define REWRITE_TD_WITH_GLOBAL_XLAYER 0

// This function writes a multi-stream decoder operation OBU,
// when multiple sub-streams are merged into one bitstream.
static int write_multi_stream_decoder_operation_obu(uint8_t *const dst,
                                                    int num_streams,
                                                    int *stream_ids,
                                                    int *stream_buffer_units) {
  struct avm_write_bit_buffer wb = { dst, 0 };
  int obu_type = OBU_MULTI_STREAM_DECODER_OPERATION;
  uint32_t size = 0;

  avm_wb_write_literal(&wb, 1, 1);              // obu_header_extension_flag
  avm_wb_write_literal(&wb, (int)obu_type, 5);  // obu_type
  avm_wb_write_literal(&wb, 0, 2);              // obu_tlayer
  avm_wb_write_literal(&wb, 0, 3);              // obu_mlayer
  avm_wb_write_literal(&wb, 31, 5);             // obu_xlayer

  avm_wb_write_literal(&wb, num_streams - 2, 3);  // signal number of streams
  avm_wb_write_literal(&wb, 2, PROFILE_BITS);     // multistream_profile_idc
  avm_wb_write_literal(&wb, SEQ_LEVEL_4_0,
                       LEVEL_BITS);  // multistream_level_idx
  avm_wb_write_bit(&wb, 0);          // multistream_tier_idx

  int multistream_even_allocation_flag = 1;
  int multistream_large_picture_idc = 0;
  int max_buffer_unit = stream_buffer_units[0];
  for (int i = 0; i < num_streams; i++) {
    if (stream_buffer_units[i] != max_buffer_unit) {
      multistream_even_allocation_flag = 0;
      if (stream_buffer_units[i] > max_buffer_unit) {
        multistream_large_picture_idc = i;
        max_buffer_unit = stream_buffer_units[i];
      }
    }
  }

  avm_wb_write_bit(
      &wb,
      multistream_even_allocation_flag);  // multistream_even_allocation_flag
  if (!multistream_even_allocation_flag)
    avm_wb_write_literal(&wb, multistream_large_picture_idc,
                         3);  // multistream_large_picture_idc

  for (int i = 0; i < num_streams; i++) {
    avm_wb_write_literal(&wb, stream_ids[i], XLAYER_BITS);  // signal stream IDs
    avm_wb_write_literal(&wb, 0, PROFILE_BITS);  // substream profile_idc
    avm_wb_write_literal(&wb, SEQ_LEVEL_4_0,
                         LEVEL_BITS);  // substream level_idx
    avm_wb_write_bit(&wb, 0);          // substream tier_idx
  }

  avm_wb_write_bit(&wb, 1);  // msdo_doh_constraint_flag

  if ((wb.bit_offset % CHAR_BIT == 0)) {
    avm_wb_write_literal(&wb, 0x80, 8);
  } else {
    // assumes that the other bits are already 0s
    avm_wb_write_bit(&wb, 1);
  }

  size = avm_wb_bytes_written(&wb);
  return size;
}

static void write_obu_header_with_stream_id(uint8_t *const dst,
                                            ObuHeader *obu_header,
                                            int stream_id) {
  struct avm_write_bit_buffer wb = { dst, 0 };
  avm_wb_write_bit(&wb, 1);                                 // extention flag
  avm_wb_write_literal(&wb, obu_header->type, 5);           // obu_type
  avm_wb_write_literal(&wb, obu_header->obu_tlayer_id, 2);  // obu_temporal
  avm_wb_write_literal(&wb, obu_header->obu_mlayer_id, 3);  // obu_mlayer
  avm_wb_write_literal(&wb, stream_id, 5);                  // obu_xlayer
}

static bool TuHasKeyFrame(const uint8_t *data, int length) {
  int pos = 0;
  while (pos < length) {
    size_t lfs = 0;
    uint64_t obu_sz = 0;
    if (avm_uleb_decode(data + pos, length - pos, &obu_sz, &lfs) != 0) break;
    ObuHeader hdr;
    memset(&hdr, 0, sizeof(hdr));
    if (!ParseAV2ObuHeader(*(data + pos + lfs), &hdr)) break;
    if (hdr.type == OBU_CLOSED_LOOP_KEY || hdr.type == OBU_OPEN_LOOP_KEY ||
        hdr.type == OBU_RAS_FRAME)
      return true;
    pos += static_cast<int>(obu_sz) + static_cast<int>(lfs);
  }
  return false;
}

// Extract non-TD OBUs from a single input TU, rewriting headers with stream_id.
// Used by the combined TU path.
static std::vector<uint8_t> WriteStreamOBUs(const uint8_t *data, int length,
                                            int stream_id) {
  std::vector<uint8_t> obus;
  const int kObuHeaderSizeBytes = 1;
  int consumed = 0;
  ObuHeader obu_header;

  while (consumed < length) {
    const int remaining = length - consumed;
    size_t length_field_size = 0;
    uint64_t obu_total_size = 0;

    memset(&obu_header, 0, sizeof(obu_header));
    if (avm_uleb_decode(data + consumed, remaining, &obu_total_size,
                        &length_field_size) != 0) {
      fprintf(stderr, "OBU size parsing failed at offset %d.\n", consumed);
      break;
    }

    const uint8_t obu_header_byte = *(data + consumed + length_field_size);
    if (!ParseAV2ObuHeader(obu_header_byte, &obu_header)) {
      fprintf(stderr, "OBU parsing failed at offset %d.\n",
              consumed + static_cast<int>(length_field_size));
      break;
    }

    int obu_header_size = 1;
    if (obu_header.obu_header_extension_flag) {
      const uint8_t obu_header_ext_byte =
          *(data + consumed + length_field_size + kObuHeaderSizeBytes);
      ParseAV2ObuHeaderExtension(obu_header_ext_byte, &obu_header);
      ++obu_header_size;
    }

    consumed +=
        static_cast<int>(obu_total_size) + static_cast<int>(length_field_size);

    // Skip TD OBUs — the combined TU path writes its own single TD.
    if (obu_header.type == OBU_TEMPORAL_DELIMITER) continue;

    // Rewrite OBU header with stream_id as xlayer_id
    std::vector<uint8_t> obu_size_data(length_field_size + 1);
    size_t coded_obu_size;
    avm_uleb_encode(obu_total_size - obu_header_size + 2,
                    sizeof(obu_total_size), obu_size_data.data(),
                    &coded_obu_size);
    obus.insert(obus.end(), obu_size_data.begin(),
                obu_size_data.begin() + coded_obu_size);

    std::vector<uint8_t> obu_header_data(2);
    write_obu_header_with_stream_id(obu_header_data.data(), &obu_header,
                                    stream_id);
    obus.insert(obus.end(), obu_header_data.begin(), obu_header_data.end());

    // Append OBU payload (after original header)
    const uint8_t *payload_start =
        data + (consumed - static_cast<int>(obu_total_size)) + obu_header_size;
    int payload_size = static_cast<int>(obu_total_size) - obu_header_size;
    obus.insert(obus.end(), payload_start, payload_start + payload_size);
  }

  return obus;
}

// Write a single combined TU containing OBUs from all streams.
// sorted_indices provides stream indices in ascending stream_id order.
static std::vector<uint8_t> WriteCombinedTU(
    std::vector<std::vector<uint8_t>> &per_stream_obus,
    const int *sorted_indices, int num_streams, int *stream_ids,
    int *stream_buffer_units, bool any_key_frame, bool redundant_msdo) {
  std::vector<uint8_t> tu;

  // Write TD OBU
#if REWRITE_TD_WITH_GLOBAL_XLAYER
  uint8_t td_obu_size = 2;
  tu.push_back(td_obu_size);
  tu.push_back(0x88);
  tu.push_back(0x1F);
#else
  // Write a minimal 1-byte TD: size=1, header byte for TD type
  uint8_t td_header = (OBU_TEMPORAL_DELIMITER << 2);  // ext=0, tlayer=0
  tu.push_back(1);                                    // leb128 size = 1
  tu.push_back(td_header);
#endif

  // Insert MSDO if any stream has a key frame or redundant mode
  if (redundant_msdo || any_key_frame) {
    std::vector<uint8_t> multi_stream_obu(num_streams * 2 + 4);
    int multi_header_obu_size = write_multi_stream_decoder_operation_obu(
        multi_stream_obu.data(), num_streams, stream_ids, stream_buffer_units);
    std::vector<uint8_t> msdo_size_data(8);
    size_t msdo_length_field_size = 0;
    avm_uleb_encode(multi_header_obu_size, sizeof(multi_header_obu_size),
                    msdo_size_data.data(), &msdo_length_field_size);
    tu.insert(tu.end(), msdo_size_data.begin(),
              msdo_size_data.begin() + msdo_length_field_size);
    tu.insert(tu.end(), multi_stream_obu.begin(),
              multi_stream_obu.begin() + multi_header_obu_size);
  }

  // Append OBUs from each stream in ascending stream_id order
  for (int j = 0; j < num_streams; ++j) {
    const auto &obus = per_stream_obus[sorted_indices[j]];
    tu.insert(tu.end(), obus.begin(), obus.end());
  }

  return tu;
}

// This function read a temporal unit from a sub-stream,
// writes the temporal unit with updates of xlayer_id,
// and multiplex the temporal units into a merged bitstream.
std::vector<uint8_t> WriteTU(const uint8_t *data, int length,
                             int *obu_overhead_bytes, int seg_idx,
                             int num_streams, int *stream_ids,
                             int *stream_buffer_units, bool redundant_msdo) {
  std::vector<uint8_t> tu_obus;
  const uint8_t *data_ptr = data;
  const int kObuHeaderSizeBytes = 1;
  const int kMinimumBytesRequired = 1 + kObuHeaderSizeBytes;
  int consumed = 0;
  int obu_overhead = 0;
  ObuHeader obu_header;

  const bool tu_has_key_frame = TuHasKeyFrame(data, length);

  while (consumed < length) {
    const int remaining = length - consumed;
    if (remaining < kMinimumBytesRequired) {
      fprintf(stderr,
              "OBU parse error. Did not consume all data, %d bytes remain.\n",
              remaining);
    }

    int obu_header_size = 0;
    size_t length_field_size = 0;
    uint64_t obu_total_size = 0;

    memset(&obu_header, 0, sizeof(obu_header));

    if (avm_uleb_decode(data + consumed, remaining, &obu_total_size,
                        &length_field_size) != 0) {
      fprintf(stderr, "OBU size parsing failed at offset %d.\n", consumed);
    }

    const uint8_t obu_header_byte = *(data + consumed + length_field_size);
    if (!ParseAV2ObuHeader(obu_header_byte, &obu_header)) {
      fprintf(stderr, "OBU parsing failed at offset %d.\n",
              consumed + static_cast<int>(length_field_size));
    }
    ++obu_overhead;
    ++obu_header_size;

    if (obu_header.obu_header_extension_flag) {
      const uint8_t obu_header_ext_byte =
          *(data + consumed + length_field_size + kObuHeaderSizeBytes);
      if (!ParseAV2ObuHeaderExtension(obu_header_ext_byte, &obu_header)) {
        fprintf(stderr, "OBU header extension parsing failed at offset %d.\n",
                static_cast<int>(consumed + length_field_size +
                                 kObuHeaderSizeBytes));
      }

      ++obu_overhead;
      ++obu_header_size;
    }

    int current_obu_length = static_cast<int>(obu_total_size) - obu_header_size;
    if (obu_header_size + static_cast<int>(length_field_size) +
            current_obu_length >
        remaining) {
      fprintf(stderr, "OBU parsing failed: not enough OBU data.\n");
    }
    consumed +=
        static_cast<int>(obu_total_size) + static_cast<int>(length_field_size);

#if PRINT_TU_INFO
    PrintObuHeader(&obu_header);
#endif  // PRINT_TU_INFO

    std::vector<uint8_t> obu_tmp(data_ptr + length_field_size,
                                 data_ptr + obu_total_size + length_field_size);

    // Write the temporal delimiter first, before any MSDO insertion,
    // since the MSDO must come after the TD in the OBU order.
    if (obu_header.type == OBU_TEMPORAL_DELIMITER) {
#if REWRITE_TD_WITH_GLOBAL_XLAYER
      // Rewrite TD with 2-byte header: extension_flag=1, xlayer_id=31(GLOBAL)
      // Byte 0: ext=1 | type=00010(TD) | tlayer=00 = 0x88
      // Byte 1: mlayer=000 | xlayer=11111 = 0x1F
      uint8_t td_obu_size = 2;  // leb128 encoding of OBU size (2 header bytes)
      tu_obus.push_back(td_obu_size);
      tu_obus.push_back(0x88);
      tu_obus.push_back(0x1F);
#else
      std::vector<uint8_t> obu_size_data(length_field_size);
      size_t coded_obu_size;
      avm_uleb_encode(obu_total_size, sizeof(obu_total_size),
                      obu_size_data.data(), &coded_obu_size);
      tu_obus.insert(tu_obus.end(), obu_size_data.begin(),
                     obu_size_data.begin() + coded_obu_size);
      tu_obus.insert(tu_obus.end(), obu_tmp.begin(), obu_tmp.end());
#endif

      // Insert MSDO immediately after the TD when required.
      if (redundant_msdo || tu_has_key_frame) {
        std::vector<uint8_t> multi_stream_obu(num_streams * 2 + 4);
        int multi_header_obu_size = write_multi_stream_decoder_operation_obu(
            multi_stream_obu.data(), num_streams, stream_ids,
            stream_buffer_units);
        std::vector<uint8_t> multi_header_obu_size_data(length_field_size);
        size_t multi_header_length_field_size = 0;
        avm_uleb_encode(multi_header_obu_size, sizeof(multi_header_obu_size),
                        multi_header_obu_size_data.data(),
                        &multi_header_length_field_size);
        tu_obus.insert(tu_obus.end(), multi_header_obu_size_data.begin(),
                       multi_header_obu_size_data.begin() +
                           multi_header_length_field_size);
        tu_obus.insert(tu_obus.end(), multi_stream_obu.begin(),
                       multi_stream_obu.begin() + multi_header_obu_size);
      }

      data_ptr += static_cast<int>(obu_total_size) +
                  static_cast<int>(length_field_size);
      continue;
    }

    // Rewrite OBU header with signaling stream_id
    {
      std::vector<uint8_t> obu_size_data(length_field_size + 1);
      size_t coded_obu_size;
      avm_uleb_encode(obu_total_size - obu_header_size + 2,
                      sizeof(obu_total_size), obu_size_data.data(),
                      &coded_obu_size);
      tu_obus.insert(tu_obus.end(), obu_size_data.begin(),
                     obu_size_data.begin() + coded_obu_size);

      std::vector<uint8_t> obu_header_data(2);
      write_obu_header_with_stream_id(obu_header_data.data(), &obu_header,
                                      stream_ids[seg_idx]);
      tu_obus.insert(tu_obus.end(), obu_header_data.begin(),
                     obu_header_data.end());
      tu_obus.insert(tu_obus.end(), obu_tmp.begin() + obu_header_size,
                     obu_tmp.end());
    }
    data_ptr +=
        static_cast<int>(obu_total_size) + static_cast<int>(length_field_size);
  }

  if (obu_overhead_bytes != nullptr) *obu_overhead_bytes = obu_overhead;

  return tu_obus;
}

int main(int argc, const char *argv[]) {
  bool redundant_msdo = false;
  bool separate_tu = false;
  int arg_offset = 0;

  // Parse optional flags
  while (arg_offset + 1 < argc) {
    if (strcmp(argv[arg_offset + 1], "--redundant-msdo") == 0) {
      redundant_msdo = true;
      ++arg_offset;
    } else if (strcmp(argv[arg_offset + 1], "--separate-tu") == 0) {
      separate_tu = true;
      ++arg_offset;
    } else {
      break;
    }
  }

  if (argc - arg_offset < 3 || (argc - arg_offset - 2) % 3) {
    fprintf(stderr,
            "command: %s [--redundant-msdo] [--separate-tu] "
            "[input file1], [stream ID 1], "
            "[unit size 1], [input "
            "file2], [stream ID 2], [unit size 2], ... [outfile]\n",
            argv[0]);
    return -1;
  } else if (argc - arg_offset > (AVM_MAX_NUM_STREAMS * 3 + 2)) {
    fprintf(stderr,
            "The number of input files cannot exceed the maximum number of "
            "streams (8)\n");
    return -1;
  }

  int num_streams = (argc - arg_offset - 2) / 3;
  int sum_buffer_units = 0;

  for (int i = 0; i < num_streams; ++i) {
    int stream_id = atoi(argv[arg_offset + i * 3 + 2]);
    if (stream_id < 0 || stream_id >= (1 << XLAYER_BITS)) {
      fprintf(stderr, "The value of stream_id must be in range [0, %d]\n",
              (1 << XLAYER_BITS) - 1);
      return -1;
    }
  }
  for (int i = 0; i < num_streams; ++i) {
    sum_buffer_units += atoi(argv[arg_offset + i * 3 + 3]);
  }
  if (sum_buffer_units > 8) {
    fprintf(stderr,
            "The sum of stream buffer units cannot exceed the max value (8)\n");
    return -1;
  }

  FILE *fout = fopen(argv[argc - 1], "wb");

  if (fout == nullptr) {
    fprintf(stderr, "Error: failed to open the output file: %s",
            argv[argc - 1]);
    exit(1);
  }

  InputContext input_ctx[AVM_MAX_NUM_STREAMS];
  AvxInputContext avx_ctx[AVM_MAX_NUM_STREAMS];
  ObuDecInputContext obu_ctx[AVM_MAX_NUM_STREAMS];
#if CONFIG_WEBM_IO
  WebmInputContext webm_ctx[AVM_MAX_NUM_STREAMS];
#endif
  std::vector<uint8_t> segments;
  FILE *fin[AVM_MAX_NUM_STREAMS];

  // Initialize file read for each stream
  for (int i = 0; i < num_streams; ++i) {
    fin[i] = fopen(argv[arg_offset + i * 3 + 1], "rb");
    if (fin[i] == nullptr) {
      fprintf(stderr, "Error: failed to open the input file\n");
    }

    input_ctx[i].avx_ctx = &avx_ctx[i];
    input_ctx[i].obu_ctx = &obu_ctx[i];
#if CONFIG_WEBM_IO
    input_ctx[i].webm_ctx = &webm_ctx[i];
#endif

    input_ctx[i].Init();
    avx_ctx[i].file = fin[i];
    avx_ctx[i].file_type = GetFileType(&input_ctx[i]);

    // behavior underneath the function calls.
    input_ctx[i].unit_buffer =
        reinterpret_cast<uint8_t *>(calloc(kInitialBufferSize, 1));
    if (!input_ctx[i].unit_buffer) {
      fprintf(stderr, "Error: No memory, can't alloc input buffer.\n");
    }
    input_ctx[i].unit_buffer_size = kInitialBufferSize;
  }

#if PRINT_TU_INFO
  printf("\n =========== Start muxing bitstreams ==============\n\n");
  printf("  Number of streams: %d\n", num_streams);
#endif  // PRINT_TU_INFO

  // Set the values of unit sizes of streams
  int stream_ids[AVM_MAX_NUM_STREAMS];
#if PRINT_TU_INFO
  printf("  Stream IDs: ");
#endif  // PRINT_TU_INFO
  for (int i = 0; i < num_streams; ++i) {
    stream_ids[i] = atoi(argv[arg_offset + i * 3 + 2]);
#if PRINT_TU_INFO
    printf("[%d] ", stream_ids[i]);
#endif  // PRINT_TU_INFO
  }

#if PRINT_TU_INFO
  printf("\n  Stream_buffer_units: ");
#endif  // PRINT_TU_INFO
  // Set the values of unit sizes of streams
  int stream_buffer_units[AVM_MAX_NUM_STREAMS];
  for (int i = 0; i < num_streams; ++i) {
    stream_buffer_units[i] = atoi(argv[arg_offset + i * 3 + 3]);
#if PRINT_TU_INFO
    printf("[%d] ", stream_buffer_units[i]);
#endif  // PRINT_TU_INFO
  }
#if PRINT_TU_INFO
  printf("\n\n");
#endif  // PRINT_TU_INFO

  // Multiplex TUs of multi-streams
  int num_tu_read = 1;
  int num_total_tus = 0;
  int unit_number[AVM_MAX_NUM_STREAMS];
  for (int i = 0; i < num_streams; ++i) unit_number[i] = 0;

  if (separate_tu) {
    // Legacy path: one output TU per input TU, round-robin
    while (num_tu_read) {
      num_tu_read = 0;
      for (int i = 0; i < num_streams; ++i) {
        size_t unit_size = 0;
        if (ReadTemporalUnit(&input_ctx[i], &unit_size)) {
#if PRINT_TU_INFO
          printf("Stream Idx %d\n", i);
          printf("Temporal unit %d\n", unit_number[i]);
#endif  // PRINT_TU_INFO
          int obu_overhead_current_unit = 0;
          segments =
              WriteTU(input_ctx[i].unit_buffer, static_cast<int>(unit_size),
                      &obu_overhead_current_unit, i, num_streams, stream_ids,
                      stream_buffer_units, redundant_msdo);
          fwrite(segments.data(), 1, segments.size(), fout);
#if PRINT_TU_INFO
          printf("  TU overhead:    %d\n", obu_overhead_current_unit);
          printf("  TU total:    %ld\n", unit_size);
#endif  // PRINT_TU_INFO
          ++unit_number[i];
          ++num_tu_read;
        }
      }
    }
  } else {
    // Combined TU path: merge corresponding TUs into a single output TU

    // Pre-compute stream indices sorted by ascending stream_id
    int sorted_indices[AVM_MAX_NUM_STREAMS];
    for (int i = 0; i < num_streams; ++i) sorted_indices[i] = i;
    for (int i = 0; i < num_streams - 1; ++i) {
      for (int j = i + 1; j < num_streams; ++j) {
        if (stream_ids[sorted_indices[j]] < stream_ids[sorted_indices[i]]) {
          int tmp = sorted_indices[i];
          sorted_indices[i] = sorted_indices[j];
          sorted_indices[j] = tmp;
        }
      }
    }

    while (num_tu_read) {
      num_tu_read = 0;
      bool any_key_frame = false;
      std::vector<std::vector<uint8_t>> per_stream_obus(num_streams);

      // Phase 1: Read one TU from each active stream
      for (int i = 0; i < num_streams; ++i) {
        size_t unit_size = 0;
        if (ReadTemporalUnit(&input_ctx[i], &unit_size)) {
#if PRINT_TU_INFO
          printf("Stream Idx %d\n", i);
          printf("Temporal unit %d\n", unit_number[i]);
#endif  // PRINT_TU_INFO
          if (TuHasKeyFrame(input_ctx[i].unit_buffer,
                            static_cast<int>(unit_size))) {
            any_key_frame = true;
          }

          per_stream_obus[i] =
              WriteStreamOBUs(input_ctx[i].unit_buffer,
                              static_cast<int>(unit_size), stream_ids[i]);

          ++unit_number[i];
          ++num_tu_read;
        }
      }

      // Phase 2: Write single combined TU
      if (num_tu_read > 0) {
        segments = WriteCombinedTU(per_stream_obus, sorted_indices, num_streams,
                                   stream_ids, stream_buffer_units,
                                   any_key_frame, redundant_msdo);
        fwrite(segments.data(), 1, segments.size(), fout);
      }
    }
  }

  for (int i = 0; i < num_streams; ++i) num_total_tus += unit_number[i];
#if PRINT_TU_INFO
  printf("  Total number of TUs: %d\n\n", num_total_tus);
  for (int i = 0; i < num_streams; ++i)
    printf("  Number of TUs with stream ID %d: %d\n", stream_ids[i],
           unit_number[i]);
  printf("\n ========== Completed muxing bitstreams ========== \n\n");
#endif  // PRINT_TU_INFO
  // Initialize file read for each stream
  for (int i = 0; i < num_streams; ++i) {
    if (fin[i] != nullptr) {
      fclose(fin[i]);
      fin[i] = nullptr;
    }
  }
  fclose(fout);
  return EXIT_SUCCESS;
}
