/*
 * Copyright (c) 2022 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ECMASCRIPT_PLATFORM_FILE_H
#define ECMASCRIPT_PLATFORM_FILE_H

#include <system_error>
#ifdef PANDA_TARGET_WINDOWS
#include <windef.h>
#include <winbase.h>
#include <winnt.h>
#else
#include <fcntl.h>
#include <dlfcn.h>
#endif

#include <string>

#include "ecmascript/platform/map.h"

namespace panda::ecmascript {
class JSThread;
#ifdef PANDA_TARGET_WINDOWS
using fd_t = HANDLE;
#define INVALID_FD INVALID_HANDLE_VALUE

#define FILE_RDONLY GENERIC_READ
#define FILE_WRONLY GENERIC_WRITE
#define FILE_RDWR (GENERIC_READ | GENERIC_WRITE)
#define FILE_CREAT CREATE_ALWAYS
#define FILE_TRUNC TRUNCATE_EXISTING

#ifdef ERROR
#undef ERROR
#endif

#ifdef VOID
#undef VOID
#endif

#ifdef CONST
#undef CONST
#endif
#else
using fd_t = int;
#define INVALID_FD (-1)

#define FILE_RDONLY O_RDONLY
#define FILE_WRONLY O_WRONLY
#define FILE_RDWR O_RDWR
#define FILE_CREAT O_CREAT
#define FILE_TRUNC O_TRUNC
#endif

#define FILE_SUCCESS 1
#define FILE_FAILED 0

#define FILE_MS_SYNC 4 // Synchronous memory sync

std::string PUBLIC_API GetFileDelimiter();
std::string PUBLIC_API GetPathSeparator();
bool PUBLIC_API RealPath(const std::string &path, std::string &realPath, bool readOnly = true);
bool PUBLIC_API RealPathByChar(const char *path, char *realPath, int rowLength, bool readOnly);
void DPrintf(fd_t fd, const std::string &buffer);
void PUBLIC_API FdsanExchangeOwnerTag(fd_t fd);
void PUBLIC_API Close(fd_t fd);
void FSync(fd_t fd);
MemMap PUBLIC_API FileMap(const char *fileName, int flag, int prot, int64_t offset = 0);
MemMap PUBLIC_API CreateFileMap([[maybe_unused]] const char *fileName, [[maybe_unused]] int fileSize,
                                [[maybe_unused]] int flag, [[maybe_unused]] int prot);
MemMap PUBLIC_API FileMapForAlignAddressByFd(const fd_t fd, int prot, int64_t offset, uint32_t offStart);
int PUBLIC_API FileUnMap(MemMap addr);
int PUBLIC_API FileSync(MemMap addr, int flag);
CString ResolveFilenameFromNative(JSThread *thread, const CString &dirname, CString request);
bool PUBLIC_API FileExist(const char *filename);
int PUBLIC_API Unlink(const char *filename);
void *LoadLib(const std::string &libname);
void *FindSymbol(void *handle, const char *symbol);
int CloseLib(void *handle);
char *LoadLibError();
void PUBLIC_API DeleteFilesWithSuffix(const std::string &dirPath, const std::string &suffix);
bool Chmod(const std::string_view &path, const std::string_view &mode);
bool Chmod(const std::string_view &path, const std::string_view &mode, std::error_code &errorCode);
// `existOk` default to true for compatibility, when `existOk` is false.
// if `newPath` already exist, rename will fail with `EEXIST` error.
std::error_code Rename(const char *oldPath, const char *newPath, bool existOk = true);

enum class FileType : uint8_t {
    BLOCK,
    CHARACTER,
    DIRECTORY,
    FIFO,
    SYMLINK,
    REGULAR,
    SOCKET,
    UNKNOWN,
};
using IterdirCallback = std::function<void(const std::string_view&, FileType fileType)>;
std::error_code Iterdir(const std::string& path, const IterdirCallback& callback);

class MemMapScope {
public:
    MemMapScope(MemMap memMap) : memMap_(memMap) {}
    ~MemMapScope()
    {
        if (escaped_) {
            return;
        }
        FileUnMap(memMap_);
    }

    void Escape()
    {
        escaped_ = true;
    }

private:
    MemMap memMap_ {};
    bool escaped_ {false};
};

class FileMemMapReader {
public:
    using ReadFailedCallback = std::function<void()>;
    FileMemMapReader(const MemMap &memMap, ReadFailedCallback readFailedCallback, CString logTag) : memMap_(memMap),
        readFailedCallback_(std::move(readFailedCallback)), readPtr_(static_cast<uint8_t *>(memMap.GetOriginAddr())),
        remainingSize_(memMap.GetSize()), fileSize_(memMap.GetSize()), logTag_(std::move(logTag)) {}

    template<typename T>
    bool ReadSingleData(T *readDst, const size_t readSize, const CString &message)
    {
        if (remainingSize_ < readSize) {
            LOG_ECMA(ERROR) << logTag_ << " read " << message << " remainingSize not sufficient";
            readFailedCallback_();
            return false;
        }
        if (memcpy_s(readDst, readSize, readPtr_, readSize) != EOK) {
            LOG_ECMA(ERROR) << logTag_ << " memcpy_s read " << message << " failed";
            readFailedCallback_();
            return false;
        }
        Step(readSize);
        return true;
    }

    bool ReadString(CString &readDst, const size_t readSize, const CString &message)
    {
        readDst.reserve(readSize);
        if (remainingSize_ < readSize) {
            LOG_ECMA(ERROR) << logTag_ << " read " << message << " remainingSize not sufficient";
            readFailedCallback_();
            return false;
        }
        readDst.assign(reinterpret_cast<const char*>(readPtr_), readSize);
        Step(readSize);
        return true;
    }

    template<typename T>
    bool ReadFromOffset(T *readDst, const size_t readSize, const size_t offset, const CString &message)
    {
        if (fileSize_ < offset + readSize) {
            LOG_ECMA(ERROR) << logTag_ << " read " << message << " fileSize_ not sufficient";
            readFailedCallback_();
            return false;
        }
        uint8_t *readPtr = static_cast<uint8_t *>(memMap_.GetOriginAddr());
        if (memcpy_s(readDst, readSize, readPtr + offset, readSize) != EOK) {
            LOG_ECMA(ERROR) << logTag_ << " memcpy_s read " << message << " failed";
            readFailedCallback_();
            return false;
        }
        return true;
    }

    bool Step(const size_t stepSize)
    {
        if (remainingSize_ < stepSize) {
            return false;
        }
        readPtr_ += stepSize;
        remainingSize_ -= stepSize;
        return true;
    }

    uint8_t *GetReadPtr() const
    {
        return readPtr_;
    }

private:
    MemMap memMap_ {};
    ReadFailedCallback readFailedCallback_ {};
    uint8_t *readPtr_ { nullptr };
    size_t remainingSize_ { 0 };
    size_t fileSize_ { 0 };
    CString logTag_ {};
};

class FileMemMapWriter {
public:
    FileMemMapWriter(const MemMap &memMap, CString logTag) : memMap_(memMap),
        writePtr_(static_cast<uint8_t *>(memMap.GetOriginAddr())),
        fileSize_(memMap.GetSize()), logTag_(std::move(logTag)) {}

    template<typename T>
    bool WriteSingleData(T *writeSrc, const size_t writeSize, const CString &message)
    {
        if (memcpy_s(writePtr_, GetRemainingSize(), writeSrc, writeSize) != EOK) {
            LOG_ECMA(ERROR) << logTag_ << " memcpy_s write " << message << " failed";
            return false;
        }
        writePtr_ += writeSize;
        return true;
    }

    uint8_t *GetWritePtr() const
    {
        return writePtr_;
    }

    bool WriteAlignUpPadding(const size_t paddingSize)
    {
        if (paddingSize <= 0) {
            return true;
        }
        if (memset_s(writePtr_, GetRemainingSize(), 0, paddingSize) != EOK) {
            LOG_ECMA(ERROR) << logTag_ << " memset_s WriteAlignUpPadding " << paddingSize << " failed";
            return false;
        }
        writePtr_ += paddingSize;
        return true;
    }

    inline size_t GetWrittenSize() const
    {
        if (writePtr_ <= memMap_.GetOriginAddr()) {
            return 0;
        }
        return writePtr_ - static_cast<uint8_t*>(memMap_.GetOriginAddr());
    }

    inline size_t GetRemainingSize() const
    {
        if (GetWrittenSize() >= fileSize_) {
            return 0;
        }
        return fileSize_ - GetWrittenSize();
    }

private:
    MemMap memMap_ {};
    uint8_t *writePtr_ { nullptr };
    size_t fileSize_ { 0 };
    CString logTag_ {};
};

/**
 * @brief posix file reader/writer, safe to use in signal handler (without malloc)
 */
class PosixFile {
public:
    enum class SeekOrigin {
        SET = 0,
        CURRENT,
        END,
    };
    PosixFile(const std::string_view &path, const std::string_view &flags, const std::string_view &mode = DEFAULT_MODE);
    ~PosixFile();
    int64_t Write(const char *data, size_t size);
    template <typename Container> int64_t Write(const Container &data) { return Write(data.data(), data.size()); }
    int64_t Read(char *buf, size_t size);
    template <typename Container> int64_t Read(Container &data) { return Read(data.data(), data.size()); }
    int64_t Seek(int64_t off, SeekOrigin whence);
    int64_t Tell();
    inline bool IsValid() { return fd_ != INVALID_FD; }
    int64_t Size();

private:
    static constexpr std::string_view DEFAULT_MODE = "rw-r--r--";

    fd_t fd_{INVALID_FD};
};
}  // namespace panda::ecmascript
#endif  // ECMASCRIPT_PLATFORM_FILE_H