/*
 * Copyright (c) 2016 GitHub, Inc.
 *
 * 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.
 */
#pragma once

#include <algorithm>
#include <memory>
#include <string>
#include <sys/types.h>
#include <unordered_map>
#include <unordered_set>
#include <vector>

#include "bcc_syms.h"
#include "file_desc.h"
#include "ns_guard.h"

class ProcStat {
  std::string procfs_;
  ino_t inode_;
  ino_t getinode_();

public:
  ProcStat(int pid);
  bool is_stale();
  void reset() { inode_ = getinode_(); }
};

class SymbolCache {
public:
  virtual ~SymbolCache() = default;

  virtual void refresh() = 0;
  virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym, bool demangle = true) = 0;
  virtual bool resolve_name(const char *module, const char *name,
                            uint64_t *addr) = 0;
};

class KSyms : SymbolCache {
  struct Symbol {
    Symbol(const char *name, uint64_t addr) : name(name), addr(addr) {}
    std::string name;
    uint64_t addr;

    bool operator<(const Symbol &rhs) const { return addr < rhs.addr; }
  };

  std::vector<Symbol> syms_;
  std::unordered_map<std::string, uint64_t> symnames_;
  static void _add_symbol(const char *, uint64_t, void *);

public:
  virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym, bool demangle = true);
  virtual bool resolve_name(const char *unused, const char *name,
                            uint64_t *addr);
  virtual void refresh();
};

class ProcSyms : SymbolCache {
  struct Symbol {
    Symbol(const std::string *name, uint64_t start, uint64_t size)
        : name(name), start(start), size(size) {}
    const std::string *name;
    uint64_t start;
    uint64_t size;

    bool operator<(const struct Symbol& rhs) const {
      return start < rhs.start;
    }
  };

  enum class ModuleType {
    UNKNOWN,
    EXEC,
    SO,
    PERF_MAP,
    VDSO
  };

  struct Module {
    struct Range {
      uint64_t start;
      uint64_t end;
      uint64_t file_offset;
      Range(uint64_t s, uint64_t e, uint64_t f)
          : start(s), end(e), file_offset(f) {}
    };

    Module(const char *name, ProcMountNS *mount_ns,
           struct bcc_symbol_option *option);

    std::string name_;
    std::vector<Range> ranges_;
    bool loaded_;
    ProcMountNS *mount_ns_;
    bcc_symbol_option *symbol_option_;
    ModuleType type_;

    // The file offset within the ELF of the SO's first text section.
    uint64_t elf_so_offset_;
    uint64_t elf_so_addr_;

    std::unordered_set<std::string> symnames_;
    std::vector<Symbol> syms_;

    void load_sym_table();

    bool contains(uint64_t addr, uint64_t &offset) const;
    uint64_t start() const { return ranges_.begin()->start; }

    bool find_addr(uint64_t offset, struct bcc_symbol *sym);
    bool find_name(const char *symname, uint64_t *addr);

    static int _add_symbol(const char *symname, uint64_t start, uint64_t size,
                           void *p);
  };

  int pid_;
  std::vector<Module> modules_;
  ProcStat procstat_;
  std::unique_ptr<ProcMountNS> mount_ns_instance_;
  bcc_symbol_option symbol_option_;

  static int _add_load_sections(uint64_t v_addr, uint64_t mem_sz,
                                uint64_t file_offset, void *payload);
  static int _add_module(const char *, uint64_t, uint64_t, uint64_t, bool,
                         void *);
  void load_exe();
  void load_modules();

public:
  ProcSyms(int pid, struct bcc_symbol_option *option = nullptr);
  virtual void refresh();
  virtual bool resolve_addr(uint64_t addr, struct bcc_symbol *sym, bool demangle = true);
  virtual bool resolve_name(const char *module, const char *name,
                            uint64_t *addr);
};

class BuildSyms {
  struct Symbol {
    Symbol(const std::string *name, uint64_t start, uint64_t size)
      :name(name), start(start), size(size) {}
    const std::string *name;
    uint64_t start;
    uint64_t size;

    bool operator<(const struct Symbol &rhs) const {
      return start < rhs.start;
    }
  };

  struct Module {
    Module(const char *module_name):
      module_name_(module_name),
      loaded_(false) {}
    const std::string module_name_;
    const std::string build_id_;
    bool loaded_;
    std::unordered_set<std::string> symnames_;
    std::vector<Symbol> syms_;
    bcc_symbol_option symbol_option_;

    bool load_sym_table();
    static int _add_symbol(const char *symname, uint64_t start, uint64_t size,
                            void *p);
    bool resolve_addr(uint64_t offset, struct bcc_symbol*, bool demangle=true);
  };

  std::unordered_map<std::string, std::unique_ptr<Module> > buildmap_;

public:
  BuildSyms() {}
  virtual ~BuildSyms() = default;
  virtual bool add_module(const std::string module_name);
  virtual bool resolve_addr(std::string build_id, uint64_t offset, struct bcc_symbol *sym, bool demangle = true);
};
