# Copyright (c) 2012 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 json, logging, threading, time, traceback

from autotest_lib.client.common_lib import error
from autotest_lib.server import autotest, test
from autotest_lib.server.cros.faft.config.config import Config as FAFTConfig

_RETRY_SUSPEND_ATTEMPTS = 1
_RETRY_SUSPEND_MS = 10000
_SUSPEND_WAIT_SECONDS = 30
_BOOT_WAIT_SECONDS = 100


class power_SuspendShutdown(test.test):
    """Test power manager fallback to power-off if suspend fails."""
    version = 1

    def initialize(self, host):
        """
        Initial settings before running test.

        @param host: Host/DUT object to run test on.

        """
        # save original boot id
        self.orig_boot_id = host.get_boot_id()
        self.host = host

        # override /sys/power/state via bind mount
        logging.info('binding /dev/full to /sys/power/state')
        host.run('mount --bind /dev/full /sys/power/state')

        # override suspend retry attempts via bind mount
        logging.info('settings retry_suspend_attempts to %s',
                     _RETRY_SUSPEND_ATTEMPTS)
        host.run('echo %s > /tmp/retry_suspend_attempts;'
                 ' mount --bind /tmp/retry_suspend_attempts'
                 ' /usr/share/power_manager/retry_suspend_attempts'
                 % _RETRY_SUSPEND_ATTEMPTS)

        # override suspend retry interval via bind mount
        logging.info('settings retry_suspend_ms to %s',
                     _RETRY_SUSPEND_MS)
        host.run('echo %s > /tmp/retry_suspend_ms;'
                 ' mount --bind /tmp/retry_suspend_ms'
                 ' /usr/share/power_manager/retry_suspend_ms'
                 % _RETRY_SUSPEND_MS)

        # restart powerd to pick up new retry settings
        logging.info('restarting powerd')
        host.run('restart powerd')
        time.sleep(2)


    def platform_check(self, platform_name):
        """
        Raises error if device does not have a lid.

        @param platform_name: Name of the platform

        """
        client_attr = FAFTConfig(platform_name)

        if not client_attr.has_lid:
            raise error.TestError(
                    'This test does nothing on devices without a lid.')

        if client_attr.chrome_ec and not 'lid' in client_attr.ec_capability:
            raise error.TestNAError("TEST IT MANUALLY! Chrome EC can't control "
                    "lid on the device %s" % client_attr.platform)


    def login_into_dut(self, client_autotest, thread_started_evt,
                       exit_without_logout=True):
        """
        Runs the Desktopui_Simple login client test in a seperate thread. The
        Desktopui_Simple client test will exit without logout.

        @param client_autotest: Client autotest name to login into DUT

        @param thread_started_evt: Thread attribute to start the thread

        @param exit_without_logout: if flag is set thread exists without logout.
                                    if not set, thread will wait fot logout
                                    event.

        """
        logging.info('Login into client started')
        thread_started_evt.set()
        try:
            self.autotest_client.run_test(client_autotest,
                                          exit_without_logout=
                                          exit_without_logout)
        except:
            logging.info('DUT login process failed')


    def create_thread(self, client_autotest, exit_without_logout):
        """
        Created seperate thread for client test

        @param client_autotest: Client autotest name to login into DUT

        @param exit_without_logout: if flag is set thread exists without logout.
                                    if not set, thread will wait fot logout
                                    event.
        @return t: thread object

        """
        thread_started_evt = threading.Event()
        logging.info('Launching Desktopui_simplelogin thread')
        try:
            t = threading.Thread(target=self.login_into_dut,
                                 args=(client_autotest,
                                 thread_started_evt, exit_without_logout))
        except:
            raise error.TestError('Thread creation failed')
        t.start()
        thread_started_evt.wait()
        logging.info('Login thread started')
        return t


    def logged_in(self):
        """
        Checks if the host has a logged in user.

        @param host: Host/DUT object

        @return True if a user is logged in on the device.

        """
        host = self.host
        try:
            out = host.run('cryptohome --action=status').stdout.strip()
        except:
            return False
        try:
            status = json.loads(out)
        except ValueError:
            logging.info('Cryptohome did not return a value, retrying.')
            return False

        return any((mount['mounted'] for mount in status['mounts']))


    def run_once(self, client_autotest):
        """
        Run the acutal test on device.

        @param client_autotest: Client autotest name to login into DUT

        @param host: Host/DUT object

        """
        # check platform is capable of running the test
        host = self.host
        platform = host.run_output('mosys platform name')
        self.platform_check(platform)
        self.autotest_client = autotest.Autotest(host)
        logging.info('platform is %s', platform)
        exit_without_logout = True
        t = self.create_thread(client_autotest, exit_without_logout)
        t.join()

        # Waiting for the login thread to finish
        max_wait_time = 15
        for check_count in range(int(max_wait_time)):
            if check_count == max_wait_time:
                raise error.TestError('Login thread is still'
                                      'alive after %s seconds' % max_wait_time)
            if t.is_alive():
                time.sleep(1)
            else:
                logging.info('Login thread successfully finished')
                break

        # close the lid while logged_in to initiate suspend
        logging.info('closing lid')
        host.servo.lid_close()

        # wait for power manager to give up and shut down
        logging.info('waiting for power off')
        host.wait_down(timeout=_SUSPEND_WAIT_SECONDS,
                       old_boot_id=self.orig_boot_id)

        # ensure host is now off
        if host.is_up():
            raise error.TestFail('DUT still up with lid closed')
        else:
            logging.info('good, host is now off')

        # restart host
        host.servo.lid_open()
        host.wait_up(timeout=_BOOT_WAIT_SECONDS)


    def cleanup(self):
        """Clean up the mounts and restore the settings."""
        # reopen lid - might still be closed due to failure
        host = self.host
        logging.info('reopening lid')
        host.servo.lid_open()

        # try to clean up the mess we've made if shutdown failed
        if host.get_boot_id() == self.orig_boot_id:
            # clean up mounts
            logging.info('cleaning up bind mounts')
            host.run('umount /sys/power/state'
                     ' /usr/share/power_manager/retry_suspend_attempts'
                     ' /usr/share/power_manager/retry_suspend_ms',
                     ignore_status=True)

            # restart powerd to pick up old retry settings
            host.run('restart powerd')

        # Reboot Device to logout and cleanup
        logging.info('Server: reboot client')
        try:
            self.host.reboot()
        except error.AutoservRebootError as e:
            raise error.TestFail('%s.\nTest failed with error %s' % (
                    traceback.format_exc(), str(e)))