// 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 _FXCRT_EXTENSION_IMP_
#define _FXCRT_EXTENSION_IMP_

#include "../../../third_party/numerics/safe_math.h"

class IFXCRT_FileAccess
{
public:
    virtual ~IFXCRT_FileAccess() {}
    virtual FX_BOOL		Open(FX_BSTR fileName, FX_DWORD dwMode) = 0;
    virtual FX_BOOL		Open(FX_WSTR fileName, FX_DWORD dwMode) = 0;
    virtual void		Close() = 0;
    virtual void		Release() = 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;
};
IFXCRT_FileAccess*	FXCRT_FileAccess_Create();
class CFX_CRTFileStream FX_FINAL : public IFX_FileStream, public CFX_Object
{
public:
    CFX_CRTFileStream(IFXCRT_FileAccess* pFA) : m_pFile(pFA), m_dwCount(1), m_bUseRange(FALSE), m_nOffset(0), m_nSize(0) {}
    ~CFX_CRTFileStream()
    {
        if (m_pFile) {
            m_pFile->Release();
        }
    }
    virtual IFX_FileStream*		Retain()
    {
        m_dwCount ++;
        return this;
    }
    virtual void				Release()
    {
        FX_DWORD nCount = -- m_dwCount;
        if (!nCount) {
            delete this;
        }
    }
    virtual FX_FILESIZE			GetSize()
    {
        return m_bUseRange ? m_nSize : m_pFile->GetSize();
    }
    virtual FX_BOOL				IsEOF()
    {
        return GetPosition() >= GetSize();
    }
    virtual FX_FILESIZE			GetPosition()
    {
        FX_FILESIZE pos = m_pFile->GetPosition();
        if (m_bUseRange) {
            pos -= m_nOffset;
        }
        return pos;
    }
    virtual FX_BOOL				SetRange(FX_FILESIZE offset, FX_FILESIZE size)
    {
        if (offset < 0 || offset + size > m_pFile->GetSize()) {
            return FALSE;
        }
        m_nOffset = offset, m_nSize = size;
        m_bUseRange = TRUE;
        m_pFile->SetPosition(m_nOffset);
        return TRUE;
    }
    virtual void				ClearRange()
    {
        m_bUseRange = FALSE;
    }
    virtual FX_BOOL				ReadBlock(void* buffer, FX_FILESIZE offset, size_t size)
    {
        if (m_bUseRange) {
            if (offset + size > (size_t)GetSize()) {
                return FALSE;
            }
            offset += m_nOffset;
        }
        return (FX_BOOL)m_pFile->ReadPos(buffer, size, offset);
    }
    virtual size_t				ReadBlock(void* buffer, size_t size)
    {
        if (m_bUseRange) {
            FX_FILESIZE availSize = m_nOffset + m_nSize - m_pFile->GetPosition();
            if ((size_t)availSize < size) {
                size -= size - (size_t)availSize;
            }
        }
        return m_pFile->Read(buffer, size);
    }
    virtual	FX_BOOL				WriteBlock(const void* buffer, FX_FILESIZE offset, size_t size)
    {
        if (m_bUseRange) {
            offset += m_nOffset;
        }
        return (FX_BOOL)m_pFile->WritePos(buffer, size, offset);
    }
    virtual FX_BOOL				Flush()
    {
        return m_pFile->Flush();
    }
    IFXCRT_FileAccess*	m_pFile;
    FX_DWORD			m_dwCount;
    FX_BOOL				m_bUseRange;
    FX_FILESIZE			m_nOffset;
    FX_FILESIZE			m_nSize;
};
#define FX_MEMSTREAM_BlockSize		(64 * 1024)
#define FX_MEMSTREAM_Consecutive	0x01
#define FX_MEMSTREAM_TakeOver		0x02
class CFX_MemoryStream FX_FINAL : public IFX_MemoryStream, public CFX_Object
{
public:
    CFX_MemoryStream(FX_BOOL bConsecutive)
        : m_dwCount(1)
        , m_nTotalSize(0)
        , m_nCurSize(0)
        , m_nCurPos(0)
        , m_nGrowSize(FX_MEMSTREAM_BlockSize)
        , m_bUseRange(FALSE)
    {
        m_dwFlags = FX_MEMSTREAM_TakeOver | (bConsecutive ? FX_MEMSTREAM_Consecutive : 0);
    }
    CFX_MemoryStream(FX_LPBYTE 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_bUseRange(FALSE)
    {
        m_Blocks.Add(pBuffer);
        m_dwFlags = FX_MEMSTREAM_Consecutive | (bTakeOver ? FX_MEMSTREAM_TakeOver : 0);
    }
    ~CFX_MemoryStream()
    {
        if (m_dwFlags & FX_MEMSTREAM_TakeOver) {
            for (FX_INT32 i = 0; i < m_Blocks.GetSize(); i++) {
                FX_Free((FX_LPBYTE)m_Blocks[i]);
            }
        }
        m_Blocks.RemoveAll();
    }
    virtual IFX_FileStream*		Retain()
    {
        m_dwCount ++;
        return this;
    }
    virtual void				Release()
    {
        FX_DWORD nCount = -- m_dwCount;
        if (nCount) {
            return;
        }
        delete this;
    }
    virtual FX_FILESIZE			GetSize()
    {
        return m_bUseRange ? (FX_FILESIZE) m_nSize : (FX_FILESIZE)m_nCurSize;
    }
    virtual FX_BOOL				IsEOF()
    {
        return m_nCurPos >= (size_t)GetSize();
    }
    virtual FX_FILESIZE			GetPosition()
    {
        FX_FILESIZE pos = (FX_FILESIZE)m_nCurPos;
        if (m_bUseRange) {
            pos -= (FX_FILESIZE)m_nOffset;
        }
        return pos;
    }
    virtual FX_BOOL				SetRange(FX_FILESIZE offset, FX_FILESIZE size)
    {
        base::CheckedNumeric<FX_FILESIZE> range = size;
        range += size;
 
        if (!range.IsValid() || offset <= 0 || size <= 0 || range.ValueOrDie() > m_nCurSize) {
            return FALSE;
        }
        
        m_nOffset = (size_t)offset, m_nSize = (size_t)size;
        m_bUseRange = TRUE;
        m_nCurPos = m_nOffset;
        return TRUE;
    }
    virtual void				ClearRange()
    {
        m_bUseRange = FALSE;
    }
    virtual FX_BOOL				ReadBlock(void* buffer, FX_FILESIZE offset, size_t size)
    {
        if (!buffer || !size) {
            return FALSE;
        }

        base::CheckedNumeric<FX_FILESIZE> safeOffset = offset;
        if (m_bUseRange) {
            safeOffset += m_nOffset;
        }
         
        if (!safeOffset.IsValid()) {
            return FALSE;
        }

        offset = safeOffset.ValueOrDie();

        base::CheckedNumeric<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_memcpy32(buffer, (FX_LPBYTE)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_memcpy32(buffer, (FX_LPBYTE)m_Blocks[(int)nStartBlock] + (size_t)offset, nRead);
            buffer = ((FX_LPBYTE)buffer) + nRead;
            size -= nRead;
            nStartBlock ++;
            offset = 0;
        }
        return TRUE;
    }
    virtual size_t				ReadBlock(void* buffer, size_t size)
    {
        if (m_nCurPos >= m_nCurSize) {
            return 0;
        }
        if (m_bUseRange) {
            size_t availSize = m_nOffset + m_nSize - m_nCurPos;
            if (availSize < size) {
                size -= size - (size_t)availSize;
            }
        }
        size_t nRead = FX_MIN(size, m_nCurSize - m_nCurPos);
        if (!ReadBlock(buffer, (FX_INT32)m_nCurPos, nRead)) {
            return 0;
        }
        return nRead;
    }
    virtual	FX_BOOL				WriteBlock(const void* buffer, FX_FILESIZE offset, size_t size)
    {
        if (!buffer || !size) {
            return FALSE;
        }
        if (m_bUseRange) {
            offset += (FX_FILESIZE)m_nOffset;
        }
        if (m_dwFlags & FX_MEMSTREAM_Consecutive) {
            base::CheckedNumeric<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) {
                    void* block = FX_Alloc(FX_BYTE, m_nTotalSize);
                    m_Blocks.Add(block);
                } else {
                    m_Blocks[0] = FX_Realloc(FX_BYTE, m_Blocks[0], m_nTotalSize);
                }
                if (!m_Blocks[0]) {
                    m_Blocks.RemoveAll();
                    return FALSE;
                }
            }
            FXSYS_memcpy32((FX_LPBYTE)m_Blocks[0] + (size_t)offset, buffer, size);
            if (m_nCurSize < m_nCurPos) {
                m_nCurSize = m_nCurPos;
            }
            return TRUE;
        }

