/* Copyright 2017 - 2022 R. Thomas
 * Copyright 2017 - 2022 Quarkslab
 *
 * 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.
 */
#include "logging.hpp"

#include "LIEF/utils.hpp"
#include "LIEF/ART/Parser.hpp"
#include "LIEF/ART/File.hpp"
#include "LIEF/ART/EnumToString.hpp"

namespace LIEF {
namespace ART {

template<typename ART_T>
void Parser::parse_file() {
  LIEF_DEBUG("Parsing ART version {}", ART_T::art_version);
  /* const size_t ptr_size = */ parse_header<ART_T>();
}

template<typename ART_T>
size_t Parser::parse_header() {
  using art_header_t = typename ART_T::art_header_t;

  const auto res_hdr = stream_->peek<art_header_t>(0);
  if (!res_hdr) {
    return 0;
  }
  const auto hdr = std::move(*res_hdr);
  imagebase_ = hdr.image_begin;

  if (hdr.pointer_size != sizeof(uint32_t) && hdr.pointer_size != sizeof(uint64_t)) {
    throw corrupted("Wrong pointer size!");
  }
  file_->header_ = &hdr;
  return hdr.pointer_size;
}

#if 0
template<typename ART_T, typename PTR_T>
void Parser::parse_sections() {
  using IMAGE_SECTIONS = typename ART_T::IMAGE_SECTIONS;
  using art_header_t   = typename ART_T::art_header_t;
  using jarray_t       = typename ART_T::template jarray_t<>;
  using jclass_t       = typename ART_T::template jclass_t<>;
  using jobject_t       = typename ART_T::template jobject_t<>;

  VLOG(VDEBUG) << "Parsing Image Sections" << std::endl;
  size_t nb_sections = file_->header().nb_sections_;

  const art_header_t& hdr = stream_->peek<art_header_t>(0);

  VLOG(VDEBUG) << "Parsing " << std::dec << file_->header().nb_sections_ << " sections";

  size_t start_offset = align(sizeof(art_header_t), sizeof(uint64_t));
  // TODO: Check section size number
  for (size_t i = 0; i < nb_sections; ++i) {
    IMAGE_SECTIONS section_type = static_cast<IMAGE_SECTIONS>(i);
    ART::image_section_t section_header = hdr.sections[i];

    uint64_t jarray_offset = start_offset;
    if (i == 1) {
      jarray_offset = align(sizeof(art_header_t) + sizeof(jarray_t) + (3409 - 1) * sizeof(uint32_t), sizeof(uint64_t));
    }

    if (i == 2) {
      jarray_offset = align(sizeof(art_header_t) + sizeof(jarray_t) + (3409 - 1) * sizeof(uint32_t), sizeof(uint64_t));
      jarray_offset += sizeof(jarray_t) + 2 * (32062 - 1) * (sizeof(uint32_t));
      jarray_offset = align(jarray_offset, sizeof(uint64_t));
      //jarray_offset = 0x700e1db0 - imagebase_;
    }
    std::cout << "addr:" << std::hex << imagebase_ + jarray_offset << std::endl;

    const jarray_t* array = reinterpret_cast<const jarray_t*>(stream_->read(jarray_offset, sizeof(jarray_t)));
    uint64_t elements_offset = jarray_offset + offsetof(jarray_t, elements);



    VLOG(VDEBUG) << to_string(section_type) << "@" << std::hex << section_header.offset
                 << " --> " << (section_header.offset + section_header.size)
                 << " #" << std::dec << array->length;

    std::cout << std::hex << stream_->read_integer<uint32_t>(jarray_offset) << std::endl;
    std::cout << std::hex << stream_->read_integer<uint32_t>(jarray_offset + sizeof(uint32_t)) << std::endl;
    std::cout << std::hex << stream_->read_integer<uint32_t>(jarray_offset + 2 * sizeof(uint32_t)) << std::endl;
    std::cout << std::hex << stream_->read_integer<uint32_t>(jarray_offset + 3 * sizeof(uint32_t)) << std::endl;
    std::cout << std::hex << stream_->read_integer<uint32_t>(jarray_offset + 4 * sizeof(uint32_t)) << std::endl;


    uint32_t parent  = array->object.klass - imagebase_;
    const jclass_t* pp = reinterpret_cast<const jclass_t*>(stream_->read(parent, sizeof(jclass_t)));
    parse_jstring<ART_T, PTR_T>(pp->name - imagebase_);

    switch (section_type) {
      case IMAGE_SECTIONS::SECTION_OBJECTS:
        {

          // '0x70000090'
          parse_objects<ART_T, PTR_T>(elements_offset, array->length);
          break;
        }

      case IMAGE_SECTIONS::SECTION_ART_FIELDS:
        {
          // '0x700035e0'
          parse_art_fields<ART_T, PTR_T>(elements_offset, array->length);
          break;
        }

      case IMAGE_SECTIONS::SECTION_ART_METHODS:
        {
          // 0x6ff99db0: long[] length:65533
          parse_art_methods<ART_T, PTR_T>(elements_offset, array->length);
          break;
        }

      case IMAGE_SECTIONS::SECTION_INTERNED_STRINGS:
        {
          parse_interned_strings<ART_T, PTR_T>(elements_offset, array->length);
          break;
        }

      default:
        {
          LOG(WARNING) << to_string(section_type) << " is !handle yet!";
        }
    }

  }
}


template<typename ART_T, typename PTR_T>
void Parser::parse_roots() {
  using jarray_t = typename ART_T::template jarray_t<>;
  VLOG(VDEBUG) << "Parsing Image Roots" << std::endl;
  using IMAGE_ROOTS = typename ART_T::IMAGE_ROOTS;

  uint32_t image_root_offset = file_->header().image_roots_ - file_->header().image_begin_;

  VLOG(VDEBUG) << "Image root at: " << std::hex << std::showbase << file_->header().image_roots_;
  VLOG(VDEBUG) << "Image root offset: " << std::hex << std::showbase << image_root_offset;

  const jarray_t* array = reinterpret_cast<const jarray_t*>(stream_->read(image_root_offset, sizeof(jarray_t)));
  std::cout << "Nb elements: " << array->length << std::endl;

  const uint32_t* array_values = reinterpret_cast<const uint32_t*>(
      stream_->read(
        image_root_offset + offsetof(jarray_t, elements),
        array->length * sizeof(uint32_t)
      ));


  for (size_t i = 0; i < ART_T::nb_image_roots; ++i) {
    IMAGE_ROOTS type = static_cast<IMAGE_ROOTS>(i);
    uint64_t struct_offset = array_values[i] - imagebase_;
    std::cout << std::hex << struct_offset << std::endl;
    const jarray_t* array = reinterpret_cast<const jarray_t*>(stream_->read(struct_offset, sizeof(jarray_t)));
    std::cout << "Nb elements: " << std::dec << array->length << std::endl;
    switch (type) {
      case IMAGE_ROOTS::DEX_CACHES:
        {
          parse_dex_caches<ART_T, PTR_T>(struct_offset + offsetof(jarray_t, elements), array->length);
          break;
        }

      case IMAGE_ROOTS::CLASS_ROOTS:
        {
          parse_class_roots<ART_T, PTR_T>(struct_offset + offsetof(jarray_t, elements), array->length);
          break;
        }

      case ART_44::IMAGE_ROOTS::CLASS_LOADER:
        {
          //parse_dex_caches<ART_T, PTR_T>(struct_offset + offsetof(jarray_t, elements), array->length);
          break;
        }

      default:
        {
          LOG(WARNING) << to_string(type) << " is !handle yet!";
        }
    }

  }
}


template<typename ART_T, typename PTR_T>
void Parser::parse_class_roots(size_t offset, size_t size) {
  using jclass_t = typename ART_T::template jclass_t<>;
  using jstring_t    = typename ART_T::template jstring_t<>;
  VLOG(VDEBUG) << "Parsing Class Roots" << std::endl;

  for (size_t i = 0; i < size; ++i) {
    uint32_t object_offset = stream_->read_integer<uint32_t>(offset + i * sizeof(uint32_t)) - imagebase_;
    parse_class<ART_T, PTR_T>(object_offset);
  }
}

template<typename ART_T, typename PTR_T>
void Parser::parse_class(size_t offset) {
  using jclass_t     = typename ART_T::template jclass_t<>;
  using jstring_t    = typename ART_T::template jstring_t<>;

  const jclass_t* cls = reinterpret_cast<const jclass_t*>(stream_->read(offset, sizeof(jclass_t)));
  parse_jstring<ART_T, PTR_T>(cls->name - imagebase_);
}

template<typename ART_T, typename PTR_T>
void Parser::parse_jstring(size_t offset) {
  using jstring_t    = typename ART_T::template jstring_t<>;
  const jstring_t* jstring = reinterpret_cast<const jstring_t*>(stream_->read(offset, sizeof(jstring_t)));
  //std::cout << "Class leng: " << std::dec << jstring->count << std::endl;

  uint64_t value_offset = offset + offsetof(jstring_t, value);

  std::u16string str = {
    reinterpret_cast<const char16_t*>(stream_->read(value_offset, static_cast<uint16_t>(jstring->count) * sizeof(char16_t))),
    static_cast<uint16_t>(jstring->count)
  };
  std::cout << u16tou8(str)  << std::endl;
}

template<typename ART_T, typename PTR_T>
void Parser::parse_dex_caches(size_t offset, size_t size) {
  using jobject_t    = typename ART_T::template jobject_t<>;
  using jarray_t     = typename ART_T::template jarray_t<>;
  using jclass_t     = typename ART_T::template jclass_t<>;
  using jstring_t    = typename ART_T::template jstring_t<>;
  using jdex_cache_t = typename ART_T::template jdex_cache_t<>;

  VLOG(VDEBUG) << "Parsing Dex Cache" << std::endl;

  for (size_t i = 0; i < size; ++i) {
    uint32_t object_offset = stream_->read_integer<uint32_t>(offset + i * sizeof(uint32_t)) - imagebase_;
    parse_dex_cache<ART_T, PTR_T>(object_offset);
  }
}

template<typename ART_T, typename PTR_T>
void Parser::parse_dex_cache(size_t object_offset) {
  using jstring_t    = typename ART_T::template jstring_t<>;
  using jdex_cache_t = typename ART_T::template jdex_cache_t<>;
  const jdex_cache_t* cache = reinterpret_cast<const jdex_cache_t*>(stream_->read(object_offset, sizeof(jdex_cache_t)));
  const jstring_t* location = reinterpret_cast<const jstring_t*>(stream_->read(cache->location - imagebase_, sizeof(jstring_t)));

  uint64_t name_offset = cache->location - imagebase_ + offsetof(jstring_t, value);

  if (location->count & 1) {
    size_t len = location->count >> 1;

    std::string location_string = {
      reinterpret_cast<const char*>(stream_->read(name_offset, static_cast<uint16_t>(len) * sizeof(char))),
      len
    };
    std::cout << location_string  << std::endl;
  } else {

    std::u16string location_string = {
      reinterpret_cast<const char16_t*>(stream_->read(name_offset, static_cast<uint16_t>(location->count) * sizeof(char16_t))),
      static_cast<uint16_t>(location->count)
    };
    std::cout << u16tou8(location_string)  << std::endl;
  }
}

template<typename ART_T, typename PTR_T>
void Parser::parse_methods() {
  using art_header_t = typename ART_T::art_header_t;
  using IMAGE_METHODS = typename ART_T::IMAGE_METHODS;

  VLOG(VDEBUG) << "Parsing Image Methods" << std::endl;


  const art_header_t* hdr = reinterpret_cast<const art_header_t*>(stream_->read(0, sizeof(art_header_t)));

  uint32_t nb_methods = file_->header().nb_methods_;
  //TODO check with ART::nb_methods... (more secure)
  for (size_t i = 0; i < nb_methods; ++i) {
    IMAGE_METHODS type = static_cast<IMAGE_METHODS>(i);
    uint64_t address = hdr->image_methods[i];
    VLOG(VDEBUG) << to_string(type) << " at " << std::showbase << std::hex << address;
  }
}


template<typename ART_T, typename PTR_T>
void Parser::parse_objects(size_t offset, size_t size) {
  using jobject_t = typename ART_T::template jobject_t<>;
  using jarray_t  = typename ART_T::template jarray_t<>;
  using jclass_t  = typename ART_T::template jclass_t<>;
  using jstring_t = typename ART_T::template jstring_t<>;

  VLOG(VDEBUG) << "Paring objects at " << std::hex << offset << std::endl;
  //const jarray_t* array = reinterpret_cast<const jarray_t*>(stream_->read(offset, sizeof(jarray_t)));
  //std::cout << std::dec << "nb elements " << array->length << std::endl;;
}


template<typename ART_T, typename PTR_T>
void Parser::parse_art_fields(size_t offset, size_t size) {
  VLOG(VDEBUG) << "Paring ART Fields at " << std::hex << offset << std::endl;
}

template<typename ART_T, typename PTR_T>
void Parser::parse_art_methods(size_t offset, size_t size) {
  VLOG(VDEBUG) << "Paring ART Methods at " << std::hex << offset << std::endl;
  // 0x6ff99db0: long[] length:65533
  const PTR_T* methods = reinterpret_cast<const PTR_T*>(stream_->read(offset, size));
  // 26740: 0x70a7ea60
  PTR_T get_device_id = methods[26740];

  std::cout << std::hex << "Get device ID " << get_device_id << std::endl;
}

template<typename ART_T, typename PTR_T>
void Parser::parse_interned_strings(size_t offset, size_t size) {

}
#endif

}
}
