// Copyright (c) the JPEG XL Project Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

#include "lib/jpegli/decode_marker.h"

#include <jxl/types.h>
#include <string.h>

#include "lib/jpegli/common.h"
#include "lib/jpegli/decode_internal.h"
#include "lib/jpegli/error.h"
#include "lib/jpegli/huffman.h"
#include "lib/jpegli/memory_manager.h"
#include "lib/jxl/base/printf_macros.h"

namespace jpegli {
namespace {

constexpr int kMaxDimPixels = 65535;
constexpr uint8_t kIccProfileTag[12] = "ICC_PROFILE";

// Macros for commonly used error conditions.

#define JPEG_VERIFY_LEN(n)                               \
  if (pos + (n) > len) {                                 \
    JPEGLI_ERROR("Unexpected end of marker: pos=%" PRIuS \
                 " need=%d len=%" PRIuS,                 \
                 pos, static_cast<int>(n), len);         \
  }

#define JPEG_VERIFY_INPUT(var, low, high)                        \
  if ((var) < (low) || (var) > (high)) {                         \
    JPEGLI_ERROR("Invalid " #var ": %d", static_cast<int>(var)); \
  }

#define JPEG_VERIFY_MARKER_END()                                              \
  if (pos != len) {                                                           \
    JPEGLI_ERROR("Invalid marker length: declared=%" PRIuS " actual=%" PRIuS, \
                 len, pos);                                                   \
  }

inline int ReadUint8(const uint8_t* data, size_t* pos) {
  return data[(*pos)++];
}

inline int ReadUint16(const uint8_t* data, size_t* pos) {
  int v = (data[*pos] << 8) + data[*pos + 1];
  *pos += 2;
  return v;
}

void ProcessSOF(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  jpeg_decomp_master* m = cinfo->master;
  if (!m->found_soi_) {
    JPEGLI_ERROR("Unexpected SOF marker.");
  }
  if (m->found_sof_) {
    JPEGLI_ERROR("Duplicate SOF marker.");
  }
  m->found_sof_ = true;
  cinfo->progressive_mode = TO_JXL_BOOL(cinfo->unread_marker == 0xc2);
  cinfo->arith_code = 0;
  size_t pos = 2;
  JPEG_VERIFY_LEN(6);
  cinfo->data_precision = ReadUint8(data, &pos);
  cinfo->image_height = ReadUint16(data, &pos);
  cinfo->image_width = ReadUint16(data, &pos);
  cinfo->num_components = ReadUint8(data, &pos);
  JPEG_VERIFY_INPUT(cinfo->data_precision, kJpegPrecision, kJpegPrecision);
  JPEG_VERIFY_INPUT(cinfo->image_height, 1, kMaxDimPixels);
  JPEG_VERIFY_INPUT(cinfo->image_width, 1, kMaxDimPixels);
  JPEG_VERIFY_INPUT(cinfo->num_components, 1, kMaxComponents);
  JPEG_VERIFY_LEN(3 * cinfo->num_components);
  cinfo->comp_info = jpegli::Allocate<jpeg_component_info>(
      cinfo, cinfo->num_components, JPOOL_IMAGE);

  // Read sampling factors and quant table index for each component.
  uint8_t ids_seen[256] = {0};
  cinfo->max_h_samp_factor = 1;
  cinfo->max_v_samp_factor = 1;
  for (int i = 0; i < cinfo->num_components; ++i) {
    jpeg_component_info* comp = &cinfo->comp_info[i];
    comp->component_index = i;
    const int id = ReadUint8(data, &pos);
    if (ids_seen[id]) {  // (cf. section B.2.2, syntax of Ci)
      JPEGLI_ERROR("Duplicate ID %d in SOF.", id);
    }
    ids_seen[id] = 1;
    comp->component_id = id;
    int factor = ReadUint8(data, &pos);
    int h_samp_factor = factor >> 4;
    int v_samp_factor = factor & 0xf;
    JPEG_VERIFY_INPUT(h_samp_factor, 1, MAX_SAMP_FACTOR);
    JPEG_VERIFY_INPUT(v_samp_factor, 1, MAX_SAMP_FACTOR);
    comp->h_samp_factor = h_samp_factor;
    comp->v_samp_factor = v_samp_factor;
    cinfo->max_h_samp_factor =
        std::max(cinfo->max_h_samp_factor, h_samp_factor);
    cinfo->max_v_samp_factor =
        std::max(cinfo->max_v_samp_factor, v_samp_factor);
    int quant_tbl_idx = ReadUint8(data, &pos);
    JPEG_VERIFY_INPUT(quant_tbl_idx, 0, NUM_QUANT_TBLS - 1);
    comp->quant_tbl_no = quant_tbl_idx;
    comp->quant_table = nullptr;  // will be allocated after SOS marker
  }
  JPEG_VERIFY_MARKER_END();

  // Set the input colorspace based on the markers we have seen and set
  // default output colorspace.
  if (cinfo->num_components == 1) {
    cinfo->jpeg_color_space = JCS_GRAYSCALE;
    cinfo->out_color_space = JCS_GRAYSCALE;
  } else if (cinfo->num_components == 3) {
    if (cinfo->saw_JFIF_marker) {
      cinfo->jpeg_color_space = JCS_YCbCr;
    } else if (cinfo->saw_Adobe_marker) {
      cinfo->jpeg_color_space =
          cinfo->Adobe_transform == 0 ? JCS_RGB : JCS_YCbCr;
    } else {
      cinfo->jpeg_color_space = JCS_YCbCr;
      if (cinfo->comp_info[0].component_id == 'R' &&  //
          cinfo->comp_info[1].component_id == 'G' &&  //
          cinfo->comp_info[2].component_id == 'B') {
        cinfo->jpeg_color_space = JCS_RGB;
      }
    }
    cinfo->out_color_space = JCS_RGB;
  } else if (cinfo->num_components == 4) {
    if (cinfo->saw_Adobe_marker) {
      cinfo->jpeg_color_space =
          cinfo->Adobe_transform == 0 ? JCS_CMYK : JCS_YCCK;
    } else {
      cinfo->jpeg_color_space = JCS_CMYK;
    }
    cinfo->out_color_space = JCS_CMYK;
  }

  // We have checked above that none of the sampling factors are 0, so the max
  // sampling factors can not be 0.
  cinfo->total_iMCU_rows =
      DivCeil(cinfo->image_height, cinfo->max_v_samp_factor * DCTSIZE);
  m->iMCU_cols_ =
      DivCeil(cinfo->image_width, cinfo->max_h_samp_factor * DCTSIZE);
  // Compute the block dimensions for each component.
  for (int i = 0; i < cinfo->num_components; ++i) {
    jpeg_component_info* comp = &cinfo->comp_info[i];
    if (cinfo->max_h_samp_factor % comp->h_samp_factor != 0 ||
        cinfo->max_v_samp_factor % comp->v_samp_factor != 0) {
      JPEGLI_ERROR("Non-integral subsampling ratios.");
    }
    m->h_factor[i] = cinfo->max_h_samp_factor / comp->h_samp_factor;
    m->v_factor[i] = cinfo->max_v_samp_factor / comp->v_samp_factor;
    comp->downsampled_width = DivCeil(cinfo->image_width, m->h_factor[i]);
    comp->downsampled_height = DivCeil(cinfo->image_height, m->v_factor[i]);
    comp->width_in_blocks = DivCeil(comp->downsampled_width, DCTSIZE);
    comp->height_in_blocks = DivCeil(comp->downsampled_height, DCTSIZE);
  }
  memset(m->scan_progression_, 0, sizeof(m->scan_progression_));
}

void ProcessSOS(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  jpeg_decomp_master* m = cinfo->master;
  if (!m->found_sof_) {
    JPEGLI_ERROR("Unexpected SOS marker.");
  }
  m->found_sos_ = true;
  size_t pos = 2;
  JPEG_VERIFY_LEN(1);
  cinfo->comps_in_scan = ReadUint8(data, &pos);
  JPEG_VERIFY_INPUT(cinfo->comps_in_scan, 1, cinfo->num_components);
  JPEG_VERIFY_INPUT(cinfo->comps_in_scan, 1, MAX_COMPS_IN_SCAN);

  JPEG_VERIFY_LEN(2 * cinfo->comps_in_scan);
  bool is_interleaved = (cinfo->comps_in_scan > 1);
  uint8_t ids_seen[256] = {0};
  cinfo->blocks_in_MCU = 0;
  for (int i = 0; i < cinfo->comps_in_scan; ++i) {
    int id = ReadUint8(data, &pos);
    if (ids_seen[id]) {  // (cf. section B.2.3, regarding CSj)
      JPEGLI_ERROR("Duplicate ID %d in SOS.", id);
    }
    ids_seen[id] = 1;
    jpeg_component_info* comp = nullptr;
    for (int j = 0; j < cinfo->num_components; ++j) {
      if (cinfo->comp_info[j].component_id == id) {
        comp = &cinfo->comp_info[j];
        cinfo->cur_comp_info[i] = comp;
      }
    }
    if (!comp) {
      JPEGLI_ERROR("SOS marker: Could not find component with id %d", id);
    }
    int c = ReadUint8(data, &pos);
    comp->dc_tbl_no = c >> 4;
    comp->ac_tbl_no = c & 0xf;
    JPEG_VERIFY_INPUT(comp->dc_tbl_no, 0, 3);
    JPEG_VERIFY_INPUT(comp->ac_tbl_no, 0, 3);
    comp->MCU_width = is_interleaved ? comp->h_samp_factor : 1;
    comp->MCU_height = is_interleaved ? comp->v_samp_factor : 1;
    comp->MCU_blocks = comp->MCU_width * comp->MCU_height;
    if (cinfo->blocks_in_MCU + comp->MCU_blocks > D_MAX_BLOCKS_IN_MCU) {
      JPEGLI_ERROR("Too many blocks in MCU.");
    }
    for (int j = 0; j < comp->MCU_blocks; ++j) {
      cinfo->MCU_membership[cinfo->blocks_in_MCU++] = i;
    }
  }
  JPEG_VERIFY_LEN(3);
  cinfo->Ss = ReadUint8(data, &pos);
  cinfo->Se = ReadUint8(data, &pos);
  JPEG_VERIFY_INPUT(cinfo->Ss, 0, 63);
  JPEG_VERIFY_INPUT(cinfo->Se, cinfo->Ss, 63);
  int c = ReadUint8(data, &pos);
  cinfo->Ah = c >> 4;
  cinfo->Al = c & 0xf;
  JPEG_VERIFY_MARKER_END();

  if (cinfo->input_scan_number == 0) {
    m->is_multiscan_ = (cinfo->comps_in_scan < cinfo->num_components ||
                        FROM_JXL_BOOL(cinfo->progressive_mode));
  }
  if (cinfo->Ah != 0 && cinfo->Al != cinfo->Ah - 1) {
    // section G.1.1.1.2 : Successive approximation control only improves
    // by one bit at a time.
    JPEGLI_ERROR("Invalid progressive parameters: Al=%d Ah=%d", cinfo->Al,
                 cinfo->Ah);
  }
  if (!cinfo->progressive_mode) {
    cinfo->Ss = 0;
    cinfo->Se = 63;
    cinfo->Ah = 0;
    cinfo->Al = 0;
  }
  const uint16_t scan_bitmask =
      cinfo->Ah == 0 ? (0xffff << cinfo->Al) : (1u << cinfo->Al);
  const uint16_t refinement_bitmask = (1 << cinfo->Al) - 1;
  if (!cinfo->coef_bits) {
    cinfo->coef_bits =
        Allocate<int[DCTSIZE2]>(cinfo, cinfo->num_components * 2, JPOOL_IMAGE);
    m->coef_bits_latch =
        Allocate<int[SAVED_COEFS]>(cinfo, cinfo->num_components, JPOOL_IMAGE);
    m->prev_coef_bits_latch =
        Allocate<int[SAVED_COEFS]>(cinfo, cinfo->num_components, JPOOL_IMAGE);

    for (int c = 0; c < cinfo->num_components; ++c) {
      for (int i = 0; i < DCTSIZE2; ++i) {
        cinfo->coef_bits[c][i] = -1;
        if (i < SAVED_COEFS) {
          m->coef_bits_latch[c][i] = -1;
        }
      }
    }
  }

  for (int i = 0; i < cinfo->comps_in_scan; ++i) {
    int comp_idx = cinfo->cur_comp_info[i]->component_index;
    for (int k = cinfo->Ss; k <= cinfo->Se; ++k) {
      if (m->scan_progression_[comp_idx][k] & scan_bitmask) {
        JPEGLI_ERROR(
            "Overlapping scans: component=%d k=%d prev_mask: %u cur_mask %u",
            comp_idx, k, m->scan_progression_[i][k], scan_bitmask);
      }
      if (m->scan_progression_[comp_idx][k] & refinement_bitmask) {
        JPEGLI_ERROR(
            "Invalid scan order, a more refined scan was already done: "
            "component=%d k=%d prev_mask=%u cur_mask=%u",
            comp_idx, k, m->scan_progression_[i][k], scan_bitmask);
      }
      m->scan_progression_[comp_idx][k] |= scan_bitmask;
    }
  }
  if (cinfo->Al > 10) {
    JPEGLI_ERROR("Scan parameter Al=%d is not supported.", cinfo->Al);
  }
}

// Reads the Define Huffman Table (DHT) marker segment and builds the Huffman
// decoding table in either dc_huff_lut_ or ac_huff_lut_, depending on the type
// and solt_id of Huffman code being read.
void ProcessDHT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  size_t pos = 2;
  if (pos == len) {
    JPEGLI_ERROR("DHT marker: no Huffman table found");
  }
  while (pos < len) {
    JPEG_VERIFY_LEN(1 + kJpegHuffmanMaxBitLength);
    // The index of the Huffman code in the current set of Huffman codes. For AC
    // component Huffman codes, 0x10 is added to the index.
    int slot_id = ReadUint8(data, &pos);
    int huffman_index = slot_id;
    bool is_ac_table = ((slot_id & 0x10) != 0);
    JHUFF_TBL** table;
    if (is_ac_table) {
      huffman_index -= 0x10;
      JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1);
      table = &cinfo->ac_huff_tbl_ptrs[huffman_index];
    } else {
      JPEG_VERIFY_INPUT(huffman_index, 0, NUM_HUFF_TBLS - 1);
      table = &cinfo->dc_huff_tbl_ptrs[huffman_index];
    }
    if (*table == nullptr) {
      *table = jpegli_alloc_huff_table(reinterpret_cast<j_common_ptr>(cinfo));
    }
    int total_count = 0;
    for (size_t i = 1; i <= kJpegHuffmanMaxBitLength; ++i) {
      int count = ReadUint8(data, &pos);
      (*table)->bits[i] = count;
      total_count += count;
    }
    if (is_ac_table) {
      JPEG_VERIFY_INPUT(total_count, 0, kJpegHuffmanAlphabetSize);
    } else {
      // Allow symbols up to 15 here, we check later whether any invalid symbols
      // are actually decoded.
      // TODO(szabadka) Make sure decoder works (does not crash) with up to
      // 15-nbits DC symbols and then increase kJpegDCAlphabetSize.
      JPEG_VERIFY_INPUT(total_count, 0, 16);
    }
    JPEG_VERIFY_LEN(total_count);
    for (int i = 0; i < total_count; ++i) {
      int value = ReadUint8(data, &pos);
      if (!is_ac_table) {
        JPEG_VERIFY_INPUT(value, 0, 15);
      }
      (*table)->huffval[i] = value;
    }
    for (int i = total_count; i < kJpegHuffmanAlphabetSize; ++i) {
      (*table)->huffval[i] = 0;
    }
  }
  JPEG_VERIFY_MARKER_END();
}

void ProcessDQT(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  jpeg_decomp_master* m = cinfo->master;
  if (m->found_sos_) {
    JPEGLI_ERROR("Updating quant tables between scans is not supported.");
  }
  size_t pos = 2;
  if (pos == len) {
    JPEGLI_ERROR("DQT marker: no quantization table found");
  }
  while (pos < len) {
    JPEG_VERIFY_LEN(1);
    int quant_table_index = ReadUint8(data, &pos);
    int precision = quant_table_index >> 4;
    JPEG_VERIFY_INPUT(precision, 0, 1);
    quant_table_index &= 0xf;
    JPEG_VERIFY_INPUT(quant_table_index, 0, NUM_QUANT_TBLS - 1);
    JPEG_VERIFY_LEN((precision + 1) * DCTSIZE2);

    if (cinfo->quant_tbl_ptrs[quant_table_index] == nullptr) {
      cinfo->quant_tbl_ptrs[quant_table_index] =
          jpegli_alloc_quant_table(reinterpret_cast<j_common_ptr>(cinfo));
    }
    JQUANT_TBL* quant_table = cinfo->quant_tbl_ptrs[quant_table_index];

    for (size_t i = 0; i < DCTSIZE2; ++i) {
      int quant_val =
          precision ? ReadUint16(data, &pos) : ReadUint8(data, &pos);
      JPEG_VERIFY_INPUT(quant_val, 1, 65535);
      quant_table->quantval[kJPEGNaturalOrder[i]] = quant_val;
    }
  }
  JPEG_VERIFY_MARKER_END();
}

void ProcessDNL(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  // Ignore marker.
}

void ProcessDRI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  jpeg_decomp_master* m = cinfo->master;
  if (m->found_dri_) {
    JPEGLI_ERROR("Duplicate DRI marker.");
  }
  m->found_dri_ = true;
  size_t pos = 2;
  JPEG_VERIFY_LEN(2);
  cinfo->restart_interval = ReadUint16(data, &pos);
  JPEG_VERIFY_MARKER_END();
}

