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


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)
    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))
