// Copyright 2014 PDFium Authors. All rights reserved.
// 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

#ifndef CORE_FXCRT_EXTENSION_H_
#define CORE_FXCRT_EXTENSION_H_

#include <algorithm>
#include <memory>

#include "core/fxcrt/include/fx_basic.h"
#include "core/fxcrt/include/fx_safe_types.h"

class IFXCRT_FileAccess {
 public:
  static IFXCRT_FileAccess* Create();
  virtual ~IFXCRT_FileAccess() {}

  virtual FX_BOOL Open(const CFX_ByteStringC& fileName, uint32_t dwMode) = 0;
  virtual FX_BOOL Open(const CFX_WideStringC& fileName, uint32_t dwMode) = 0;
  virtual void Close() = 0;
  virtual FX_FILESIZE GetSize() const = 0;
  virtual FX_FILESIZE GetPosition() const = 0;
  virtual FX_FILESIZE SetPosition(FX_FILESIZE pos) = 0;
  virtual size_t Read(void* pBuffer, size_t szBuffer) = 0;
  virtual size_t Write(const void* pBuffer, size_t szBuffer) = 0;
  virtual size_t ReadPos(void* pBuffer, size_t szBuffer, FX_FILESIZE pos) = 0;
  virtual size_t WritePos(const void* pBuffer,
                          size_t szBuffer,
                          FX_FILESIZE pos) = 0;
  virtual FX_BOOL Flush() = 0;
  virtual FX_BOOL Truncate(FX_FILESIZE szFile) = 0;
};

#ifdef PDF_ENABLE_XFA
class CFX_CRTFileAccess : public IFX_FileAccess {
 public:
  CFX_CRTFileAccess() : m_RefCount(0) {}

  // IFX_FileAccess
  void Release() override {
    if (--m_RefCount == 0)
      delete this;
  }

  IFX_FileAccess* Retain() override {
    m_RefCount++;
    return (IFX_FileAccess*)this;
  }

  void GetPath(CFX_WideString& wsPath) override { wsPath = m_path; }

  IFX_FileStream* CreateFileStream(uint32_t dwModes) override {
    return FX_CreateFileStream(m_path.c_str(), dwModes);
  }

  FX_BOOL Init(const CFX_WideStringC& wsPath) {
    m_path = wsPath;
    m_RefCount = 1;
    return TRUE;
  }

 protected:
  CFX_WideString m_path;
  uint32_t m_RefCount;
};
#endif  // PDF_ENABLE_XFA

class CFX_CRTFileStream final : public IFX_FileStream {
 public:
  explicit CFX_CRTFileStream(std::unique_ptr<IFXCRT_FileAccess> pFA);
  ~CFX_CRTFileStream() override;

  // IFX_FileStream:
  IFX_FileStream* Retain() override;
  void Release() override;
  FX_FILESIZE GetSize() override;
  FX_BOOL IsEOF() override;
  FX_FILESIZE GetPosition() override;
  FX_BOOL ReadBlock(void* buffer, FX_FILESIZE offset, size_t size) override;
  size_t ReadBlock(void* buffer, size_t size) override;
  FX_BOOL WriteBlock(const void* buffer,
                     FX_FILESIZE offset,
                     size_t size) override;
  FX_BOOL Flush() override;

 protected:
  std::unique_ptr<IFXCRT_FileAccess> m_pFile;
  uint32_t m_dwCount;
};

#define FX_MEMSTREAM_BlockSize (64 * 1024)
#define FX_MEMSTREAM_Consecutive 0x01
#define FX_MEMSTREAM_TakeOver 0x02
class CFX_MemoryStream final : public IFX_MemoryStream {
 public:
  explicit CFX_MemoryStream(FX_BOOL bConsecutive)
      : m_dwCount(1),
        m_nTotalSize(0),
        m_nCurSize(0),
        m_nCurPos(0),
        m_nGrowSize(FX_MEMSTREAM_BlockSize) {
    m_dwFlags =
        FX_MEMSTREAM_TakeOver | (bConsecutive ? FX_MEMSTREAM_Consecutive : 0);
  }
  CFX_MemoryStream(uint8_t* pBuffer, size_t nSize, FX_BOOL bTakeOver)
      : m_dwCount(1),
        m_nTotalSize(nSize),
        m_nCurSize(nSize),
        m_nCurPos(0),
        m_nGrowSize(FX_MEMSTREAM_BlockSize) {
    m_Blocks.Add(pBuffer);
    m_dwFlags =
        FX_MEMSTREAM_Consecutive | (bTakeOver ? FX_MEMSTREAM_TakeOver : 0);
  }
  ~CFX_MemoryStream() override {
    if (m_dwFlags & FX_MEMSTREAM_TakeOver) {
      for (int32_t i = 0; i < m_Blocks.GetSize(); i++) {
        FX_Free(m_Blocks[i]);
      }
    }
    m_Blocks.RemoveAll();
  }