void ProcessAPP(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  jpeg_decomp_master* m = cinfo->master;
  const uint8_t marker = cinfo->unread_marker;
  const uint8_t* payload = data + 2;
  size_t payload_size = len - 2;
  if (marker == 0xE0) {
    if (payload_size >= 14 && memcmp(payload, "JFIF", 4) == 0) {
      cinfo->saw_JFIF_marker = TRUE;
      cinfo->JFIF_major_version = payload[5];
      cinfo->JFIF_minor_version = payload[6];
      cinfo->density_unit = payload[7];
      cinfo->X_density = (payload[8] << 8) + payload[9];
      cinfo->Y_density = (payload[10] << 8) + payload[11];
    }
  } else if (marker == 0xEE) {
    if (payload_size >= 12 && memcmp(payload, "Adobe", 5) == 0) {
      cinfo->saw_Adobe_marker = TRUE;
      cinfo->Adobe_transform = payload[11];
    }
  } else if (marker == 0xE2) {
    if (payload_size >= sizeof(kIccProfileTag) &&
        memcmp(payload, kIccProfileTag, sizeof(kIccProfileTag)) == 0) {
      payload += sizeof(kIccProfileTag);
      payload_size -= sizeof(kIccProfileTag);
      if (payload_size < 2) {
        JPEGLI_ERROR("ICC chunk is too small.");
      }
      uint8_t index = payload[0];
      uint8_t total = payload[1];
      ++m->icc_index_;
      if (m->icc_index_ != index) {
        JPEGLI_ERROR("Invalid ICC chunk order.");
      }
      if (total == 0) {
        JPEGLI_ERROR("Invalid ICC chunk total.");
      }
      if (m->icc_total_ == 0) {
        m->icc_total_ = total;
      } else if (m->icc_total_ != total) {
        JPEGLI_ERROR("Invalid ICC chunk total.");
      }
      if (m->icc_index_ > m->icc_total_) {
        JPEGLI_ERROR("Invalid ICC chunk index.");
      }
      m->icc_profile_.insert(m->icc_profile_.end(), payload + 2,
                             payload + payload_size);
    }
  }
}

