#!/usr/bin/env python3

import locale
import subprocess
from collections import defaultdict
locale.setlocale(locale.LC_ALL, 'en_US.UTF-8')

header = """
#
#   #####   #######           #     #     #     #     #     #
# ##     #  #     #          # #    #  #  #    # #     #   #
# ##        #     #         #   #   #  #  #   #   #     # #
# ##  ####  #     #        #     #  #  #  #  #     #     #
# ##     #  #     #        #######  #  #  #  #######     #      ###
# ##     #  #     #        #     #  #  #  #  #     #     #      ###
# # #####   #######        #     #   ## ##   #     #     #      ###
#
# Seriously. You shouldn't even be looking at this file unless you're
# debugging generate_mozbuild.py.
#
# DO NOT MODIFY THIS FILE IT IS AUTOGENERATED.
#

skia_opt_flags = []

if CONFIG['MOZ_OPTIMIZE']:
    if CONFIG['CC_TYPE'] == 'clang-cl':
        skia_opt_flags += ['-O2']
    elif CONFIG['CC_TYPE'] in ('clang', 'gcc'):
        skia_opt_flags += ['-O3']

"""

footer = """

# We allow warnings for third-party code that can be updated from upstream.
AllowCompilerWarnings()

FINAL_LIBRARY = 'xul'
LOCAL_INCLUDES += [
    'skia',
]

if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':
    DEFINES['UNICODE'] = True
    DEFINES['_UNICODE'] = True

# We should autogenerate these SSE related flags.

if CONFIG['INTEL_ARCHITECTURE']:
    SOURCES['skia/modules/skcms/skcms.cc'].flags += ['-DSKCMS_DISABLE_SKX']
    skia_ssse3_flags = ['-Dskvx=skvx_ssse3', '-mssse3']
    skia_avx_flags = ['-Dskvx=skvx_avx', '-mavx']
    skia_hsw_flags = ['-Dskvx=skvx_hsw', '-mavx2', '-mf16c', '-mfma']
    SOURCES['skia/src/core/SkBitmapProcState_opts_ssse3.cpp'].flags += skia_ssse3_flags
    SOURCES['skia/src/core/SkBlitMask_opts_ssse3.cpp'].flags += skia_ssse3_flags
    SOURCES['skia/src/core/SkSwizzler_opts_ssse3.cpp'].flags += ['-Dskvx=skvx_ssse3']
    SOURCES['skia/src/core/SkMemset_opts_avx.cpp'].flags += skia_avx_flags
    SOURCES['skia/src/core/SkBlitRow_opts_hsw.cpp'].flags += skia_hsw_flags
    SOURCES['skia/src/core/SkSwizzler_opts_hsw.cpp'].flags += ['-Dskvx=skvx_hsw']
    SOURCES['skia/src/opts/SkOpts_hsw.cpp'].flags += skia_hsw_flags
    SOURCES['skia/modules/skcms/src/skcms_TransformHsw.cc'].flags += skia_hsw_flags

DEFINES['MOZ_SKIA'] = True

DEFINES['SKIA_IMPLEMENTATION'] = 1

DEFINES['SK_PDF_USE_HARFBUZZ_SUBSETTING'] = 1

if CONFIG['MOZ_TREE_FREETYPE']:
    DEFINES['SK_CAN_USE_DLOPEN'] = 0

# Suppress warnings in third-party code.
CXXFLAGS += [
    '-Wno-deprecated-declarations',
    '-Wno-overloaded-virtual',
    '-Wno-sign-compare',
    '-Wno-unreachable-code',
    '-Wno-unused-function',
]
if CONFIG['CC_TYPE'] == 'gcc':
    CXXFLAGS += [
        '-Wno-logical-op',
        '-Wno-maybe-uninitialized',
    ]
if CONFIG['CC_TYPE'] in ('clang', 'clang-cl'):
    CXXFLAGS += [
        '-Wno-implicit-fallthrough',
        '-Wno-inconsistent-missing-override',
        '-Wno-macro-redefined',
        '-Wno-unused-private-field',
    ]

if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('gtk', 'android'):
    LOCAL_INCLUDES += [
        "/gfx/cairo/cairo/src",
    ]
    CXXFLAGS += CONFIG['CAIRO_FT_CFLAGS']

if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk':
    CXXFLAGS += CONFIG['MOZ_PANGO_CFLAGS']

if CONFIG['TARGET_CPU'] in ('mips32', 'mips64'):
    # The skia code uses `mips` as a variable, but it's a builtin preprocessor
    # macro on mips that expands to `1`.
    DEFINES['mips'] = False

# Work around bug 1841199.
if CONFIG['TARGET_CPU'] in ('mips32', 'mips64', 'ppc64'):
    DEFINES['musttail'] = 'nomusttail'

if CONFIG['TARGET_CPU'] == 'loongarch64':
    # In ABI1.0, the compilers disable 128bit SIMD defautly; in ABI2.0, it
    # enable defaultly. The below flags can maintain compatibility.
    CXXFLAGS += ['-mlsx']
    if (
        CONFIG['CC_TYPE'] == 'clang'
        and int(CONFIG["CC_VERSION"].split(".")[0]) >= 18
    ):
        CXXFLAGS += ['-flax-vector-conversions=all']
    else:
        # gcc, clang8 for loongarch64.
        CXXFLAGS += ['-flax-vector-conversions']

    SOURCES += ['skia/src/opts/SkOpts_lasx.cpp']
    SOURCES['skia/src/opts/SkOpts_lasx.cpp'].flags += skia_opt_flags
    SOURCES['skia/src/core/SkBitmapProcState_opts_lasx.cpp'].flags += ['-mlasx']
    SOURCES['skia/src/core/SkBlitRow_opts_lasx.cpp'].flags += ['-mlasx']
    SOURCES['skia/src/core/SkSwizzler_opts_lasx.cpp'].flags += ['-mlasx']
    SOURCES['skia/src/opts/SkOpts_lasx.cpp'].flags += ['-mlasx']
"""

