// Copyright 2019 The PDFium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Original code copyright 2014 Foxit Software Inc. http://www.foxitsoftware.com

#include "core/fxcodec/jpx/jpx_decode_utils.h"

#include <algorithm>
#include <limits>
#include <type_traits>

#include "core/fxcrt/compiler_specific.h"
#include "core/fxcrt/fx_memcpy_wrappers.h"
#include "core/fxcrt/numerics/safe_conversions.h"

namespace fxcodec {

DecodeData::DecodeData(pdfium::span<const uint8_t> data)
    : src_data(data.data()),
      src_size(pdfium::checked_cast<OPJ_SIZE_T>(data.size())) {}

OPJ_SIZE_T opj_read_from_memory(void* p_buffer,
                                OPJ_SIZE_T nb_bytes,
                                void* p_user_data) {
  DecodeData* srcData = static_cast<DecodeData*>(p_user_data);
  if (!srcData || !srcData->src_data || srcData->src_size == 0) {
    return static_cast<OPJ_SIZE_T>(-1);
  }

  // Reads at EOF return an error code.
  if (srcData->offset >= srcData->src_size) {
    return static_cast<OPJ_SIZE_T>(-1);
  }

  UNSAFE_TODO({
    OPJ_SIZE_T bufferLength = srcData->src_size - srcData->offset;
    OPJ_SIZE_T readlength = nb_bytes < bufferLength ? nb_bytes : bufferLength;
    FXSYS_memcpy(p_buffer, &srcData->src_data[srcData->offset], readlength);
    srcData->offset += readlength;
    return readlength;
  })
}

OPJ_OFF_T opj_skip_from_memory(OPJ_OFF_T nb_bytes, void* p_user_data) {
  DecodeData* srcData = static_cast<DecodeData*>(p_user_data);
  if (!srcData || !srcData->src_data || srcData->src_size == 0) {
    return static_cast<OPJ_OFF_T>(-1);
  }

  // Offsets are signed and may indicate a negative skip. Do not support this
  // because of the strange return convention where either bytes skipped or
  // -1 is returned. Following that convention, a successful relative seek of
  // -1 bytes would be required to to give the same result as the error case.
  if (nb_bytes < 0) {
    return static_cast<OPJ_OFF_T>(-1);
  }

  auto unsigned_nb_bytes =
      static_cast<std::make_unsigned<OPJ_OFF_T>::type>(nb_bytes);
  // Additionally, the offset may take us beyond the range of a size_t (e.g.
  // 32-bit platforms). If so, just clamp at EOF.
  if (unsigned_nb_bytes >
      std::numeric_limits<OPJ_SIZE_T>::max() - srcData->offset) {
    srcData->offset = srcData->src_size;
  } else {
    OPJ_SIZE_T checked_nb_bytes = static_cast<OPJ_SIZE_T>(unsigned_nb_bytes);
    // Otherwise, mimic fseek() semantics to always succeed, even past EOF,
    // clamping at EOF.  We can get away with this since we don't actually
    // provide negative relative skips from beyond EOF back to inside the
    // data, which would be the only reason to need to know exactly how far
    // beyond EOF we are.
    srcData->offset =
        std::min(srcData->offset + checked_nb_bytes, srcData->src_size);
  }
  return nb_bytes;
}

OPJ_BOOL opj_seek_from_memory(OPJ_OFF_T nb_bytes, void* p_user_data) {
  DecodeData* srcData = static_cast<DecodeData*>(p_user_data);
  if (!srcData || !srcData->src_data || srcData->src_size == 0) {
    return OPJ_FALSE;
  }

  // Offsets are signed and may indicate a negative position, which would
  // be before the start of the file. Do not support this.
  if (nb_bytes < 0) {
    return OPJ_FALSE;
  }

  auto unsigned_nb_bytes =
      static_cast<std::make_unsigned<OPJ_OFF_T>::type>(nb_bytes);
  // Additionally, the offset may take us beyond the range of a size_t (e.g.
  // 32-bit platforms). If so, just clamp at EOF.
  if (unsigned_nb_bytes > std::numeric_limits<OPJ_SIZE_T>::max()) {
    srcData->offset = srcData->src_size;
  } else {
    OPJ_SIZE_T checked_nb_bytes = static_cast<OPJ_SIZE_T>(nb_bytes);
    // Otherwise, mimic fseek() semantics to always succeed, even past EOF,
    // again clamping at EOF.
    srcData->offset = std::min(checked_nb_bytes, srcData->src_size);
  }
  return OPJ_TRUE;
}

}  // namespace fxcodec
