/*
 * Copyright 2011, The Android Open Source Project
 *
 * 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 ELF_SYMBOL_HXX
#define ELF_SYMBOL_HXX

#include "ELFSectionHeaderTable.h"
#include "ELFSection.h"
#include "ELFSectionStrTab.h"

#include "ELFObject.h"
#include "ELFSectionHeaderTable.h"
#include "ELFSectionProgBits.h"
#include "ELFSectionNoBits.h"

#include "utils/rsl_assert.h"
#include "ELF.h"

template <unsigned Bitwidth>
inline char const *ELFSymbol_CRTP<Bitwidth>::getName() const {
  ELFSectionHeaderTableTy const &shtab = *owner->getSectionHeaderTable();
  size_t const index = shtab.getByName(std::string(".strtab"))->getIndex();
  ELFSectionTy const *section = owner->getSectionByIndex(index);
  ELFSectionStrTabTy const &strtab =
    *static_cast<ELFSectionStrTabTy const *>(section);
  return strtab[getNameIndex()];
}

template <unsigned Bitwidth>
template <typename Archiver>
inline ELFSymbol<Bitwidth> *
ELFSymbol_CRTP<Bitwidth>::read(Archiver &AR,
                               ELFObjectTy const *owner,
                               size_t index) {
  if (!AR) {
    // Archiver is in bad state before calling read function.
    // Return NULL and do nothing.
    return 0;
  }

  llvm::OwningPtr<ELFSymbolTy> sh(new ELFSymbolTy());

  if (!sh->serialize(AR)) {
    // Unable to read the structure.  Return NULL.
    return 0;
  }

  if (!sh->isValid()) {
    // SymTabEntry read from archiver is not valid.  Return NULL.
    return 0;
  }

  // Set the section header index
  sh->index = index;

  // Set the owner elf object
  sh->owner = owner;

  return sh.take();
}

template <unsigned Bitwidth>
inline void ELFSymbol_CRTP<Bitwidth>::print(bool shouldPrintHeader) const {
  using namespace llvm;

  if (shouldPrintHeader) {
    out() << '\n' << fillformat('=', 79) << '\n';
    out().changeColor(raw_ostream::WHITE, true);
    out() << "ELF Symbol Table Entry "
          << this->getIndex() << '\n';
    out().resetColor();
    out() << fillformat('-', 79) << '\n';
  } else {
    out() << fillformat('-', 79) << '\n';
    out().changeColor(raw_ostream::YELLOW, true);
    out() << "ELF Symbol Table Entry "
          << this->getIndex() << " : " << '\n';
    out().resetColor();
  }

#define PRINT_LINT(title, value) \
  out() << format("  %-11s : ", (char const *)(title)) << (value) << '\n'
  PRINT_LINT("Name",        getName()                                    );
  PRINT_LINT("Type",        getTypeStr(getType())                        );
  PRINT_LINT("Bind",        getBindingAttributeStr(getBindingAttribute()));
  PRINT_LINT("Visibility",  getVisibilityStr(getVisibility())            );
  PRINT_LINT("Shtab Index", getSectionIndex()                            );
  PRINT_LINT("Value",       getValue()                                   );
  PRINT_LINT("Size",        getSize()                                    );
#undef PRINT_LINT

// TODO: Horizontal type or vertical type can use option to decide.
#if 0
  using namespace term::color;
  using namespace std;

  cout << setw(20) << getName() <<
          setw(10) << getTypeStr(getType()) <<
          setw(10) << getBindingAttributeStr(getBindingAttribute()) <<
          setw(15) << getVisibilityStr(getVisibility()) <<
          setw(10) << getSectionIndex() <<
          setw(7) << getValue() <<
          setw(7) << getSize() <<
          endl;
#endif
}

template <unsigned Bitwidth>
void *ELFSymbol_CRTP<Bitwidth>::getAddress(int machine, bool autoAlloc) const {
  if (my_addr != 0) {
    return my_addr;
  }
  size_t idx = (size_t)getSectionIndex();
  switch (getType()) {
    default:
      break;

    case STT_OBJECT:
      switch (idx) {
        default:
          {
            ELFSectionHeaderTableTy const *header =
              owner->getSectionHeaderTable();

            unsigned section_type = (*header)[idx]->getType();

            rsl_assert((section_type == SHT_PROGBITS ||
                        section_type == SHT_NOBITS) &&
                       "STT_OBJECT with not BITS section.");

            if (section_type == SHT_NOBITS) {
              // FIXME(logan): This is a workaround for .lcomm directives
              // bug of LLVM ARM MC code generator.  Remove this when the
              // LLVM bug is fixed.

              size_t align = 16;

              my_addr = const_cast<ELFObjectTy *>(owner)->
                allocateSHNCommonData((size_t)getSize(), align);

              if (!my_addr) {
                rsl_assert(0 && "Unable to allocate memory for SHN_COMMON.");
                abort();
              }
            } else {
              ELFSectionTy const *sec = owner->getSectionByIndex(idx);
              rsl_assert(sec != 0 && "STT_OBJECT with null section.");

              ELFSectionBitsTy const &st =
                static_cast<ELFSectionBitsTy const &>(*sec);
              my_addr =const_cast<unsigned char *>(&st[0] + (off_t)getValue());
            }
          }
          break;

        case SHN_COMMON:
          {
            if (!autoAlloc) {
              return NULL;
            }
#if 0
#if _POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600
            if (posix_memalign(&my_addr,
                               std::max((size_t)getValue(), sizeof(void*)),
                               (size_t)getSize()) != 0) {
              rsl_assert(0 && "posix_memalign failed.");
            }
#else
            my_addr = memalign(std::max((size_t)getValue(), sizeof(void *)),
                               (size_t)getSize());

            rsl_assert(my_addr != NULL && "memalign failed.");
#endif
            if (my_addr) {
              memset(my_addr, '\0', getSize());
            }
#else
            size_t align = (size_t)getValue();
            my_addr = const_cast<ELFObjectTy *>(owner)->
                          allocateSHNCommonData((size_t)getSize(), align);
            if (!my_addr) {
              rsl_assert(0 && "Unable to allocate memory for SHN_COMMON.");
              abort();
            }
#endif
          }
          break;

        case SHN_UNDEF:
          if (machine == EM_MIPS && strcmp(getName(), "_gp_disp") == 0) // OK for MIPS
            break;

        case SHN_ABS:
        case SHN_XINDEX:
          rsl_assert(0 && "STT_OBJECT with special st_shndx.");
          break;
      }
      break;


    case STT_FUNC:
      switch (idx) {
        default:
          {
#ifndef NDEBUG
            ELFSectionHeaderTableTy const *header =
              owner->getSectionHeaderTable();
            rsl_assert((*header)[idx]->getType() == SHT_PROGBITS &&
                   "STT_FUNC with not PROGBITS section.");
#endif
            ELFSectionTy const *sec = owner->getSectionByIndex(idx);
            rsl_assert(sec != 0 && "STT_FUNC with null section.");

            ELFSectionProgBitsTy const &st =
              static_cast<ELFSectionProgBitsTy const &>(*sec);
            my_addr = const_cast<unsigned char *>(&st[0] + (off_t)getValue());
          }
          break;

        case SHN_ABS:
        case SHN_COMMON:
        case SHN_UNDEF:
        case SHN_XINDEX:
          rsl_assert(0 && "STT_FUNC with special st_shndx.");
          break;
      }
      break;


    case STT_SECTION:
      switch (idx) {
        default:
          {
#ifndef NDEBUG
            ELFSectionHeaderTableTy const *header =
              owner->getSectionHeaderTable();
            rsl_assert(((*header)[idx]->getType() == SHT_PROGBITS ||
                    (*header)[idx]->getType() == SHT_NOBITS) &&
                   "STT_SECTION with not BITS section.");
#endif
            ELFSectionTy const *sec = owner->getSectionByIndex(idx);
            rsl_assert(sec != 0 && "STT_SECTION with null section.");

            ELFSectionBitsTy const &st =
              static_cast<ELFSectionBitsTy const &>(*sec);
            my_addr = const_cast<unsigned char *>(&st[0] + (off_t)getValue());
          }
          break;

        case SHN_ABS:
        case SHN_COMMON:
        case SHN_UNDEF:
        case SHN_XINDEX:
          rsl_assert(0 && "STT_SECTION with special st_shndx.");
          break;
      }
      break;

    case STT_NOTYPE:
      switch (idx) {
        default:
          {
#ifndef NDEBUG
            ELFSectionHeaderTableTy const *header =
              owner->getSectionHeaderTable();
            rsl_assert(((*header)[idx]->getType() == SHT_PROGBITS ||
                    (*header)[idx]->getType() == SHT_NOBITS) &&
                   "STT_SECTION with not BITS section.");
#endif
            ELFSectionTy const *sec = owner->getSectionByIndex(idx);
            rsl_assert(sec != 0 && "STT_SECTION with null section.");

            ELFSectionBitsTy const &st =
              static_cast<ELFSectionBitsTy const &>(*sec);
            my_addr = const_cast<unsigned char *>(&st[0] + (off_t)getValue());
          }
          break;

        case SHN_ABS:
        case SHN_COMMON:
        case SHN_XINDEX:
          rsl_assert(0 && "STT_SECTION with special st_shndx.");
          break;
        case SHN_UNDEF:
          return 0;
      }
      break;
      return 0;

    case STT_COMMON:
    case STT_FILE:
    case STT_TLS:
    case STT_LOOS:
    case STT_HIOS:
    case STT_LOPROC:
    case STT_HIPROC:
      rsl_assert(0 && "Not implement.");
      return 0;
  }
  return my_addr;
}

#endif // ELF_SYMBOL_HXX