  // IFX_MemoryStream:
  IFX_FileStream* Retain() override {
    m_dwCount++;
    return this;
  }
  void Release() override {
    uint32_t nCount = --m_dwCount;
    if (nCount) {
      return;
    }
    delete this;
  }
  FX_FILESIZE GetSize() override { return (FX_FILESIZE)m_nCurSize; }
  FX_BOOL IsEOF() override { return m_nCurPos >= (size_t)GetSize(); }
  FX_FILESIZE GetPosition() override { return (FX_FILESIZE)m_nCurPos; }
  FX_BOOL ReadBlock(void* buffer, FX_FILESIZE offset, size_t size) override {
    if (!buffer || !size) {
      return FALSE;
    }

    FX_SAFE_SIZE_T newPos = size;
    newPos += offset;
    if (!newPos.IsValid() || newPos.ValueOrDefault(0) == 0 ||
        newPos.ValueOrDie() > m_nCurSize) {
      return FALSE;
    }

    m_nCurPos = newPos.ValueOrDie();
    if (m_dwFlags & FX_MEMSTREAM_Consecutive) {
      FXSYS_memcpy(buffer, m_Blocks[0] + (size_t)offset, size);
      return TRUE;
    }
    size_t nStartBlock = (size_t)offset / m_nGrowSize;
    offset -= (FX_FILESIZE)(nStartBlock * m_nGrowSize);
    while (size) {
      size_t nRead = m_nGrowSize - (size_t)offset;
      if (nRead > size) {
        nRead = size;
      }
      FXSYS_memcpy(buffer, m_Blocks[(int)nStartBlock] + (size_t)offset, nRead);
      buffer = ((uint8_t*)buffer) + nRead;
      size -= nRead;
      nStartBlock++;
      offset = 0;
    }
    return TRUE;
  }
  size_t ReadBlock(void* buffer, size_t size) override {
    if (m_nCurPos >= m_nCurSize) {
      return 0;
    }
    size_t nRead = std::min(size, m_nCurSize - m_nCurPos);
    if (!ReadBlock(buffer, (int32_t)m_nCurPos, nRead)) {
      return 0;
    }
    return nRead;
  }
  FX_BOOL WriteBlock(const void* buffer,
                     FX_FILESIZE offset,
                     size_t size) override {
    if (!buffer || !size) {
      return FALSE;
    }
    if (m_dwFlags & FX_MEMSTREAM_Consecutive) {
      FX_SAFE_SIZE_T newPos = size;
      newPos += offset;
      if (!newPos.IsValid())
        return FALSE;

      m_nCurPos = newPos.ValueOrDie();
      if (m_nCurPos > m_nTotalSize) {
        m_nTotalSize =
            (m_nCurPos + m_nGrowSize - 1) / m_nGrowSize * m_nGrowSize;
        if (m_Blocks.GetSize() < 1) {
          uint8_t* block = FX_Alloc(uint8_t, m_nTotalSize);
          m_Blocks.Add(block);
        } else {
          m_Blocks[0] = FX_Realloc(uint8_t, m_Blocks[0], m_nTotalSize);
        }
        if (!m_Blocks[0]) {
          m_Blocks.RemoveAll();
          return FALSE;
        }
      }
      FXSYS_memcpy(m_Blocks[0] + (size_t)offset, buffer, size);
      if (m_nCurSize < m_nCurPos) {
        m_nCurSize = m_nCurPos;
      }
      return TRUE;
    }

    FX_SAFE_SIZE_T newPos = size;
    newPos += offset;
    if (!newPos.IsValid()) {
      return FALSE;
    }

    if (!ExpandBlocks(newPos.ValueOrDie())) {
      return FALSE;
    }
    m_nCurPos = newPos.ValueOrDie();
    size_t nStartBlock = (size_t)offset / m_nGrowSize;
    offset -= (FX_FILESIZE)(nStartBlock * m_nGrowSize);
    while (size) {
      size_t nWrite = m_nGrowSize - (size_t)offset;
      if (nWrite > size) {
        nWrite = size;
      }
      FXSYS_memcpy(m_Blocks[(int)nStartBlock] + (size_t)offset, buffer, nWrite);
      buffer = ((uint8_t*)buffer) + nWrite;
      size -= nWrite;
      nStartBlock++;
      offset = 0;
    }
    return TRUE;
  }
  FX_BOOL Flush() override { return TRUE; }
  FX_BOOL IsConsecutive() const override {
    return !!(m_dwFlags & FX_MEMSTREAM_Consecutive);
  }
  void EstimateSize(size_t nInitSize, size_t nGrowSize) override {
    if (m_dwFlags & FX_MEMSTREAM_Consecutive) {
      if (m_Blocks.GetSize() < 1) {
        uint8_t* pBlock =
            FX_Alloc(uint8_t, std::max(nInitSize, static_cast<size_t>(4096)));
        m_Blocks.Add(pBlock);
      }
      m_nGrowSize = std::max(nGrowSize, static_cast<size_t>(4096));
    } else if (m_Blocks.GetSize() < 1) {
      m_nGrowSize = std::max(nGrowSize, static_cast<size_t>(4096));
    }
  }
  uint8_t* GetBuffer() const override {
    return m_Blocks.GetSize() ? m_Blocks[0] : nullptr;
  }
  void AttachBuffer(uint8_t* pBuffer,
                    size_t nSize,
                    FX_BOOL bTakeOver = FALSE) override {
    if (!(m_dwFlags & FX_MEMSTREAM_Consecutive)) {
      return;
    }
    m_Blocks.RemoveAll();
    m_Blocks.Add(pBuffer);
    m_nTotalSize = m_nCurSize = nSize;
    m_nCurPos = 0;
    m_dwFlags =
        FX_MEMSTREAM_Consecutive | (bTakeOver ? FX_MEMSTREAM_TakeOver : 0);
  }
  void DetachBuffer() override {
    if (!(m_dwFlags & FX_MEMSTREAM_Consecutive)) {
      return;
    }
    m_Blocks.RemoveAll();
    m_nTotalSize = m_nCurSize = m_nCurPos = 0;
    m_dwFlags = FX_MEMSTREAM_TakeOver;
  }