import json

platforms = ['linux', 'mac', 'android', 'win']

def parse_sources(output):
  return set(v.replace('//', 'skia/') for v in output.decode('utf-8').split() if v.endswith('.cpp') or v.endswith('.S'))

def generate_opt_sources():
  cpus = [('intel', 'x86', [':hsw'])]

  opt_sources = {}
  for key, cpu, deps in cpus:
    subprocess.check_output('cd skia && bin/gn gen out/{0} --args=\'target_cpu="{1}"\''.format(key, cpu), shell=True)
    opt_sources[key] = set()
    for dep in deps:
        try:
            output = subprocess.check_output('cd skia && bin/gn desc out/{0} {1} sources'.format(key, dep), shell=True)
            if output:
                opt_sources[key].update(parse_sources(output))
        except subprocess.CalledProcessError as e:
            if e.output.find(b'source_set') < 0:
                raise

  return opt_sources

def generate_platform_sources():
  sources = {}
  platform_args = {
    'win' : 'win_vc="C:/" win_sdk_version="00.0.00000.0" win_toolchain_version="00.00.00000"'
  }
  source_sets = [':core', ':skia', ':clipstack_utils', ':pathops']
  for plat in platforms:
    args = platform_args.get(plat, '')
    subprocess.check_output('cd skia && bin/gn gen out/{0} --args=\'target_os="{0}" {1}\' > /dev/null'.format(plat, args), shell=True)
    output = b''
    for source_set in source_sets:
      set_output = subprocess.check_output('cd skia && bin/gn desc out/{0} {1} sources'.format(plat, source_set), shell=True)
      if set_output:
        output += set_output
    sources[plat] = parse_sources(output)

  plat_deps = {
    ':fontmgr_win' : 'win',
    ':fontmgr_win_gdi' : 'win',
    ':fontmgr_mac_ct' : 'mac',
  }
  for dep, key in plat_deps.items():
    output = subprocess.check_output('cd skia && bin/gn desc out/{1} {0} sources'.format(dep, key), shell=True)
    if output:
      sources[key].update(parse_sources(output))

  deps = {':pdf' : 'pdf'}
  for dep, key in deps.items():
    output = subprocess.check_output('cd skia && bin/gn desc out/linux {} sources'.format(dep), shell=True)
    if output:
      sources[key] = parse_sources(output)

  sources.update(generate_opt_sources())
  return sources