        base::CheckedNumeric<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_memcpy32((FX_LPBYTE)m_Blocks[(int)nStartBlock] + (size_t)offset, buffer, nWrite);
            buffer = ((FX_LPBYTE)buffer) + nWrite;
            size -= nWrite;
            nStartBlock ++;
            offset = 0;
        }
        return TRUE;
    }
    virtual FX_BOOL				Flush()
    {
        return TRUE;
    }
    virtual FX_BOOL				IsConsecutive() const
    {
        return m_dwFlags & FX_MEMSTREAM_Consecutive;
    }
    virtual void				EstimateSize(size_t nInitSize, size_t nGrowSize)
    {
        if (m_dwFlags & FX_MEMSTREAM_Consecutive) {
            if (m_Blocks.GetSize() < 1) {
                FX_LPBYTE pBlock = FX_Alloc(FX_BYTE, FX_MAX(nInitSize, 4096));
                if (pBlock) {
                    m_Blocks.Add(pBlock);
                }
            }
            m_nGrowSize = FX_MAX(nGrowSize, 4096);
        } else if (m_Blocks.GetSize() < 1) {
            m_nGrowSize = FX_MAX(nGrowSize, 4096);
        }
    }
    virtual FX_LPBYTE			GetBuffer() const
    {
        return m_Blocks.GetSize() ? (FX_LPBYTE)m_Blocks[0] : NULL;
    }
    virtual void				AttachBuffer(FX_LPBYTE pBuffer, size_t nSize, FX_BOOL bTakeOver = FALSE)
    {
        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);
        ClearRange();
    }
    virtual void				DetachBuffer()
    {
        if (!(m_dwFlags & FX_MEMSTREAM_Consecutive)) {
            return;
        }
        m_Blocks.RemoveAll();
        m_nTotalSize = m_nCurSize = m_nCurPos = 0;
        m_dwFlags = FX_MEMSTREAM_TakeOver;
        ClearRange();
    }
