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

import contextlib
import json
import logging
from lxml import etree
import os
import StringIO

from autotest_lib.client.common_lib import utils


class ChartFixture:
    """Sets up chart tablet to display dummy scene image."""
    DISPLAY_SCRIPT = '/usr/local/autotest/bin/display_chart.py'

    def __init__(self, chart_host, scene_uri):
        self.host = chart_host
        self.scene_uri = scene_uri
        self.display_pid = None

    def initialize(self):
        """Prepare scene file and display it on chart host."""
        logging.info('Prepare scene file')
        chart_dir = self.host.get_tmp_dir()
        scene_path = os.path.join(
                chart_dir, self.scene_uri[self.scene_uri.rfind('/') + 1:])
        self.host.run('wget', args=('-O', scene_path, self.scene_uri))
        self.host.run('chmod', args=('-R', '755', chart_dir))

        logging.info('Display scene file')
        self.display_pid = self.host.run_background(
                'python %s %s' % (self.DISPLAY_SCRIPT, scene_path))
        # TODO(inker): Suppose chart should be displayed very soon. Or require
        # of waiting until chart actually displayed.

    def cleanup(self):
        """Cleanup display script."""
        if self.display_pid is not None:
            self.host.run('kill', args=('-2', str(self.display_pid)))


def get_chart_address(host_address, args):
    """Get address of chart tablet from commandline args or mapping logic in
    test lab.

    @param host_address: a list of hostname strings.
    @param args: a dict parse from commandline args.
    @return:
        A list of strings for chart tablet addresses.
    """
    address = utils.args_to_dict(args).get('chart')
    if address is not None:
        return address.split(',')
    elif utils.is_in_container():
        return [
                utils.get_lab_chart_address(host)
                for host in host_address
        ]
    else:
        return None


class DUTFixture:
    """Sets up camera filter for target camera facing on DUT."""
    TEST_CONFIG_PATH = '/var/cache/camera/test_config.json'
    GENERATE_CAMERA_PROFILE = os.path.join('/usr', 'bin',
                                           'generate_camera_profile')
    GENERATE_CAMERA_PROFILE_BACKUP = GENERATE_CAMERA_PROFILE + '.bak'
    CAMERA_PROFILE_PATH = ('/mnt/stateful_partition/encrypted/var/cache/camera'
                           '/media_profiles.xml')

    def __init__(self, test, host, facing):
        self.test = test
        self.host = host
        self.facing = facing

    @contextlib.contextmanager
    def _set_selinux_permissive(self):
        selinux_mode = self.host.run_output('getenforce')
        self.host.run('setenforce 0')
        yield
        self.host.run('setenforce', args=(selinux_mode, ))

    def _filter_camera_profile(self, content, facing):
        """Filter camera profile of target facing from content of camera
        profile.

        @return:
            New camera profile with only target facing, camera ids are
            renumbered from 0.
        """
        tree = etree.parse(
                StringIO.StringIO(content),
                parser=etree.XMLParser(compact=False))
        root = tree.getroot()
        profiles = root.findall('CamcorderProfiles')
        logging.debug('%d number of camera(s) found in camera profile',
                      len(profiles))
        assert 1 <= len(profiles) <= 2
        if len(profiles) == 2:
            cam_id = 0 if facing == 'back' else 1
            for p in profiles:
                if cam_id == int(p.attrib['cameraId']):
                    p.attrib['cameraId'] = '0'
                else:
                    root.remove(p)
        else:
            with self.test._login_chrome(
                    board=self.test._get_board_name(),
                    reboot=False), self._set_selinux_permissive():
                has_front_camera = (
                        'feature:android.hardware.camera.front' in self.host.
                        run_output('android-sh -c "pm list features"'))
                logging.debug('has_front_camera=%s', has_front_camera)
            if (facing == 'front') != has_front_camera:
                root.remove(profiles[0])
        return etree.tostring(
                tree, xml_declaration=True, encoding=tree.docinfo.encoding)

    def _read_file(self, filepath):
        """Read content of filepath from host."""
        tmp_path = os.path.join(self.test.tmpdir, os.path.basename(filepath))
        self.host.get_file(filepath, tmp_path, delete_dest=True)
        with open(tmp_path) as f:
            return f.read()

    def _write_file(self, filepath, content, permission=None, owner=None):
        """Write content to filepath on remote host.
        @param permission: set permission to 0xxx octal number of remote file.
        @param owner: set owner of remote file.
        """
        tmp_path = os.path.join(self.test.tmpdir, os.path.basename(filepath))
        with open(tmp_path, 'w') as f:
            f.write(content)
        if permission is not None:
            os.chmod(tmp_path, permission)
        self.host.send_file(tmp_path, filepath, delete_dest=True)
        if owner is not None:
            self.host.run('chown', args=(owner, filepath))

    def initialize(self):
        """Filter out camera other than target facing on DUT."""
        logging.info('Restart camera service with filter option')
        self._write_file(
                self.TEST_CONFIG_PATH,
                json.dumps({
                        'enable_back_camera': self.facing == 'back',
                        'enable_front_camera': self.facing == 'front',
                        'enable_external_camera': False
                }),
                owner='arc-camera')
        self.host.run('restart cros-camera')

        # To replace camera profile in ARC++ container, arc_setup will run
        # GENERATE_CAMERA_PROFILE and mount its generated profile under
        # CAMERA_PROFILE_PATH into container.
        logging.info('Replace camera profile in ARC++ container')
        self.host.run(
                'mv',
                args=(self.GENERATE_CAMERA_PROFILE,
                      self.GENERATE_CAMERA_PROFILE_BACKUP))
        self._write_file(
                self.GENERATE_CAMERA_PROFILE,
                '''\
#!/bin/bash
# Put this executable file to %r to make sure arc-setup knows
# we're using dynamic media_profiles.xml copy from host path
# %r''' % (self.GENERATE_CAMERA_PROFILE, self.CAMERA_PROFILE_PATH),
                permission=0755)
        profile = self._read_file(self.CAMERA_PROFILE_PATH)
        new_profile = self._filter_camera_profile(profile, self.facing)
        self._write_file(self.CAMERA_PROFILE_PATH, new_profile)
        self.host.run('restart ui')

    def cleanup(self):
        """Cleanup camera filter."""
        logging.info('Remove filter option and restore camera service')
        self.host.run('rm', args=('-f', self.TEST_CONFIG_PATH))
        self.host.run('restart cros-camera')

        # Restore GENERATE_CAMERA_PROFILE to regenerate camera profile on DUT.
        logging.info('Restore camera profile in ARC++ container')
        self.host.run(
                'mv',
                args=(self.GENERATE_CAMERA_PROFILE_BACKUP,
                      self.GENERATE_CAMERA_PROFILE),
                ignore_status=True)
        self.host.run('restart ui')