# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# copy of chromium's build/config/ios/write_framework_hmap.py

import os
import struct
import sys


def Main(args):
  if len(args) < 4:
    print(
        "Usage: %s output.hmap Foo.framework header1.h..." % args[0],
        file=sys.stderr)
    return 1

  (out, framework, all_headers) = args[1], args[2], args[3:]

  framework_name = os.path.basename(framework).split('.')[0]
  all_headers = map(os.path.abspath, all_headers)
  filelist = {}
  for header in all_headers:
    filename = os.path.basename(header)
    filelist[filename] = header
    filelist[os.path.join(framework_name, filename)] = header
  WriteHmap(out, filelist)
  return 0


def NextGreaterPowerOf2(x):
  return 2**(x).bit_length()


def WriteHmap(output_name, filelist):
  """Generates a header map based on |filelist|.

  Per Mark Mentovai:
    A header map is structured essentially as a hash table, keyed by names used
    in #includes, and providing pathnames to the actual files.

  The implementation below and the comment above comes from inspecting:
    http://www.opensource.apple.com/source/distcc/distcc-2503/distcc_dist/include_server/headermap.py?txt
  while also looking at the implementation in clang in:
    https://llvm.org/svn/llvm-project/cfe/trunk/lib/Lex/HeaderMap.cpp
  """
  magic = 1751998832
  version = 1
  _reserved = 0
  count = len(filelist)
  capacity = NextGreaterPowerOf2(count)
  strings_offset = 24 + (12 * capacity)
  # pylint: disable=W0108
  max_value_length = len(max(filelist.values(), key=lambda v: len(v)))

  out = open(output_name, 'wb')
  out.write(
      struct.pack('<LHHLLLL', magic, version, _reserved, strings_offset, count,
                  capacity, max_value_length))

  # Create empty hashmap buckets.
  buckets = [None] * capacity
  for file, path in filelist.items():
    key = 0
    for c in file:
      key += ord(c.lower()) * 13

    # Fill next empty bucket.
    while buckets[key & capacity - 1] is not None:
      key = key + 1
    buckets[key & capacity - 1] = (file, path)

  next_offset = 1
  for bucket in buckets:
    if bucket is None:
      out.write(struct.pack('<LLL', 0, 0, 0))
    else:
      (file, path) = bucket
      key_offset = next_offset
      prefix_offset = key_offset + len(file) + 1
      suffix_offset = prefix_offset + len(os.path.dirname(path) + os.sep) + 1
      next_offset = suffix_offset + len(os.path.basename(path)) + 1
      out.write(struct.pack('<LLL', key_offset, prefix_offset, suffix_offset))

  # Pad byte since next offset starts at 1.
  out.write(struct.pack('<x'))

  for bucket in buckets:
    if bucket is not None:
      (file, path) = bucket
      base = os.path.dirname(path) + os.sep
      path = os.path.basename(path)
      file = file.encode('UTF-8')
      base = base.encode('UTF-8')
      path = path.encode('UTF-8')
      out.write(struct.pack('<%ds' % len(file), file))
      out.write(struct.pack('<s', b'\0'))
      out.write(struct.pack('<%ds' % len(base), base))
      out.write(struct.pack('<s', b'\0'))
      out.write(struct.pack('<%ds' % len(path), path))
      out.write(struct.pack('<s', b'\0'))


if __name__ == '__main__':
  sys.exit(Main(sys.argv))
