/*
 * Copyright (c) 2021 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 HIPERF_CALLSTACK_H
#define HIPERF_CALLSTACK_H

#if HAVE_LIBUNWIND
// for libunwind.h empty struct has size 0 in c, size 1 in c++
#define UNW_EMPTY_STRUCT uint8_t unused;
#include <libunwind.h>
#endif

#include <map>
#include <optional>
#include <string>
#include <vector>

#if !is_mingw
#include <sys/mman.h>
#endif

#include "hashlist.hpp"
#include "register.h"
#include "utilities.h"
#include "virtual_thread.h"

namespace OHOS {
namespace Developtools {
namespace HiPerf {
const int MAX_CALL_FRAME_EXPAND_CYCLE = 10;
const size_t MAX_CALL_FRAME_EXPAND_CACHE_SIZE = 10;
const size_t MAX_CALL_FRAME_UNWIND_SIZE = 256;
// if ip is 0 , 1 both not useful
const uint64_t BAD_IP_ADDRESS = 2;

#if HAVE_LIBUNWIND
struct UnwindInfo;
#endif

class CallStack {
public:
    CallStack();
    ~CallStack();
    bool UnwindCallStack(const VirtualThread &thread, bool abi32, u64 *regs, u64 regsNum,
                         const u8 *stack, u64 stackSize, std::vector<CallFrame> &,
                         size_t maxStackLevel = MAX_CALL_FRAME_UNWIND_SIZE);
    size_t ExpandCallStack(pid_t tid, std::vector<CallFrame> &callFrames, size_t expandLimit = 1u);

private:
    uint64_t stackPoint_ = 0;
    uint64_t stackEnd_ = 0;
    u64 *regs_ = nullptr; // not const , be cause we will fix it for arm64 cpu in UpdateRegForABI
    u64 regsNum_ = 0;
    const u8 *stack_ = nullptr;
    u64 stackSize_ = 0;

    void LogFrame(const std::string msg, const std::vector<CallFrame> &frames);
    size_t DoExpandCallStack(std::vector<CallFrame> &newCallFrames,
                           const std::vector<CallFrame> &cachedCallFrames, size_t expandLimit);

    // we have a cache for all thread
    std::map<pid_t, HashList<uint64_t, std::vector<CallFrame>>> cachedCallFramesMap_;
    bool GetIpSP(uint64_t &ip, uint64_t &sp, const u64 *regs, size_t regNum) const;
    ArchType arch_ = ArchType::UNSUPPORT;
#if HAVE_LIBUNWIND
    static bool ReadVirtualThreadMemory(UnwindInfo &unwindInfoPtr, unw_word_t addr,
                                        unw_word_t *data);
    static const std::string GetUnwErrorName(int error);
    static void dumpUDI(unw_dyn_info_t &di);
    static bool fillUDI(unw_dyn_info_t &di, SymbolsFile &symbolsFile, const MemMapItem &mmap,
                        const VirtualThread &thread);
    static int FindProcInfo(unw_addr_space_t as, unw_word_t ip, unw_proc_info_t *pi,
                            int need_unwind_info, void *arg);
    static int AccessMem(unw_addr_space_t as, unw_word_t addr, unw_word_t *valuePoint,
                         int writeOperation, void *arg);
    static int AccessReg(unw_addr_space_t as, unw_regnum_t regnum, unw_word_t *valuePoint,
                         int writeOperation, void *arg);
    static void PutUnwindInfo(unw_addr_space_t as, unw_proc_info_t *pi, void *arg);
    static int AccessFpreg(unw_addr_space_t as, unw_regnum_t num, unw_fpreg_t *val,
                           int writeOperation, void *arg);
    static int GetDynInfoListAaddr(unw_addr_space_t as, unw_word_t *dil_vaddr, void *arg);
    static int Resume(unw_addr_space_t as, unw_cursor_t *cu, void *arg);
    static int getProcName(unw_addr_space_t as, unw_word_t addr, char *bufp, size_t buf_len,
                           unw_word_t *offp, void *arg);
    static int FindUnwindTable(SymbolsFile *symbolsFile, const MemMapItem &mmap,
                               UnwindInfo *unwindInfoPtr, unw_addr_space_t as, unw_word_t ip,
                               unw_proc_info_t *pi, int need_unwind_info, void *arg);
    void UnwindStep(unw_cursor_t &c, std::vector<CallFrame> &callFrames, size_t maxStackLevel);
    std::unordered_map<pid_t, unw_addr_space_t> unwindAddrSpaceMap_;

    using dsoUnwDynInfoMap = std::unordered_map<std::string, std::optional<unw_dyn_info_t>>;
    std::unordered_map<pid_t, dsoUnwDynInfoMap> unwindDynInfoMap_;

    using unwMemoryCache = std::unordered_map<unw_word_t, unw_word_t>;
    std::unordered_map<pid_t, unwMemoryCache> porcessMemoryMap_;

    unw_accessors_t accessors_ = {
        .find_proc_info = FindProcInfo,
        .put_unwind_info = PutUnwindInfo,
        .get_dyn_info_list_addr = GetDynInfoListAaddr,
        .access_mem = AccessMem,
        .access_reg = AccessReg,
        .access_fpreg = AccessFpreg,
        .resume = Resume,
        .get_proc_name = getProcName,
    };
    bool DoUnwind(const VirtualThread &thread, std::vector<CallFrame> &callStack,
                  size_t maxStackLevel);
#endif

    FRIEND_TEST(CallStackTest, ExpendCallStackFullCache);
    FRIEND_TEST(CallStackTest, LibUnwindEmptyFunc);
    FRIEND_TEST(CallStackTest, GetUnwErrorName);
};

#if HAVE_LIBUNWIND
struct UnwindInfo {
    const VirtualThread &thread;
    const u64 *regs;
    size_t regNumber;
    ArchType arch;
    CallStack &callStack;
};
#endif
} // namespace HiPerf
} // namespace Developtools
} // namespace OHOS
#endif // HIPERF_CALLSTACK_H