 protected:
  CFX_ArrayTemplate<uint8_t*> m_Blocks;
  uint32_t m_dwCount;
  size_t m_nTotalSize;
  size_t m_nCurSize;
  size_t m_nCurPos;
  size_t m_nGrowSize;
  uint32_t m_dwFlags;
  FX_BOOL ExpandBlocks(size_t size) {
    if (m_nCurSize < size) {
      m_nCurSize = size;
    }
    if (size <= m_nTotalSize) {
      return TRUE;
    }
    int32_t iCount = m_Blocks.GetSize();
    size = (size - m_nTotalSize + m_nGrowSize - 1) / m_nGrowSize;
    m_Blocks.SetSize(m_Blocks.GetSize() + (int32_t)size);
    while (size--) {
      uint8_t* pBlock = FX_Alloc(uint8_t, m_nGrowSize);
      m_Blocks.SetAt(iCount++, pBlock);
      m_nTotalSize += m_nGrowSize;
    }
    return TRUE;
  }
};

#ifdef __cplusplus
extern "C" {
#endif
#define MT_N 848
#define MT_M 456
#define MT_Matrix_A 0x9908b0df
#define MT_Upper_Mask 0x80000000
#define MT_Lower_Mask 0x7fffffff
struct FX_MTRANDOMCONTEXT {
  FX_MTRANDOMCONTEXT() {
    mti = MT_N + 1;
    bHaveSeed = FALSE;
  }
  uint32_t mti;
  FX_BOOL bHaveSeed;
  uint32_t mt[MT_N];
};
#if _FXM_PLATFORM_ == _FXM_PLATFORM_WINDOWS_
FX_BOOL FX_GenerateCryptoRandom(uint32_t* pBuffer, int32_t iCount);
#endif
#ifdef __cplusplus
}
#endif

#endif  // CORE_FXCRT_EXTENSION_H_