void ProcessCOM(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  // Ignore marker.
}

void ProcessSOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  jpeg_decomp_master* m = cinfo->master;
  if (m->found_soi_) {
    JPEGLI_ERROR("Duplicate SOI marker");
  }
  m->found_soi_ = true;
}

void ProcessEOI(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  cinfo->master->found_eoi_ = true;
}

void SaveMarker(j_decompress_ptr cinfo, const uint8_t* data, size_t len) {
  const uint8_t marker = cinfo->unread_marker;
  const uint8_t* payload = data + 2;
  size_t payload_size = len - 2;

  // Insert new saved marker to the head of the list.
  jpeg_saved_marker_ptr next = cinfo->marker_list;
  cinfo->marker_list =
      jpegli::Allocate<jpeg_marker_struct>(cinfo, 1, JPOOL_IMAGE);
  cinfo->marker_list->next = next;
  cinfo->marker_list->marker = marker;
  cinfo->marker_list->original_length = payload_size;
  cinfo->marker_list->data_length = payload_size;
  cinfo->marker_list->data =
      jpegli::Allocate<uint8_t>(cinfo, payload_size, JPOOL_IMAGE);
  memcpy(cinfo->marker_list->data, payload, payload_size);
}

uint8_t ProcessNextMarker(j_decompress_ptr cinfo, const uint8_t* const data,
                          const size_t len, size_t* pos) {
  jpeg_decomp_master* m = cinfo->master;
  size_t num_skipped = 0;
  uint8_t marker = cinfo->unread_marker;
  if (marker == 0) {
    // kIsValidMarker[i] == 1 means (0xc0 + i) is a valid marker.
    static const uint8_t kIsValidMarker[] = {
        1, 1, 1, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 1, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
        1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0,
    };
    // Skip bytes between markers.
    while (*pos + 1 < len && (data[*pos] != 0xff || data[*pos + 1] < 0xc0 ||
                              !kIsValidMarker[data[*pos + 1] - 0xc0])) {
      ++(*pos);
      ++num_skipped;
    }
    if (*pos + 2 > len) {
      return kNeedMoreInput;
    }
    marker = data[*pos + 1];
    if (num_skipped > 0) {
      if (m->found_soi_) {
        JPEGLI_WARN("Skipped %d bytes before marker 0x%02x",
                    static_cast<int>(num_skipped), marker);
      } else {
        JPEGLI_ERROR("Did not find SOI marker.");
      }
    }
    *pos += 2;
    cinfo->unread_marker = marker;
  }
  if (!m->found_soi_ && marker != 0xd8) {
    JPEGLI_ERROR("Did not find SOI marker.");
  }
  if (GetMarkerProcessor(cinfo)) {
    return kHandleMarkerProcessor;
  }
  const uint8_t* marker_data = &data[*pos];
  size_t marker_len = 0;
  if (marker != 0xd8 && marker != 0xd9) {
    if (*pos + 2 > len) {
      return kNeedMoreInput;
    }
    marker_len += (data[*pos] << 8) + data[*pos + 1];
    if (marker_len < 2) {
      JPEGLI_ERROR("Invalid marker length");
    }
    if (*pos + marker_len > len) {
      // TODO(szabadka) Limit our memory usage by using the skip_input_data
      // source manager callback on APP markers that are not saved.
      return kNeedMoreInput;
    }
    if (marker >= 0xe0 && m->markers_to_save_[marker - 0xe0]) {
      SaveMarker(cinfo, marker_data, marker_len);
    }
  }
  if (marker == 0xc0 || marker == 0xc1 || marker == 0xc2) {
    ProcessSOF(cinfo, marker_data, marker_len);
  } else if (marker == 0xc4) {
    ProcessDHT(cinfo, marker_data, marker_len);
  } else if (marker == 0xda) {
    ProcessSOS(cinfo, marker_data, marker_len);
  } else if (marker == 0xdb) {
    ProcessDQT(cinfo, marker_data, marker_len);
  } else if (marker == 0xdc) {
    ProcessDNL(cinfo, marker_data, marker_len);
  } else if (marker == 0xdd) {
    ProcessDRI(cinfo, marker_data, marker_len);
  } else if (marker >= 0xe0 && marker <= 0xef) {
    ProcessAPP(cinfo, marker_data, marker_len);
  } else if (marker == 0xfe) {
    ProcessCOM(cinfo, marker_data, marker_len);
  } else if (marker == 0xd8) {
    ProcessSOI(cinfo, marker_data, marker_len);
  } else if (marker == 0xd9) {
    ProcessEOI(cinfo, marker_data, marker_len);
  } else {
    JPEGLI_ERROR("Unexpected marker 0x%x", marker);
  }
  *pos += marker_len;
  cinfo->unread_marker = 0;
  if (marker == 0xda) {
    return JPEG_REACHED_SOS;
  } else if (marker == 0xd9) {
    return JPEG_REACHED_EOI;
  }
  return kProcessNextMarker;
}

}  // namespace

jpeg_marker_parser_method GetMarkerProcessor(j_decompress_ptr cinfo) {
  jpeg_decomp_master* m = cinfo->master;
  uint8_t marker = cinfo->unread_marker;
  jpeg_marker_parser_method callback = nullptr;
  if (marker >= 0xe0 && marker <= 0xef) {
    callback = m->app_marker_parsers[marker - 0xe0];
  } else if (marker == 0xfe) {
    callback = m->com_marker_parser;
  }
  return callback;
}

int ProcessMarkers(j_decompress_ptr cinfo, const uint8_t* const data,
                   const size_t len, size_t* pos) {
  for (;;) {
    int status = ProcessNextMarker(cinfo, data, len, pos);
    if (status != kProcessNextMarker) {
      return status;
    }
  }
}

}  // namespace jpegli
