##############################################################################
#
# Copyright (c) 2011 Nexedi SA and Contributors. All Rights Reserved.
#
# WARNING: This program as such is intended to be used by professional
# programmers who take the whole responsibility of assessing all potential
# consequences resulting from its eventual inadequacies and bugs
# End users who are looking for a ready-to-use solution with commercial
# guarantees and support are strongly advised to contract a Free Software
# Service Company
#
# This program is Free Software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 3
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
##############################################################################
import os
import glob
import json
import logging

from . import logger
from .ProcessManager import SubprocessError, format_command
from .SlapOSControler import SlapOSControler
from .Utils import createFolder
from slapos.grid.utils import md5digest

def dealShebang(run_test_suite_path):
  with open(run_test_suite_path) as f:
    if f.read(2) == '#!':
      return f.readline().split(None, 1)
  return []

class UnitTestRunner(object):

  def __init__(self, testnode):
    self.testnode = testnode

  def _getSlapOSControler(self, working_directory, use_local_shared_part):
    """
    Create a SlapOSControler
    """
    return SlapOSControler(
               working_directory,
               self.testnode.config,
               use_local_shared_part=use_local_shared_part)

  def _prepareSlapOS(self, working_directory, slapos_instance,
          create_partition=1, software_path_list=None, use_local_shared_part=False, **kw):
    """
    Launch slapos to build software and partitions
    """
    slapproxy_log = os.path.join(self.testnode.config['log_directory'],
                                  'slapproxy.log')
    logger.debug('Configured slapproxy log to %r', slapproxy_log)
    reset_software = slapos_instance.retry_software_count > 10
    if reset_software:
      slapos_instance.retry_software_count = 0
    logger.info('testnode, retry_software_count: %r',
             slapos_instance.retry_software_count)

    # XXX Create a new controler because working_directory can be
    #     Different depending of the preparation
    slapos_controler = self._getSlapOSControler(
        working_directory,
        use_local_shared_part)

    slapos_controler.initializeSlapOSControler(slapproxy_log=slapproxy_log,
       process_manager=self.testnode.process_manager, reset_software=reset_software,
       software_path_list=software_path_list)
    self.testnode.process_manager.supervisord_pid_file = os.path.join(\
         slapos_controler.instance_root, 'var', 'run', 'supervisord.pid')
    method_list= ["runSoftwareRelease"]
    if create_partition:
      method_list.append("runComputerPartition")
    for method_name in method_list:
      slapos_method = getattr(slapos_controler, method_name)
      logger.debug("Before status_dict = slapos_method(...)")
      status_dict = slapos_method(self.testnode.config,
                                  environment=self.testnode.config['environment'],
                                  **kw)
      logger.info(status_dict)
      logger.debug("After status_dict = slapos_method(...)")
      if status_dict['status_code'] != 0:
         slapos_instance.retry = True
         slapos_instance.retry_software_count += 1
         raise SubprocessError(status_dict)
      else:
         slapos_instance.retry_software_count = 0
    return status_dict

  def prepareSlapOSForTestNode(self, test_node_slapos):
    """
    We will build slapos software needed by the testnode itself,
    like the building of selenium-runner by default
    """
    # report-url, report-project and suite-url are required to seleniumrunner
    # instance. This is a hack which must be removed.
    config = self.testnode.config
    return self._prepareSlapOS(test_node_slapos.working_directory,
              test_node_slapos, create_partition=0,
              software_path_list=config.get("software_list"),
              cluster_configuration={
                'report-url': config.get("report-url", ""),
                'report-project': config.get("report-project", ""),
                'suite-url': config.get("suite-url", ""),
              })

  def prepareSlapOSForTestSuite(self, node_test_suite):
    """
    Build softwares needed by testsuites.
    """
    return self._prepareSlapOS(node_test_suite.working_directory,
              node_test_suite,
              software_path_list=[node_test_suite.custom_profile_path],
              cluster_configuration={'_': json.dumps(node_test_suite.cluster_configuration)},
              use_local_shared_part=True)

  def getInstanceRoot(self, node_test_suite):
    return self._getSlapOSControler(
      node_test_suite.working_directory, True).instance_root

  def runTestSuite(self, node_test_suite, portal_url):
    config = self.testnode.config
    run_test_suite_path_list = glob.glob(
        self.getInstanceRoot(node_test_suite) + "/*/bin/runTestSuite")
    try:
      run_test_suite_path = min(run_test_suite_path_list)
    except ValueError:
      raise ValueError('No runTestSuite provided in installed partitions.')
    # Deal with Shebang size limitation
    invocation_list = dealShebang(run_test_suite_path)
    invocation_list += (run_test_suite_path,
      '--master_url', portal_url,
      '--revision', node_test_suite.revision,
      '--test_node_title', config['test_node_title'],
      '--test_suite', node_test_suite.test_suite,
      '--test_suite_title', node_test_suite.test_suite_title)
    soft = config['slapos_directory'] + '/soft/'
    software_list = [soft + md5digest(x) for x in config['software_list']]
    PATH = os.getenv('PATH', '')
    PATH = ':'.join(x + '/bin' for x in software_list) + (PATH and ':' + PATH)
    SLAPOS_TEST_SHARED_PART_LIST = os.pathsep.join(
        self._getSlapOSControler(
            node_test_suite.working_directory,
            True
        ).shared_part_list)
    SLAPOS_TEST_LOG_DIRECTORY = node_test_suite.log_folder_path
    supported_parameter_set = set(self.testnode.process_manager
      .getSupportedParameterList(run_test_suite_path))
    def path(name, compat): # BBB
        path, = filter(os.path.exists, (base + relative
          for relative in ('/bin/' + name, '/parts/' + compat)
          for base in software_list))
        return path
    for option, value in (
        ('--firefox_bin', lambda: path('firefox', 'firefox/firefox-slapos')),
        ('--frontend_url', lambda: config['frontend_url']),
        ('--node_quantity', lambda: config['node_quantity']),
        ('--xvfb_bin', lambda: path('xvfb', 'xserver/bin/Xvfb')),
        ('--project_title', lambda: node_test_suite.project_title),
        ('--shared_part_list', lambda: SLAPOS_TEST_SHARED_PART_LIST),
        ('--log_directory', lambda: SLAPOS_TEST_LOG_DIRECTORY),
        ):
      if option in supported_parameter_set:
        invocation_list += option, value()

    # TODO : include testnode correction ( b111682f14890bf )
    if hasattr(node_test_suite,'additional_bt5_repository_id'):
      additional_bt5_path = os.path.join(
              node_test_suite.working_directory,
              node_test_suite.additional_bt5_repository_id)
      invocation_list.extend(["--bt5_path", additional_bt5_path])
    # From this point, test runner becomes responsible for updating test
    # result. We only do cleanup if the test runner itself is not able
    # to run.
    createFolder(node_test_suite.test_suite_directory, clean=True)

    # Log the actual command with root logger
    root_logger = logging.getLogger()
    root_logger.info(
        "Running test suite with: %s",
        format_command(*invocation_list, PATH=PATH))

    def hide_distributor_url(s):
      # type: (bytes) -> bytes
      return s.replace(portal_url.encode('utf-8'), b'$DISTRIBUTOR_URL')

    self.testnode.process_manager.spawn(*invocation_list, PATH=PATH,
                          SLAPOS_TEST_SHARED_PART_LIST=SLAPOS_TEST_SHARED_PART_LIST,
                          SLAPOS_TEST_LOG_DIRECTORY=SLAPOS_TEST_LOG_DIRECTORY,
                          cwd=node_test_suite.test_suite_directory,
                          log_prefix='runTestSuite',
                          output_replacers=(hide_distributor_url,),
                          get_output=False)

  def getRelativePathUsage(self):
    """
    Used by the method testnode.constructProfile() to know
    if the software.cfg have to use relative path or not.
    """
    return False