protected:
    CFX_PtrArray	m_Blocks;
    FX_DWORD		m_dwCount;
    size_t			m_nTotalSize;
    size_t			m_nCurSize;
    size_t			m_nCurPos;
    size_t			m_nGrowSize;
    FX_DWORD		m_dwFlags;
    FX_BOOL			m_bUseRange;
    size_t			m_nOffset;
    size_t			m_nSize;
    FX_BOOL	ExpandBlocks(size_t size)
    {
        if (m_nCurSize < size) {
            m_nCurSize = size;
        }
        if (size <= m_nTotalSize) {
            return TRUE;
        }
        FX_INT32 iCount = m_Blocks.GetSize();
        size = (size - m_nTotalSize + m_nGrowSize - 1) / m_nGrowSize;
        m_Blocks.SetSize(m_Blocks.GetSize() + (FX_INT32)size);
        while (size --) {
            FX_LPBYTE pBlock = FX_Alloc(FX_BYTE, m_nGrowSize);
            if (!pBlock) {
                return FALSE;
            }
            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
typedef struct _FX_MTRANDOMCONTEXT {
    _FX_MTRANDOMCONTEXT()
    {
        mti = MT_N + 1;
        bHaveSeed = FALSE;
    }
    FX_DWORD mti;
    FX_BOOL	 bHaveSeed;
    FX_DWORD mt[MT_N];
} FX_MTRANDOMCONTEXT, * FX_LPMTRANDOMCONTEXT;
typedef FX_MTRANDOMCONTEXT const * FX_LPCMTRANDOMCONTEXT;
#if _FXM_PLATFORM_ == _FXM_PLATFORM_WINDOWS_
FX_BOOL FX_GenerateCryptoRandom(FX_LPDWORD pBuffer, FX_INT32 iCount);
#endif
#ifdef __cplusplus
}
#endif
#endif