"""This module contains methods for running website tours.
These helper methods SHOULD NOT be called directly from tests."""
import os
import re
import textwrap
import time
from r_seleniumbase import config as sb_config
from r_seleniumbase.config import settings
from r_seleniumbase.core import style_sheet
from r_seleniumbase.fixtures import constants
from r_seleniumbase.fixtures import js_utils
from r_seleniumbase.fixtures import page_actions

EXPORTED_TOURS_FOLDER = constants.Tours.EXPORTED_TOURS_FOLDER


def activate_bootstrap(driver):
    """Allows you to use Bootstrap Tours with r_seleniumbase
    http://bootstraptour.com/
    """
    bootstrap_tour_css = constants.BootstrapTour.MIN_CSS
    bootstrap_tour_js = constants.BootstrapTour.MIN_JS

    verify_script = """// Verify Bootstrap Tour activated
                     var tour2 = new Tour({
                     });"""

    backdrop_style = style_sheet.get_bt_backdrop_style()
    js_utils.add_css_style(driver, backdrop_style)
    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, bootstrap_tour_css)
        js_utils.add_js_link(driver, bootstrap_tour_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # Bootstrap needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_bootstrap_activated(driver):
    verify_script = """// Verify Bootstrap Tour activated
                     var tour2 = new Tour({
                     });"""
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_driverjs(driver):
    """Allows you to use DriverJS Tours with r_seleniumbase
    https://kamranahmed.info/driver.js/
    """
    backdrop_style = style_sheet.get_dt_backdrop_style()
    driverjs_css = constants.DriverJS.MIN_CSS
    driverjs_js = constants.DriverJS.MIN_JS

    verify_script = """// Verify DriverJS activated
                     var driverjs2 = Driver.name;
                     """

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, driverjs_css)
        js_utils.add_js_link(driver, driverjs_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # DriverJS needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_driverjs_activated(driver):
    verify_script = """// Verify DriverJS activated
                     var driverjs2 = Driver.name;
                     """
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_hopscotch(driver):
    """Allows you to use Hopscotch Tours with r_seleniumbase
    http://linkedin.github.io/hopscotch/
    """
    hopscotch_css = constants.Hopscotch.MIN_CSS
    hopscotch_js = constants.Hopscotch.MIN_JS
    backdrop_style = style_sheet.get_hops_backdrop_style()

    verify_script = """// Verify Hopscotch activated
                     var hops = hopscotch.isActive;
                     """

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, hopscotch_css)
        js_utils.add_js_link(driver, hopscotch_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # Hopscotch needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_hopscotch_activated(driver):
    verify_script = """// Verify Hopscotch activated
                     var hops = hopscotch.isActive;
                     """
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_introjs(driver):
    """Allows you to use IntroJS Tours with r_seleniumbase
    https://introjs.com/
    """
    intro_css = constants.IntroJS.MIN_CSS
    intro_js = constants.IntroJS.MIN_JS

    theme_color = sb_config.introjs_theme_color
    hover_color = sb_config.introjs_hover_color
    backdrop_style = style_sheet.get_introjs_style() % (
        theme_color,
        hover_color,
        hover_color,
        hover_color,
        theme_color,
    )

    verify_script = """// Verify IntroJS activated
                     var intro2 = introJs();
                     """

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    for x in range(4):
        js_utils.activate_jquery(driver)
        js_utils.add_css_link(driver, intro_css)
        js_utils.add_js_link(driver, intro_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # IntroJS needs a small amount of time to load & activate.
            try:
                driver.execute_script(verify_script)
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_introjs_activated(driver):
    verify_script = """// Verify IntroJS activated
                     var intro2 = introJs();
                     """
    try:
        driver.execute_script(verify_script)
        return True
    except Exception:
        return False


def activate_shepherd(driver):
    """Allows you to use Shepherd Tours with r_seleniumbase
    https://cdnjs.com/libraries/shepherd/1.8.1
    """
    shepherd_js = constants.Shepherd.MIN_JS
    sh_theme_arrows_css = constants.Shepherd.THEME_ARROWS_CSS
    sh_theme_arrows_fix_css = constants.Shepherd.THEME_ARR_FIX_CSS
    sh_theme_default_css = constants.Shepherd.THEME_DEFAULT_CSS
    sh_theme_dark_css = constants.Shepherd.THEME_DARK_CSS
    sh_theme_sq_css = constants.Shepherd.THEME_SQ_CSS
    sh_theme_sq_dark_css = constants.Shepherd.THEME_SQ_DK_CSS
    tether_js = constants.Tether.MIN_JS
    spinner_css = constants.Messenger.SPINNER_CSS
    sh_style = style_sheet.get_sh_style_test()
    backdrop_style = style_sheet.get_sh_backdrop_style()

    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    js_utils.add_css_style(driver, backdrop_style)
    js_utils.wait_for_ready_state_complete(driver)
    js_utils.wait_for_angularjs(driver)
    for x in range(4):
        js_utils.add_css_link(driver, spinner_css)
        js_utils.add_css_link(driver, sh_theme_arrows_css)
        js_utils.add_css_link(driver, sh_theme_arrows_fix_css)
        js_utils.add_css_link(driver, sh_theme_default_css)
        js_utils.add_css_link(driver, sh_theme_dark_css)
        js_utils.add_css_link(driver, sh_theme_sq_css)
        js_utils.add_css_link(driver, sh_theme_sq_dark_css)
        js_utils.add_js_link(driver, tether_js)
        js_utils.add_js_link(driver, shepherd_js)
        time.sleep(0.1)
        for x in range(int(settings.MINI_TIMEOUT * 2.0)):
            # Shepherd needs a small amount of time to load & activate.
            try:
                driver.execute_script(sh_style)  # Verify Shepherd has loaded
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                driver.execute_script(sh_style)  # Need it twice for ordering
                js_utils.wait_for_ready_state_complete(driver)
                js_utils.wait_for_angularjs(driver)
                time.sleep(0.05)
                return
            except Exception:
                time.sleep(0.15)
    js_utils.raise_unable_to_load_jquery_exception(driver)


def is_shepherd_activated(driver):
    sh_style = style_sheet.get_sh_style_test()
    try:
        driver.execute_script(sh_style)  # Verify Shepherd has loaded
        return True
    except Exception:
        return False


def play_shepherd_tour(driver, tour_steps, msg_dur, name=None, interval=0):
    """Plays a Shepherd tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """
        // Start the tour
        tour.start();
        $tour = tour;"""
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp' || event.key === 'ArrowLeft') {
            Shepherd.activeTour.back(); }
        if (event.key === 'PageDown' || event.key === 'ArrowRight') {
            Shepherd.activeTour.next(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_shepherd_activated(driver):
        instructions += extra
        activate_shepherd(driver)

    if len(tour_steps[name]) > 1:
        try:
            selector = re.search(
                r"[\S\s]+{element: '([\S\s]+)', on: [\S\s]+",
                tour_steps[name][1],
            ).group(1)
            selector = selector.replace("\\", "")
            page_actions.wait_for_element_present(
                driver,
                selector,
                by="css selector",
                timeout=settings.SMALL_TIMEOUT,
            )
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )
    driver.execute_script(instructions)
    try:
        page_actions.wait_for_element_visible(
            driver, "a.tour-button-right", by="css selector", timeout=1.2
        )
    except Exception:
        pass
    try:
        driver.execute_script('document.activeElement.blur();')
    except Exception:
        pass
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_element = None
        latest_text = None
    while tour_on:
        try:
            time.sleep(0.01)
            result = driver.execute_script(
                "return Shepherd.activeTour.currentStep.isOpen()"
            )
        except Exception:
            tour_on = False
            result = None
        if result:
            tour_on = True
            if autoplay:
                try:
                    element = driver.execute_script(
                        "return Shepherd.activeTour.currentStep"
                        ".options.attachTo.element"
                    )
                    shep_text = driver.execute_script(
                        "return Shepherd.activeTour.currentStep"
                        ".options.text"
                    )
                except Exception:
                    continue
                if element != latest_element or shep_text != latest_text:
                    latest_element = element
                    latest_text = shep_text
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if (element == latest_element) and (
                        shep_text == latest_text
                    ):
                        driver.execute_script("Shepherd.activeTour.next()")
                        try:
                            latest_element = driver.execute_script(
                                "return Shepherd.activeTour.currentStep"
                                ".options.attachTo.element"
                            )
                            latest_text = driver.execute_script(
                                "return Shepherd.activeTour.currentStep"
                                ".options.text"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                selector = driver.execute_script(
                    "return Shepherd.activeTour"
                    ".currentStep.options.attachTo.element"
                )
                try:
                    js_utils.wait_for_css_query_selector(
                        driver, selector, timeout=settings.SMALL_TIMEOUT
                    )
                except Exception:
                    remove_script = (
                        "jQuery('%s').remove()" % "div.shepherd-content"
                    )
                    driver.execute_script(remove_script)
                    js_utils.post_messenger_error_message(
                        driver,
                        "Tour Error: {'%s'} was not found!" % selector,
                        msg_dur,
                    )
                    time.sleep(0.1)
                driver.execute_script("Shepherd.activeTour.next()")
                if autoplay:
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                tour_on = True
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_bootstrap_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays a Bootstrap tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]);
        // Initialize the tour
        tour.init();
        // Start the tour
        tour.start();
        // Fix timing issue by restarting tour immediately
        tour.restart();
        // Save for later
        $tour = tour;"""
    if interval and interval > 0:
        if interval < 1:
            interval = 1
        interval = str(float(interval) * 1000.0)
        instructions = instructions.replace(
            "duration: 0,", "duration: %s," % interval
        )
    if not is_bootstrap_activated(driver):
        activate_bootstrap(driver)
    if len(tour_steps[name]) > 1:
        try:
            if "element: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+element: '([\S\s]+)',[\S\s]+title: '",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "").replace(":first", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )
    driver.execute_script(instructions)
    tour_on = True
    try:
        page_actions.wait_for_element_visible(
            driver, ".tour-tour", by="css selector", timeout=1.2
        )
    except Exception:
        pass
    try:
        driver.execute_script('document.activeElement.blur();')
    except Exception:
        pass
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = driver.execute_script("return $tour.ended()")
            else:
                page_actions.wait_for_element_present(
                    driver, ".tour-tour", by="css selector", timeout=0.48
                )
                result = False
        except Exception:
            tour_on = False
            result = None
        if result is False:
            tour_on = True
            time.sleep(0.05)
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = driver.execute_script("return $tour.ended()")
                else:
                    page_actions.wait_for_element_present(
                        driver, ".tour-tour", by="css selector", timeout=0.48
                    )
                    result = False
                if result is False:
                    time.sleep(0.05)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_driverjs_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays a DriverJS tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]
        );
        // Start the tour!
        tour.start();
        $tour = tour;"""
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp') { $tour.movePrevious(); }
        if (event.key === 'PageDown') { $tour.moveNext(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_driverjs_activated(driver):
        instructions += extra
        activate_driverjs(driver)

    if len(tour_steps[name]) > 1:
        try:
            if "element: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+element: '([\S\s]+)',[\S\s]+popover: {",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "").replace(":first", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )

    driver.execute_script(instructions)
    driver.execute_script(
        'document.querySelector(".driver-next-btn").focus();'
    )
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_step = 0
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = not driver.execute_script("return $tour.isActivated")
            else:
                page_actions.wait_for_element_visible(
                    driver,
                    "#driver-popover-item",
                    by="css selector",
                    timeout=1.1,
                )
                result = False
        except Exception:
            tour_on = False
            result = None
        if result is False:
            tour_on = True
            driver.execute_script(
                'document.querySelector(".driver-next-btn").focus();'
            )
            if autoplay:
                try:
                    current_step = driver.execute_script(
                        "return $tour.currentStep"
                    )
                except Exception:
                    continue
                if current_step != latest_step:
                    latest_step = current_step
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if current_step == latest_step:
                        driver.execute_script("$tour.moveNext()")
                        try:
                            latest_step = driver.execute_script(
                                "return $tour.currentStep"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = not driver.execute_script(
                        "return $tour.isActivated"
                    )
                else:
                    page_actions.wait_for_element_visible(
                        driver,
                        "#driver-popover-item",
                        by="css selector",
                        timeout=1.1,
                    )
                    result = False
                if result is False:
                    time.sleep(0.1)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_hopscotch_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays a Hopscotch tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]
        };
        // Start the tour!
        hopscotch.startTour(tour);
        $tour = hopscotch;"""
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp' || event.key === 'ArrowLeft') {
            $tour.prevStep(); }
        if (event.key === 'PageDown' || event.key === 'ArrowRight') {
            $tour.nextStep(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_hopscotch_activated(driver):
        instructions += extra
        activate_hopscotch(driver)

    if len(tour_steps[name]) > 1:
        try:
            if "target: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+target: '([\S\s]+)',[\S\s]+title: '",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "").replace(":first", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )

    driver.execute_script(instructions)
    try:
        page_actions.wait_for_element_visible(
            driver, "button.hopscotch-next", by="css selector", timeout=1.2
        )
    except Exception:
        pass
    try:
        driver.execute_script('document.activeElement.blur();')
    except Exception:
        pass
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_step = 0
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = not driver.execute_script("return $tour.isActive")
            else:
                page_actions.wait_for_element_present(
                    driver,
                    ".hopscotch-bubble",
                    by="css selector",
                    timeout=0.4,
                )
                result = False
        except Exception:
            tour_on = False
            result = None
        if result is False:
            tour_on = True
            if autoplay:
                try:
                    current_step = driver.execute_script(
                        "return $tour.getCurrStepNum()"
                    )
                except Exception:
                    continue
                if current_step != latest_step:
                    latest_step = current_step
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if current_step == latest_step:
                        driver.execute_script("$tour.nextStep()")
                        try:
                            latest_step = driver.execute_script(
                                "return $tour.getCurrStepNum()"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = not driver.execute_script("return $tour.isActive")
                else:
                    page_actions.wait_for_element_present(
                        driver,
                        ".hopscotch-bubble",
                        by="css selector",
                        timeout=0.4,
                    )
                    result = False
                if result is False:
                    time.sleep(0.1)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def play_introjs_tour(
    driver, tour_steps, browser, msg_dur, name=None, interval=0
):
    """Plays an IntroJS tour on the current website."""
    instructions = ""
    for tour_step in tour_steps[name]:
        instructions += tour_step
    instructions += """]
        });
        intro.setOption("disableInteraction", true);
        intro.setOption("overlayOpacity", .29);
        intro.setOption("scrollToElement", true);
        intro.setOption("keyboardNavigation", true);
        intro.setOption("exitOnEsc", true);
        intro.setOption("hidePrev", true);
        intro.setOption("nextToDone", true);
        intro.setOption("exitOnOverlayClick", false);
        intro.setOption("showStepNumbers", false);
        intro.setOption("showProgress", false);
        intro.start();
        $tour = intro;
        };
        // Start the tour
        startIntro();
        """
    extra = """
        document.body.addEventListener('keyup', function (event) {
        if (event.key === 'PageUp') { $tour.previousStep(); }
        if (event.key === 'PageDown') { $tour.nextStep(); }
        })"""
    autoplay = False
    if interval and interval > 0:
        autoplay = True
        interval = float(interval)
        if interval < 0.5:
            interval = 0.5

    if not is_introjs_activated(driver):
        instructions += extra
        activate_introjs(driver)

    if len(tour_steps[name]) > 1:
        try:
            if "element: " in tour_steps[name][1]:
                selector = re.search(
                    r"[\S\s]+element: '([\S\s]+)',[\S\s]+intro: '",
                    tour_steps[name][1],
                ).group(1)
                selector = selector.replace("\\", "")
                page_actions.wait_for_element_present(
                    driver,
                    selector,
                    by="css selector",
                    timeout=settings.SMALL_TIMEOUT,
                )
            else:
                selector = "html"
        except Exception:
            js_utils.post_messenger_error_message(
                driver, "Tour Error: {'%s'} was not found!" % selector, msg_dur
            )
            raise Exception(
                "Tour Error: {'%s'} was not found! "
                "Exiting due to failure on first tour step!"
                "" % selector
            )
    driver.execute_script(instructions)
    tour_on = True
    if autoplay:
        start_ms = time.time() * 1000.0
        stop_ms = start_ms + (interval * 1000.0)
        latest_step = 0
    while tour_on:
        try:
            time.sleep(0.01)
            if browser != "firefox":
                result = driver.execute_script("return $tour._currentStep")
            else:
                page_actions.wait_for_element_present(
                    driver, ".introjs-tooltip", by="css selector", timeout=0.4
                )
                result = True
        except Exception:
            tour_on = False
            result = None
        if result is not None:
            tour_on = True
            if autoplay:
                try:
                    current_step = driver.execute_script(
                        "return $tour._currentStep"
                    )
                except Exception:
                    continue
                if current_step != latest_step:
                    latest_step = current_step
                    start_ms = time.time() * 1000.0
                    stop_ms = start_ms + (interval * 1000.0)
                now_ms = time.time() * 1000.0
                if now_ms >= stop_ms:
                    if current_step == latest_step:
                        try:
                            driver.execute_script("$tour.nextStep()")
                        except Exception:
                            driver.execute_script("$tour.exit()")
                        try:
                            latest_step = driver.execute_script(
                                "return $tour._currentStep"
                            )
                            start_ms = time.time() * 1000.0
                            stop_ms = start_ms + (interval * 1000.0)
                        except Exception:
                            pass
                        continue
        else:
            try:
                time.sleep(0.01)
                if browser != "firefox":
                    result = driver.execute_script("return $tour._currentStep")
                else:
                    page_actions.wait_for_element_present(
                        driver,
                        ".introjs-tooltip",
                        by="css selector",
                        timeout=0.4,
                    )
                    result = True
                if result is not None:
                    time.sleep(0.1)
                    continue
                else:
                    return
            except Exception:
                tour_on = False
                time.sleep(0.1)


def export_tour(tour_steps, name=None, filename="my_tour.js", url=None):
    """Exports a tour as a JS file.
    It will include necessary resources as well, such as jQuery.
    You'll be able to copy the tour directly into the Console of
    any web browser to play the tour outside of r_seleniumbase runs."""
    if not name:
        name = "default"
    if name not in tour_steps:
        raise Exception("Tour {%s} does not exist!" % name)
    if not filename.endswith(".js"):
        raise Exception('Tour file must end in ".js"!')
    if not url:
        url = "data:,"

    tour_type = None
    if "Bootstrap" in tour_steps[name][0]:
        tour_type = "bootstrap"
    elif "DriverJS" in tour_steps[name][0]:
        tour_type = "driverjs"
    elif "Hopscotch" in tour_steps[name][0]:
        tour_type = "hopscotch"
    elif "IntroJS" in tour_steps[name][0]:
        tour_type = "introjs"
    elif "Shepherd" in tour_steps[name][0]:
        tour_type = "shepherd"
    else:
        raise Exception("Unknown tour type!")

    instructions = (
        """////////  Load Tour Start Page (if not there now)  ////////\n\n"""
        """if (window.location.href != "%s") {\n"""
        """    window.location.href="%s";\n"""
        """}\n\n"""
        """////////  Resources  ////////\n\n"""
        """function injectCSS(css_link) {"""
        """var head = document.getElementsByTagName("head")[0];"""
        """var link = document.createElement("link");"""
        """link.rel = "stylesheet";"""
        """link.type = "text/css";"""
        """link.href = css_link;"""
        """link.crossorigin = "anonymous";"""
        """head.appendChild(link);"""
        """};\n"""
        """function injectJS(js_link) {"""
        """var head = document.getElementsByTagName("head")[0];"""
        """var script = document.createElement("script");"""
        """script.src = js_link;"""
        """script.defer;"""
        """script.type="text/javascript";"""
        """script.crossorigin = "anonymous";"""
        """script.onload = function() { null };"""
        """head.appendChild(script);"""
        """};\n"""
        """function injectStyle(css) {"""
        """var head = document.getElementsByTagName("head")[0];"""
        """var style = document.createElement("style");"""
        """style.type = "text/css";"""
        """style.appendChild(document.createTextNode(css));"""
        """head.appendChild(style);"""
        """};\n""" % (url, url)
    )

    if tour_type == "bootstrap":
        jquery_js = constants.JQuery.MIN_JS
        bootstrap_tour_css = constants.BootstrapTour.MIN_CSS
        bootstrap_tour_js = constants.BootstrapTour.MIN_JS
        backdrop_style = style_sheet.get_bt_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectJS("%s");\n' % jquery_js
        instructions += "\n"
        instructions += "function loadResources() { "
        instructions += 'if ( typeof jQuery !== "undefined" ) {\n'
        instructions += 'injectCSS("%s");\n' % bootstrap_tour_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % bootstrap_tour_js
        instructions += '} else { window.setTimeout("loadResources();",100); '
        instructions += "} }\n"
        instructions += "loadResources()"

    elif tour_type == "driverjs":
        driverjs_css = constants.DriverJS.MIN_CSS
        driverjs_js = constants.DriverJS.MIN_JS
        backdrop_style = style_sheet.get_dt_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % driverjs_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % driverjs_js

    elif tour_type == "hopscotch":
        hopscotch_css = constants.Hopscotch.MIN_CSS
        hopscotch_js = constants.Hopscotch.MIN_JS
        backdrop_style = style_sheet.get_hops_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % hopscotch_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % hopscotch_js

    elif tour_type == "introjs":
        intro_css = constants.IntroJS.MIN_CSS
        intro_js = constants.IntroJS.MIN_JS
        theme_color = sb_config.introjs_theme_color
        hover_color = sb_config.introjs_hover_color
        backdrop_style = style_sheet.get_introjs_style() % (
            theme_color,
            hover_color,
            hover_color,
            hover_color,
            theme_color,
        )
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % intro_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");' % intro_js

    elif tour_type == "shepherd":
        jquery_js = constants.JQuery.MIN_JS
        shepherd_js = constants.Shepherd.MIN_JS
        sh_theme_arrows_css = constants.Shepherd.THEME_ARROWS_CSS
        sh_theme_arrows_fix_css = constants.Shepherd.THEME_ARR_FIX_CSS
        sh_theme_default_css = constants.Shepherd.THEME_DEFAULT_CSS
        sh_theme_dark_css = constants.Shepherd.THEME_DARK_CSS
        sh_theme_sq_css = constants.Shepherd.THEME_SQ_CSS
        sh_theme_sq_dark_css = constants.Shepherd.THEME_SQ_DK_CSS
        tether_js = constants.Tether.MIN_JS
        spinner_css = constants.Messenger.SPINNER_CSS
        backdrop_style = style_sheet.get_sh_backdrop_style()
        backdrop_style = backdrop_style.replace("\n", "")
        backdrop_style = js_utils.escape_quotes_if_needed(backdrop_style)
        instructions += 'injectCSS("%s");\n' % spinner_css
        instructions += 'injectJS("%s");\n' % jquery_js
        instructions += 'injectJS("%s");\n' % tether_js
        instructions += "\n"
        instructions += "function loadResources() { "
        instructions += 'if ( typeof jQuery !== "undefined" ) {\n'
        instructions += 'injectCSS("%s");' % sh_theme_arrows_css
        instructions += 'injectCSS("%s");' % sh_theme_arrows_fix_css
        instructions += 'injectCSS("%s");' % sh_theme_default_css
        instructions += 'injectCSS("%s");' % sh_theme_dark_css
        instructions += 'injectCSS("%s");' % sh_theme_sq_css
        instructions += 'injectCSS("%s");\n' % sh_theme_sq_dark_css
        instructions += 'injectStyle("%s");\n' % backdrop_style
        instructions += 'injectJS("%s");\n' % shepherd_js
        instructions += '} else { window.setTimeout("loadResources();",100); '
        instructions += "} }\n"
        instructions += "loadResources()"

    instructions += "\n\n////////  Tour Code  ////////\n\n"
    first_instructions = instructions
    instructions = "        "
    if tour_type == "bootstrap":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof Tour !== "undefined" ) {\n'
    elif tour_type == "driverjs":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof Driver !== "undefined" ) {\n'
    elif tour_type == "hopscotch":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof hopscotch !== "undefined" ) {\n'
    elif tour_type == "introjs":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof introJs !== "undefined" ) {\n'
    elif tour_type == "shepherd":
        instructions += "function loadTour() { "
        instructions += 'if ( typeof Shepherd !== "undefined" ) {\n'

    for tour_step in tour_steps[name]:
        instructions += tour_step

    if tour_type == "bootstrap":
        instructions += """]);
            // Initialize the tour
            tour.init();
            // Start the tour
            tour.start();
            $tour = tour;
            $tour.restart();\n"""
    elif tour_type == "driverjs":
        instructions += """]
            );
            // Start the tour!
            tour.start();
            $tour = tour;\n"""
    elif tour_type == "hopscotch":
        instructions += """]
            };
            // Start the tour!
            hopscotch.startTour(tour);
            $tour = hopscotch;\n"""
    elif tour_type == "introjs":
        instructions += """]
            });
            intro.setOption("disableInteraction", true);
            intro.setOption("overlayOpacity", .29);
            intro.setOption("scrollToElement", true);
            intro.setOption("keyboardNavigation", true);
            intro.setOption("exitOnEsc", true);
            intro.setOption("hidePrev", true);
            intro.setOption("nextToDone", true);
            intro.setOption("exitOnOverlayClick", false);
            intro.setOption("showStepNumbers", false);
            intro.setOption("showProgress", false);
            intro.start();
            $tour = intro;
            };
            startIntro();\n"""
    elif tour_type == "shepherd":
        instructions += """
            tour.start();
            $tour = tour;\n"""
    else:
        pass
    instructions = textwrap.dedent(instructions)
    instructions = first_instructions + instructions
    instructions += '\n} else { window.setTimeout("loadTour();",100); } '
    instructions += "}\n"
    instructions += "loadTour()\n"

    exported_tours_folder = EXPORTED_TOURS_FOLDER
    if exported_tours_folder.endswith("/"):
        exported_tours_folder = exported_tours_folder[:-1]
    if not os.path.exists(exported_tours_folder):
        try:
            os.makedirs(exported_tours_folder)
        except Exception:
            pass
    import codecs

    file_path = exported_tours_folder + "/" + filename
    out_file = codecs.open(file_path, "w+", encoding="utf-8")
    out_file.writelines(instructions)
    out_file.close()
    print("\n>>> [%s] was saved!\n" % file_path)
