#!/usr/bin/python3
#
# Copyright (C) 2015 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the 'License');
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an 'AS IS' BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

import os
import sys
import tempfile
import threading
import time
import traceback

from android_device import *
from avd import *
from queue import Queue, Empty


# This dict should contain one entry for every density listed in CDD 7.1.1.3.
CTS_THEME_dict = {
    120: "ldpi",
    140: "140dpi",
    160: "mdpi",
    180: "180dpi",
    200: "200dpi",
    213: "tvdpi",
    220: "220dpi",
    240: "hdpi",
    260: "260dpi",
    280: "280dpi",
    300: "300dpi",
    320: "xhdpi",
    340: "340dpi",
    360: "360dpi",
    400: "400dpi",
    420: "420dpi",
    440: "440dpi",
    480: "xxhdpi",
    560: "560dpi",
    640: "xxxhdpi",
}

OUT_FILE = "/sdcard/cts-theme-assets.zip"


class ParallelExecutor(threading.Thread):
    def __init__(self, tasks, setup, q):
        threading.Thread.__init__(self)
        self._q = q
        self._tasks = tasks
        self._setup = setup
        self._result = 0

    def run(self):
        try:
            while True:
                config = self._q.get(block=True, timeout=2)
                for t in self._tasks:
                    try:
                        if t(self._setup, config):
                            self._result += 1
                    except KeyboardInterrupt:
                        raise
                    except:
                        print("Failed to execute thread:", sys.exc_info()[0])
                        traceback.print_exc()
                self._q.task_done()
        except KeyboardInterrupt:
            raise
        except Empty:
            pass

    def get_result(self):
        return self._result


# pass a function with number of instances to be executed in parallel
# each thread continues until config q is empty.
def execute_parallel(tasks, setup, q, num_threads):
    result = 0
    threads = []
    for i in range(num_threads):
        t = ParallelExecutor(tasks, setup, q)
        t.start()
        threads.append(t)
    for t in threads:
        t.join()
        result += t.get_result()
    return result


def print_adb_result(device, out, err):
    print("device: " + device)
    if out is not None:
        print("out:\n" + out)
    if err is not None:
        print("err:\n" + err)


def do_capture(setup, device_serial):
    (themeApkPath, out_path) = setup

    device = AndroidDevice(device_serial)

    version = device.get_version_codename()
    if version == "REL":
        version = str(device.get_version_sdk())

    density = device.get_density()

    if CTS_THEME_dict[density]:
        density_bucket = CTS_THEME_dict[density]
    else:
        density_bucket = str(density) + "dpi"

    out_file = os.path.join(out_path, os.path.join(version, "%s.zip" % density_bucket))

    device.uninstall_package('android.theme.app')

    (out, err, success) = device.install_apk(themeApkPath)
    if not success:
        print("Failed to install APK on " + device_serial)
        print_adb_result(device_serial, out, err)
        return False

    print("Generating images on " + device_serial + "...")
    try:
        (out, err) = device.run_instrumentation_test(
            "android.theme.app/androidx.test.runner.AndroidJUnitRunner")
    except KeyboardInterrupt:
        raise
    except:
        (out, err) = device.run_instrumentation_test(
            "android.theme.app/android.test.InstrumentationTestRunner")

    # Detect test failure and abort.
    if "FAILURES!!!" in out.split():
        print_adb_result(device_serial, out, err)
        return False

    # Make sure that the run is complete by checking the process itself
    print("Waiting for " + device_serial + "...")
    wait_time = 0
    while device.is_process_alive("android.theme.app"):
        time.sleep(1)
        wait_time = wait_time + 1
        if wait_time > 180:
            print("Timed out")
            break

    time.sleep(10)

    print("Pulling images from " + device_serial + " to " + out_file)
    device.run_adb_command("pull " + OUT_FILE + " " + out_file)
    device.run_adb_command("shell rm -rf " + OUT_FILE)
    return True


def get_emulator_path():
    if 'ANDROID_SDK_ROOT' not in os.environ:
        print('Environment variable ANDROID_SDK_ROOT must point to your Android SDK root.')
        sys.exit(1)

    sdk_path = os.environ['ANDROID_SDK_ROOT']
    if not os.path.isdir(sdk_path):
        print("Failed to find Android SDK at ANDROID_SDK_ROOT: %s" % sdk_path)
        sys.exit(1)

    emu_path = os.path.join(os.path.join(sdk_path, 'tools'), 'emulator')
    if not os.path.isfile(emu_path):
        print("Failed to find emulator within ANDROID_SDK_ROOT: %s" % sdk_path)
        sys.exit(1)

    return emu_path


def start_emulator(name, density):
    if name == "local":
        emu_path = ""
    else:
        emu_path = get_emulator_path()

    # Start emulator for 560dpi, normal screen size.
    test_avd = AVD(name, emu_path)
    test_avd.configure_screen(density, 360, 640)
    test_avd.start()
    try:
        test_avd_device = test_avd.get_device()
        test_avd_device.wait_for_device()
        test_avd_device.wait_for_boot_complete()
        return test_avd
    except:
        test_avd.stop()
        return None


def main(argv):
    if 'ANDROID_BUILD_TOP' not in os.environ or 'ANDROID_HOST_OUT' not in os.environ:
        print('Missing environment variables. Did you run build/envsetup.sh and lunch?')
        sys.exit(1)

    theme_apk = os.path.join(os.environ['ANDROID_HOST_OUT'],
                             'cts/android-cts/testcases/CtsThemeDeviceApp.apk')
    if not os.path.isfile(theme_apk):
        print('Couldn\'t find test APK. Did you run make cts?')
        sys.exit(1)

    out_path = os.path.join(os.environ['ANDROID_BUILD_TOP'],
                            'cts/hostsidetests/theme/assets')
    os.system("mkdir -p %s" % out_path)

    if len(argv) is 2:
        for density in CTS_THEME_dict.keys():
            emulator = start_emulator(argv[1], density)
            result = do_capture(setup=(theme_apk, out_path), device_serial=emulator.get_serial())
            emulator.stop()
            if result:
                print("Generated reference images for %ddpi" % density)
            else:
                print("Failed to generate reference images for %ddpi" % density)
                break
    else:
        tasks = [do_capture]
        setup = (theme_apk, out_path)

        devices = enumerate_android_devices()

        if len(devices) > 0:
            device_queue = Queue()
            for device in devices:
                device_queue.put(device)

            result = execute_parallel(tasks, setup, device_queue, len(devices))

            if result > 0:
                print('Generated reference images for %(count)d devices' % {"count": result})
            else:
                print('Failed to generate reference images')
        else:
            print('No devices found')


if __name__ == '__main__':
    main(sys.argv)