def generate_separated_sources(platform_sources):
  ignorelist = [
    'skia/src/android/',
    'skia/src/effects/Sk',
    'skia/src/effects/imagefilters/',
    'skia/src/fonts/',
    'skia/src/ports/SkImageEncoder',
    'skia/src/ports/SkImageGenerator',
    'SkLight',
    'codec',
    'SkWGL',
    'SkMemory_malloc',
    'third_party',
    'SkAnimCodecPlayer',
    'SkCamera',
    'SkCapture',
    'SkCanvasStack',
    'SkCanvasStateUtils',
    'SkMultiPictureDocument',
    'SkNullCanvas',
    'SkNWayCanvas',
    'SkOverdrawCanvas',
    'SkPaintFilterCanvas',
    'SkParseColor',
    'SkXPS',
    'SkCreateCGImageRef',
    'skia/src/ports/SkGlobalInitialization',
    'skia/src/utils/SkJSON',
  ]

  def isignorelisted(value):
    for item in ignorelist:
      if value.find(item) >= 0:
        return True

    return False

  separated = defaultdict(set, {
    'common': {
      'skia/src/codec/SkCodec.cpp',
      'skia/src/codec/SkCodecImageGenerator.cpp',
      'skia/src/codec/SkColorPalette.cpp',
      'skia/src/codec/SkImageGenerator_FromEncoded.cpp',
      'skia/src/codec/SkPixmapUtils.cpp',
      'skia/src/codec/SkSampler.cpp',
      'skia/src/effects/imagefilters/SkBlendImageFilter.cpp',
      'skia/src/effects/imagefilters/SkBlurImageFilter.cpp',
      'skia/src/effects/imagefilters/SkComposeImageFilter.cpp',
      'skia/src/effects/imagefilters/SkCropImageFilter.cpp',
      'skia/src/effects/SkBlenders.cpp',
      'skia/src/effects/SkDashPathEffect.cpp',
      'skia/src/encode/SkJpegEncoder_none.cpp',
      'skia/src/encode/SkPngEncoder_none.cpp',
      'skia/src/encode/SkWebpEncoder_none.cpp',
      'skia/src/ports/SkDiscardableMemory_none.cpp',
      'skia/src/ports/SkGlobalInitialization_default.cpp',
      'skia/src/ports/SkMemory_mozalloc.cpp',
      'skia/src/ports/SkImageGenerator_none.cpp',
      'skia/modules/skcms/skcms.cc',
      'skia/modules/skcms/src/skcms_TransformBaseline.cc',
      'skia/src/core/SkImageFilterTypes.cpp',
    },
    'android': {
      # 'skia/src/ports/SkDebug_android.cpp',
      'skia/src/ports/SkFontHost_cairo.cpp',
      # 'skia/src/ports/SkFontHost_FreeType.cpp',
      'skia/src/ports/SkFontHost_FreeType_common.cpp',
      # 'skia/src/ports/SkTime_Unix.cpp',
      # 'skia/src/utils/SkThreadUtils_pthread.cpp',
    },
    'linux': {
      'skia/src/ports/SkFontHost_cairo.cpp',
      'skia/src/ports/SkFontHost_FreeType_common.cpp',
    },
    'win': set (),
    'intel': {
      'skia/modules/skcms/src/skcms_TransformHsw.cc',
    },
    'arm': set(),
    'arm64': set(),
    'none': set(),
    'pdf': set()
  })

  for plat in platform_sources.keys():
    for value in platform_sources[plat]:
      if isignorelisted(value):
        continue

      if value in separated['common']:
        continue

      key = plat

      if all(value in platform_sources.get(p, {})
             for p in platforms if p != plat):
        key = 'common'

      separated[key].add(value)

  return separated

def uniq(seq):
  seen = set()
  seen_add = seen.add
  return [ x for x in seq if x not in seen and not seen_add(x)]

def write_cflags(f, values, subsearch, cflag, indent):
  def write_indent(indent):
    for _ in range(indent):
        f.write(' ')

  if isinstance(subsearch, str):
    subsearch = [ subsearch ]

  def isallowlisted(value):
    for item in subsearch:
      if value.find(item) >= 0:
        return True

    return False

  val_list = uniq(sorted(values, key=lambda x: x.lower()))

  if len(val_list) == 0:
    return

  for val in val_list:
    if isallowlisted(val):
      write_indent(indent)
      f.write("SOURCES[\'" + val + "\'].flags += " + cflag + "\n")

