# coding=utf-8
# Copyright 2014 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import (absolute_import, division, generators, nested_scopes, print_function,
                        unicode_literals, with_statement)

import logging
import sys
from contextlib import contextmanager

from twitter.common.collections import maybe_list

from pants.base.payload import Payload
from pants.base.target import Target
from pants.base.workunit import WorkUnit
from pants.goal.context import Context


def create_option_values(option_values):
  """Create a fake OptionValues object for testing.

  :param dict option_values: A dict of option name -> value.
  """
  class TestOptionValues(object):
    def __init__(self):
      self.__dict__ = option_values
    def __getitem__(self, key):
      return getattr(self, key)
  return TestOptionValues()


def create_options(options):
  """Create a fake Options object for testing.

  Note that the returned object only provides access to the provided options values. There is
  no registration mechanism on this object. Code under test shouldn't care about resolving
  cmd-line flags vs. config vs. env vars etc. etc.

  :param dict options: A dict of scope -> (dict of option name -> value).
  """
  class TestOptions(object):
    def for_scope(self, scope):
      return create_option_values(options[scope])

    def for_global_scope(self):
      return self.for_scope('')

    def passthru_args_for_scope(self, scope):
      return []

    def items(self):
      return options.items()

    def registration_args_iter_for_scope(self, scope):
      return []

    def payload_for_scope(self, scope):
      return Payload()

    def __getitem__(self, key):
      return self.for_scope(key)
  return TestOptions()


class TestContext(Context):
  """A Context to use during unittesting.

  Stubs out various dependencies that we don't want to introduce in unit tests.

  TODO: Instead of extending the runtime Context class, create a Context interface and have
  TestContext and a runtime Context implementation extend that. This will also allow us to
  isolate the parts of the interface that a Task is allowed to use vs. the parts that the
  task-running machinery is allowed to use.
  """
  class DummyWorkUnit(object):
    """A workunit stand-in that sends all output to stderr.

   These outputs are typically only used by subprocesses spawned by code under test, not
   the code under test itself, and would otherwise go into some reporting black hole.  The
   testing framework will only display the stderr output when a test fails.

   Provides no other tracking/labeling/reporting functionality. Does not require "opening"
   or "closing".
   """

    def output(self, name):
      return sys.stderr

    def set_outcome(self, outcome):
      return sys.stderr.write('\nWorkUnit outcome: {}\n'.format(WorkUnit.outcome_string(outcome)))

  class DummyRunTracker(object):
    """A runtracker stand-in that does no actual tracking."""
    class DummyArtifactCacheStats(object):
      def add_hit(self, cache_name, tgt): pass
      def add_miss(self, cache_name, tgt): pass

    artifact_cache_stats = DummyArtifactCacheStats()

  @contextmanager
  def new_workunit(self, name, labels=None, cmd=''):
    sys.stderr.write('\nStarting workunit {}\n'.format(name))
    yield TestContext.DummyWorkUnit()

  @property
  def log(self):
    return logging.getLogger('test')

  def submit_background_work_chain(self, work_chain, parent_workunit_name=None):
    # Just do the work synchronously, so we don't need a run tracker, background workers and so on.
    for work in work_chain:
      for args_tuple in work.args_tuples:
        work.func(*args_tuple)

  def subproc_map(self, f, items):
    # Just execute in-process.
    return map(f, items)


# TODO: Make Console and Workspace into subsystems, and simplify this signature.
def create_context(options=None, target_roots=None, build_graph=None,
                   build_file_parser=None, address_mapper=None,
                   console_outstream=None, workspace=None):
  """Creates a ``Context`` with no options or targets by default.

  :param options: A map of scope -> (map of key to value).

  Other params are as for ``Context``.
  """
  options = create_options(options or {})
  run_tracker = TestContext.DummyRunTracker()
  target_roots = maybe_list(target_roots, Target) if target_roots else []
  return TestContext(options=options, run_tracker=run_tracker, target_roots=target_roots,
                     build_graph=build_graph, build_file_parser=build_file_parser,
                     address_mapper=address_mapper, console_outstream=console_outstream,
                     workspace=workspace)
