#!/usr/bin/env python
# Copyright (c) Facebook, Inc. and its affiliates.
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from __future__ import unicode_literals
'Miscellaneous utility functions.'

import itertools
import logging
import os
import shutil
import subprocess
import sys

from contextlib import contextmanager


def recursively_flatten_list(l):
    return itertools.chain.from_iterable(
        (recursively_flatten_list(i) if type(i) is list else (i,))
            for i in l
    )


def run_command(*cmd, **kwargs):
    'The stdout of most fbcode_builder utilities is meant to be parsed.'
    logging.debug('Running: {0} with {1}'.format(cmd, kwargs))
    kwargs['stdout'] = sys.stderr
    subprocess.check_call(cmd, **kwargs)


@contextmanager
def make_temp_dir(d):
    os.mkdir(d)
    try:
        yield d
    finally:
        shutil.rmtree(d, ignore_errors=True)


def _inner_read_config(path):
    '''
    Helper to read a named config file.
    The grossness with the global is a workaround for this python bug:
    https://bugs.python.org/issue21591
    The bug prevents us from defining either a local function or a lambda
    in the scope of read_fbcode_builder_config below.
    '''
    global _project_dir
    full_path = os.path.join(_project_dir, path)
    return read_fbcode_builder_config(full_path)


def read_fbcode_builder_config(filename):
    # Allow one spec to read another
    # When doing so, treat paths as relative to the config's project directory.
    # _project_dir is a "local" for _inner_read_config; see the comments
    # in that function for an explanation of the use of global.
    global _project_dir
    _project_dir = os.path.dirname(filename)

    scope = {'read_fbcode_builder_config': _inner_read_config}
    with open(filename) as config_file:
        code = compile(config_file.read(), filename, mode='exec')
    exec(code, scope)
    return scope['config']


def steps_for_spec(builder, spec, processed_modules=None):
    '''
    Sets `builder` configuration, and returns all the builder steps
    necessary to build `spec` and its dependencies.

    Traverses the dependencies in depth-first order, honoring the sequencing
    in each 'depends_on' list.
    '''
    if processed_modules is None:
        processed_modules = set()
    steps = []
    for module in spec.get('depends_on', []):
        if module not in processed_modules:
            processed_modules.add(module)
            steps.extend(steps_for_spec(
                builder,
                module.fbcode_builder_spec(builder),
                processed_modules
            ))
    steps.extend(spec.get('steps', []))
    return steps


def build_fbcode_builder_config(config):
    return lambda builder: builder.build(
        steps_for_spec(builder, config['fbcode_builder_spec'](builder))
    )