opt_allowlist = [
  'SkOpts',
  'SkBitmapProcState',
  'SkBlitRow',
  'SkBlitter',
  'SkMemset',
  'SkSpriteBlitter',
  'SkSwizzler',
  'SkMatrix.cpp',
  'skcms',
  '_opts',
]

# Unfortunately for now the gpu and pathops directories are
# non-unifiable. Keep track of this and fix it.
unified_ignorelist = [
  'FontHost',
  'SkBitmapProcState_matrixProcs.cpp',
  'SkBlitter_A8.cpp',
  'SkBlitter_ARGB32.cpp',
  'SkBlitter_Sprite.cpp',
  'SkCpu.cpp',
  'SkScan_Antihair.cpp',
  'SkScan_AntiPath.cpp',
  'SkParse.cpp',
  'SkPDFFont.cpp',
  'SkPDFDevice.cpp',
  'SkPDFType1Font.cpp',
  'SkPictureData.cpp',
  'SkColorSpace',
  'SkPath.cpp',
  'SkPathOpsDebug.cpp',
  'SkParsePath.cpp',
  'SkRecorder.cpp',
  'SkRTree.cpp',
  'SkVertices.cpp',
  'SkSLLexer.cpp',
  'SkTypeface_mac_ct.cpp',
] + opt_allowlist

def write_sources(f, values, indent):
  def isignorelisted(value):
    for item in unified_ignorelist:
      if value.find(item) >= 0:
        return True

    return False

  sources = {}
  sources['nonunified'] = set()
  sources['unified'] = set()

  for item in values:
    if isignorelisted(item):
      sources['nonunified'].add(item)
    else:
      sources['unified'].add(item)

  write_list(f, "UNIFIED_SOURCES", sources['unified'], indent)
  write_list(f, "SOURCES", sources['nonunified'], indent)

def write_list(f, name, values, indent):
  def write_indent(indent):
    for _ in range(indent):
        f.write(' ')

  val_list = uniq(sorted(values, key=lambda x: x.lower()))

  if len(val_list) == 0:
    return

  write_indent(indent)
  f.write(name + ' += [\n')
  for val in val_list:
    write_indent(indent + 4)
    f.write('\'' + val + '\',\n')

  write_indent(indent)
  f.write(']\n')

def write_mozbuild(sources):
  filename = 'moz.build'
  f = open(filename, 'w')

  f.write(header)

  write_sources(f, sources['common'], 0)
  write_cflags(f, sources['common'], opt_allowlist, 'skia_opt_flags', 0)

  f.write("if CONFIG['MOZ_ENABLE_SKIA_PDF']:\n")
  write_sources(f, sources['pdf'], 4)

  f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':\n")
  write_sources(f, sources['android'], 4)

  f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] in ('cocoa', 'uikit'):\n")
  write_sources(f, sources['mac'], 4)

  f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gtk':\n")
  write_sources(f, sources['linux'], 4)

  f.write("if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':\n")
  write_list(f, "SOURCES", sources['win'], 4)

  f.write("if CONFIG['INTEL_ARCHITECTURE']:\n")
  write_sources(f, sources['intel'], 4)
  write_cflags(f, sources['intel'], opt_allowlist, 'skia_opt_flags', 4)

  if sources['arm']:
    f.write("elif CONFIG['TARGET_CPU'] == 'arm' and CONFIG['CC_TYPE'] in ('clang', 'gcc'):\n")
    write_sources(f, sources['arm'], 4)
    write_cflags(f, sources['arm'], opt_allowlist, 'skia_opt_flags', 4)

  if sources['arm64']:
    f.write("elif CONFIG['TARGET_CPU'] == 'aarch64':\n")
    write_sources(f, sources['arm64'], 4)
    write_cflags(f, sources['arm64'], opt_allowlist, 'skia_opt_flags', 4)

  if sources['none']:
    f.write("else:\n")
    write_sources(f, sources['none'], 4)

  f.write(footer)

  f.close()

  print('Wrote ' + filename)

def main():
  platform_sources = generate_platform_sources()
  separated_sources = generate_separated_sources(platform_sources)
  write_mozbuild(separated_sources)


if __name__ == '__main__':
  main()
